Relación muchos a muchos con el patrón repositorio en ADO.NET

¡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…

⚠ ¡Aviso navegantes! ⚠

Esta implementación del patrón repositorio asume ciertas nociones previas explicadas en los
siguientes artículos:


👉 Qué es el patrón repositorio y sus ventajas donde explicamos cómo funciona el patrón y por qué querrías usarlo.
👉 Implementación básica del patrón repositorio con una clase sin relaciones, donde se explica cómo funciona SqlConnection, SqlCommand y SqlReader.
👉 Relación uno a muchos con el patrón repositorio en ADO.NET, donde presentamos a SqlTransaction y la implementación de la relación uno a muchos entre Artículos y Autores.

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.

Diagrama UML de la relación muchos a muchos entre Categoria y Articulo.

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:

CategoriaIdNombreArticuloIdTituloContenidoFechaCreación
1Programación1Prueba tituloEsto es el contenido2022-04-05
3ASPNET1Prueba tituloEsto es el contenido2022-04-05
2C#2Otra prueba de tituloMás contenido interesante que te gustará.2022-04-05
3ASPNET2Otra prueba de tituloMá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.

Si habéis seguido estos 3 artículos y os estáis preguntando:

¿Otra vez hacer un repositorio? ¿No hay alguna forma de «sacar el factor común»? 🤔

La respuesta es sí, y podéis aprender a hacerlo en el artículo Cómo y cuándo implementar el Patrón repositorio genérico en C#.

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
    }
}

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

%d