En EF Core, las relaciones muchos a muchos no están directamente soportadas y deben implementarse mediante dos relacioens uno a muchos. El mapeo de estas relaciones se basa en convenciones, donde el nombre del campo de clave foránea por defecto es (nombreDeLaEntidadReferenciada + nombreDeLaClavePrimaria) o simplemente el nombre de la clave primaria de la entidad referenciada.
public class Producto
{
[Key]
public int IdProducto { get; set; }
public string CodigoProducto { get; set; }
public string NombreProducto { get; set; }
public List<CategoriaProducto> CategoriaProductos { get; set; }
}
public class Categoria
{
[Key]
public int IdCategoria { get; set; }
public string CodigoCategoria { get; set; }
public string NombreCategoria { get; set; }
public List<CategoriaProducto> CategoriaProductos { get; set; }
}
Por ejemplo, si la antidad Producto tiene una relación con Categoria, la consulta SQL buscará un campo IdCategoria en la tabla Producto.
Para definir una relación muchos a muchos entre Producto y Categoria, se necesita una clase intermedia:
/// <summary>
/// EF CORE maneja las relaciones muchos a muchos como dos relaciones uno a muchos.
/// </summary>
public class CategoriaProducto
{
public int Id { get; set; }
public int IdCategoria { get; set; }
public Categoria Categoria { get; set; }
public int IdProducto { get; set; }
public Producto Producto { get; set; }
}
Se debe configurar el mapeo en el método OnModelCreating del contexto:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CategoriaProducto>().HasKey(cp => new { cp.IdCategoria, cp.IdProducto });
modelBuilder.Entity<CategoriaProducto>()
.HasOne(cp => cp.Categoria)
.WithMany(c => c.CategoriaProductos)
.HasForeignKey(cp => cp.IdCategoria);
modelBuilder.Entity<CategoriaProducto>()
.HasOne(cp => cp.Producto)
.WithMany(p => p.CategoriaProductos)
.HasForeignKey(cp => cp.IdProducto);
}
Con esta configuración, para obtener todos los productos de una categoría específica, se puede usar:
var productos = _context.CategoriaProductos
.Where(cp => cp.IdCategoria == 1)
.Include(cp => cp.Producto)
.Select(cp => cp.Producto)
.ToList();
O bien, para evitar consultas múltiples, se puede hacer lo siguiente:
var idsProductos = _context.CategoriaProductos
.Where(cp => cp.IdCategoria == 1)
.Select(cp => cp.IdProducto)
.ToList();
var productos = _context.Productos
.Where(p => idsProductos.Contains(p.IdProducto))
.ToList();
var resultado = new { total = productos.Count, filas = productos };
return Json(resultado);
Si la relación entre Categoria y Producto fuera uno a muchos, la estructura de las entidades sería:
public class Producto
{
[Key]
public int IdProducto { get; set; }
public string CodigoProducto { get; set; }
public string NombreProducto { get; set; }
public int IdCategoria { get; set; }
public Categoria Categoria { get; set; }
}
public class Categoria
{
[Key]
public int IdCategoria { get; set; }
public string CodigoCategoria { get; set; }
public string NombreCategoria { get; set; }
public List<Producto> Productos { get; set; }
}
Y el mapeo en OnModelCreating:
modelBuilder.Entity<Producto>()
.HasOne(p => p.Categoria)
.WithMany(c => c.Productos)
.HasForeignKey(p => p.IdCategoria);
Para obtener todos los productos de una categoría, se puede usar:
var categoria = _context.Categorias
.Where(c => c.IdCategoria == id)
.Include(c => c.Productos)
.FirstOrDefault();
var productos = categoria.Productos
.Select(p => new
{
p.IdProducto,
p.CodigoProducto,
p.NombreProducto,
p.NombreCompletoProducto,
p.IdCategoria,
CategoriaNombre = p.Categoria.NombreCategoria
})
.ToList();
var resultado = new { total = productos.Count, filas = productos };
return Json(resultado);
Esto evita el problema de bucles de referencia al sreializar a JSON.