¡La última de las relaciones que nos quedaba! La relación muchos a muchos es la última de la serie que tengo hablándoos sobre cómo implementar diferentes tipos de relaciones con el patrón repositorio en ADO.NET. Estamos lejos de acabar de haber visto todo lo que el patrón repositorio puede hacer integrándose con otros patrones, así que no os preocupeis 😉
Como ya es tradición…
Relación muchos a muchos
¿Habéis visto algún blog sin categorías para sus artículos? Para la mayoría de vosotros la respuesta seguramente es «no», y es que las categorías ayudan a distribuir el contenido por temáticas, ayudando al usuario a encontrar el tipo de contenido que le gusta.
Sin embargo, la relación que se establece entre los Artículos y las Categorías es distinto al que teníamos con los Autores. En este caso, un Artículo puede tener múltiples Categorías, mientras que cada Categoría puede estar en múltiples Artículos. Esto es lo que entendemos por una relación muchos a muchos.
Vamos a representar esta relación el código de las clases Artículo y Categoría:
public class Articulo
{
public int Id { get; set; }
public string Titulo { get; set; }
public string Contenido { get; set; }
public DateTime FechaCreacion { get; set; }
public Autor Autor { get; set; }
public List<Categoria> Categorias { get; set; } = new List<Categoria>();
}
public class Categoria
{
public int Id { get; set; }
public string Nombre { get; set; }
public List<Articulo> Articulos { get; set; } = new List<Articulo>();
}
Como podéis apreciar, cada clase tiene un elemento de tipo lista de la otra clase. Como pasaba en el caso de la relación uno a muchos, en nuestra base de datos esta relación se representa como la tabla compuesta CategoriaArticulo. Recordad que la relación entre las tablas y la tabla compuesta tiene que ser de DELETE y UPDATE CASCADE, para que los cambios se apliquen automáticamente.

Nuevamente hacemos una prueba en la base de datos para verificar que tiene sentido lo que hemos hecho.
SELECT CategoriaId, Nombre, ArticuloId, Titulo, Contenido, FechaCreacion
FROM CategoriaArticulo
JOIN Categoria ON CategoriaArticulo.CategoriaId = Categoria.Id
JOIN Articulo ON CategoriaArticulo.ArticuloId = Articulo.Id
ORDER BY ArticuloId;
Considerando ese código obtenemos la siguiente tabla:
CategoriaId | Nombre | ArticuloId | Titulo | Contenido | FechaCreación |
1 | Programación | 1 | Prueba titulo | Esto es el contenido | 2022-04-05 |
3 | ASPNET | 1 | Prueba titulo | Esto es el contenido | 2022-04-05 |
2 | C# | 2 | Otra prueba de titulo | Más contenido interesante que te gustará. | 2022-04-05 |
3 | ASPNET | 2 | Otra prueba de titulo | Más contenido interesante que te gustará. | 2022-04-05 |
Con esto podemos ver que efectivamente un artículo puede tener varias categorías y cada categoría puede pertenecer a varios artículos, cumpliéndose la relación muchos a muchos.
Métodos CRUD de las categorias
public void Add(Categoria entity)
{
using SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmdCategoria = new SqlCommand("INSERT INTO Categoria VALUES(@Nombre)", con);
cmdCategoria.Parameters.AddWithValue("@Nombre", entity.Nombre);
try
{
cmdCategoria.ExecuteNonQuery();
}
catch (Exception ex)
{
/// Gestionar la excepción
}
con.Close();
}
public Categoria GetById(int id)
{
Categoria categoria = new Categoria();
try
{
using SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmd = new SqlCommand("SELECT Categoria.Id AS CategoriaId, Nombre, ArticuloId, Titulo, Contenido, FechaCreacion FROM CategoriaArticulo RIGHT JOIN Categoria ON CategoriaArticulo.CategoriaId = Categoria.Id LEFT JOIN Articulo ON CategoriaArticulo.ArticuloId = Articulo.Id WHERE Categoria.Id = @CategoriaId", con);
cmd.Parameters.AddWithValue("@CategoriaId", id);
SqlDataReader reader = cmd.ExecuteReader();
using (reader)
{
while (reader.Read())
{
categoria.Id = (int)reader["CategoriaId"];
categoria.Nombre = (string)reader["Nombre"];
if (reader["ArticuloId"] == DBNull.Value)
continue;
Articulo articulo = new Articulo();
articulo.Id = (int)reader["ArticuloId"];
articulo.Titulo = (string)reader["Titulo"];
articulo.Contenido = (string)reader["Contenido"];
articulo.FechaCreacion = (DateTime)reader["FechaCreacion"];
categoria.Articulos.Add(articulo);
}
}
con.Close();
}
catch (Exception ex)
{
/// Gestionar la excepción
}
return categoria;
}
public IList<Categoria> GetAll()
{
List<Categoria> categorias = new List<Categoria>();
try
{
using SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmd = new SqlCommand("SELECT Categoria.Id AS CategoriaId, Nombre, ArticuloId, Titulo, Contenido, FechaCreacion FROM CategoriaArticulo RIGHT JOIN Categoria ON CategoriaArticulo.CategoriaId = Categoria.Id LEFT JOIN Articulo ON CategoriaArticulo.ArticuloId = Articulo.Id", con);
SqlDataReader reader = cmd.ExecuteReader();
using (reader)
{
while (reader.Read())
{
Categoria categoria = new Categoria();
categoria.Id = (int)reader["CategoriaId"];
categoria.Nombre = (string)reader["Nombre"];
if (reader["ArticuloId"] == DBNull.Value)
{
categorias.Add(categoria);
continue;
}
Articulo articulo = new Articulo();
articulo.Id = (int)reader["ArticuloId"];
articulo.Titulo = (string)reader["Titulo"];
articulo.Contenido = (string)reader["Contenido"];
articulo.FechaCreacion = (DateTime)reader["FechaCreacion"];
if (categorias.Any(c => c.Id == categoria.Id))
{
categorias.Where(c => c.Id == categoria.Id)
.FirstOrDefault()
?.Articulos.Add(articulo);
}
else
{
categoria.Articulos.Add(articulo);
categorias.Add(categoria);
}
}
}
con.Close();
}
catch (Exception ex)
{
/// Gestionar la excepción
}
return categorias;
}
public void Update(Categoria entity)
{
using SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmdCategoria = new SqlCommand("UPDATE Categoria SET Nombre = @Nombre WHERE Id = @CategoriaId", con);
cmdCategoria.Parameters.AddWithValue("@CategoriaId", entity.Id);
cmdCategoria.Parameters.AddWithValue("@Nombre", entity.Nombre);
try
{
cmdCategoria.ExecuteNonQuery();
}
catch (Exception ex)
{
/// Gestionar la excepción y deshacer inserciones para evitar fallos en la persistencia de los datos
}
con.Close();
}
public void Delete(int id)
{
try
{
using SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmd = new SqlCommand("DELETE FROM Categoria WHERE Id = @CategoriaId", con);
cmd.Parameters.AddWithValue("@CategoriaId", id);
cmd.Connection = con;
cmd.ExecuteNonQuery();
con.Close();
}
catch (Exception ex)
{
/// Gestionar la excepción
}
}