Reputation: 83
I have a project with ASP.NET Core 3.1 using Visual Studio 2019 (64 bit) and SQL Server 2019. When I run the backend it generates an error when I use Entity Framework's .Include
to load data from a related table. It's strange that the same code worked with no problem using ASP.NET Core 2.1 in Visual Studio 2017. This happens to me in every method where I use .Include
to load data from a related table.
The following image shows the error generated when I run the backend:
This is the method where I use the .Include
:
namespace Sistema.Web.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ArticulosController : ControllerBase
{
private readonly DbContextSistema _context;
public object Articulos { get; private set; }
public ArticulosController(DbContextSistema context)
{
_context = context;
}
[HttpGet("[action]")]
public async Task<IEnumerable<ArticuloViewModel>> Listar()
{
var articulo = await _context.Articulos.Include(a=>a.categoria).ToListAsync();
return articulo.Select(a => new ArticuloViewModel
{
idarticulo = a.idarticulo,
idcategoria = a.idcategoria,
categoria = a.categoria.nombre,
codigo = a.codigo,
nombre = a.nombre,
stock = a.stock,
precio_venta = a.precio_venta,
descripcion = a.descripcion,
condicion = a.condicion
});
}
private bool ArticuloExists(int id)
{
return _context.Articulos.Any(e => e.idarticulo == id);
}
}
}
This is the entity of the main table (Articulo):
namespace Sistema.Entidades.Almacen
{
public class Articulo
{
public int idarticulo { get; set; }
[Required]
public int idcategoria { get; set; }
public string codigo { get; set; }
[Required]
public string nombre { get; set; }
[Required]
public decimal precio_venta { get; set; }
[Required]
public int stock { get; set; }
[StringLength(256)]
public string descripcion { get; set; }
public bool condicion { get; set; }
public Categoria categoria { get; set; }
}
}
This is the entity of the related table (Categoria):
namespace Sistema.Entidades.Almacen
{
public class Categoria
{
public int idcategoria { get; set; }
[Required]
public string nombre { get; set; }
[StringLength(256)]
public string descripcion { get; set; }
public bool condicion { get; set; }
public ICollection<Articulo> articulos { get; set; }
}
}
This is the mapping class for Articulo
:
namespace Sistema.Datos.Mapping.Almacen
{
public class ArticuloMap : IEntityTypeConfiguration<Articulo>
{
public void Configure(EntityTypeBuilder<Articulo> builder)
{
builder.ToTable("articulo")
.HasKey(a => a.idarticulo);
}
}
}
This is the mapping class for Categoria
:
namespace Sistema.Datos.Mapping.Almacen
{
public class CategoriaMap : IEntityTypeConfiguration<Categoria>
{
public void Configure(EntityTypeBuilder<Categoria> builder)
{
builder.ToTable("categoria")
.HasKey(c => c.idcategoria);
builder.Property(c => c.nombre)
.HasMaxLength(50);
builder.Property(c => c.descripcion)
.HasMaxLength(256);
}
}
}
This is the DbContext
where I include my ArticuloMap
and CategoriaMap
:
namespace Sistema.Datos
{
public class DbContextSistema : DbContext
{
public DbSet<Categoria> Categorias { get; set; }
public DbSet<Articulo> Articulos { get; set; }
public DbSet<Rol> Roles { get; set; }
public DbSet<Usuario> Usuarios { get; set; }
public DbSet<Persona> Personas { get; set; }
public DbSet<Ingreso> Ingresos { get; set; }
public object Entry(object articulos)
{
throw new NotImplementedException();
}
public DbSet<DetalleIngreso> DetalleIngresos { get; set; }
public DbSet<Venta> Ventas { get; set; }
public DbSet<DetalleVenta> DetalleVentas { get; set; }
public DbContextSistema(DbContextOptions<DbContextSistema> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new CategoriaMap());
modelBuilder.ApplyConfiguration(new ArticuloMap());
modelBuilder.ApplyConfiguration(new RolMap());
modelBuilder.ApplyConfiguration(new UsuarioMap());
modelBuilder.ApplyConfiguration(new PersonaMap());
modelBuilder.ApplyConfiguration(new IngresoMap());
modelBuilder.ApplyConfiguration(new DetalleIngresoMap());
modelBuilder.ApplyConfiguration(new VentaMap());
modelBuilder.ApplyConfiguration(new DetalleVentaMap());
}
}
}
Here is the db table design for articulo
and categoria
and the db relationships as requested:
Has something changed in .NET Core 3.1? Is there something I need to change or adjust to fix this issue? Thank you very much in advance for your responses!
EDIT:
Categoria
.CategoriaMap
.I apologize in advance for making the post only with images instead of the source code. Thank you for your responses, I hope that the last Edit make things more clear for a solution to the problem.
Upvotes: 1
Views: 2930
Reputation: 74595
There are several ways to tell EF how to wire up a relationship. By default for your objects:
//principal
public class Categoria
{
public int idcategoria { get; set; }
...
public ICollection<Articulo> articulos { get; set; }
}
//dependent
public class Articulo
{
public int idarticulo { get; set; }
public int idcategoria { get; set; }
...
public Categoria categoria { get; set; }
}
EF will be able to see these are related, because an Articulo has a Categoria and a Categoria has many Articulo so it can infer how the 1:M relationship is structured. When guessing which column of the dependent links to the principal it tries:
Most critically it no longer (since 3.0) tries to find a pair of properties between the two classes that have identical names, which I presume was why it worked before
If your naming convention was typical English it'd have found it, but with those rules it won't find a Spanish style name so you'll have to be explicit, probably easiest by annotating either of the properties in the relationship (EF doesn't care which) - I see arguments for either way being acceptable:
So it looks like:
//principal
public class Categoria
{
public int idcategoria { get; set; }
...
[ForeignKey("idcategoria")] //it means Articulo.idcategoria is the relevant property that links to PK of this class
public ICollection<Articulo> articulos { get; set; }
}
OR
//dependent
public class Articulo
{
public int idarticulo { get; set; }
public int idcategoria { get; set; }
...
[ForeignKey("idcategoria")] //it means idcategoria proerty above is the relevant property that links to PK property of Categoria
public Categoria categoria { get; set; }
}
I guess just pick whichever you like more and be consistent. The examples in MSDN use dependent
Upvotes: 1