Eager loading fails to include collections for the first item in the source collection
See original GitHub issueHere is my fun hierarchy:
- 1 product type -> many product categories
- 1 product category -> many products
- 1 product -> many product images
- 1 product -> many product specifications
When I try to eager load the related data for each product, it couldn’t load the product images and specifications for the first product in the list. For all other products all related data is loaded fine.
For the first product in the list, I also notice that its product category and even the product type of its product category are loaded fine. Just the collections are not loaded correctly.
Steps to reproduce
-
Create entities
public class ProductTypeEntity { public Guid Id { get; set; } public string Name { get; set; } public List<ProductCategoryEntity> ProductCategories { get; set; } } public class ProductCategoryEntity { public Guid Id { get; set; } public string Name { get; set; } public Guid ProductTypeId { get; set; } public ProductTypeEntity ProductType { get; set; } public List<ProductEntity> Products { get; set; } } public class ProductEntity { public Guid Id { get; set; } public string Name { get; set; } public Guid ProductCategoryId { get; set; } public ProductCategoryEntity ProductCategory { get; set; } public List<ProductImageEntity> ProductImages { get; set; } public List<ProductSpecificationEntity> ProductSpecifications { get; set; } } public class ProductImageEntity { public Guid Id { get; set; } public string Url { get; set; } public Guid ProductId { get; set; } public ProductEntity Product { get; set; } } public class ProductSpecificationEntity { public Guid Id { get; set; } public string Value { get; set; } public double Price { get; set; } public double? DiscountedPrice { get; set; } public Guid ProductId { get; set; } public ProductEntity Product { get; set; } }
-
Create configurations
public class ProductTypeConfiguration : IEntityTypeConfiguration<ProductTypeEntity> { public void Configure(EntityTypeBuilder<ProductTypeEntity> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.Name).IsRequired(); builder.HasIndex(x => x.Name).IsUnique(); builder.ToTable("ProductType"); } } public class ProductCategoryConfiguration : IEntityTypeConfiguration<ProductCategoryEntity> { public void Configure(EntityTypeBuilder<ProductCategoryEntity> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.ProductTypeId).IsRequired(); builder.Property(x => x.Name).IsRequired(); builder.HasIndex(x => x.Name).IsUnique(); builder.HasOne(x => x.ProductType) .WithMany(t => t.ProductCategories) .HasForeignKey(x => x.ProductTypeId); builder.ToTable("ProductCategory"); } } public class ProductConfiguration : IEntityTypeConfiguration<ProductEntity> { public void Configure(EntityTypeBuilder<ProductEntity> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.ProductCategoryId).IsRequired(); builder.Property(x => x.Name).IsRequired(); builder.HasIndex(x => x.Name).IsUnique(); builder.HasOne(x => x.ProductCategory) .WithMany(c => c.Products) .HasForeignKey(x => x.ProductCategoryId); builder.ToTable("Product"); } } public class ProductImageConfiguration : IEntityTypeConfiguration<ProductImageEntity> { public void Configure(EntityTypeBuilder<ProductImageEntity> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.Url).IsRequired(); builder.HasOne(x => x.Product) .WithMany(p => p.ProductImages) .HasForeignKey(x => x.ProductId); builder.ToTable("ProductImage"); } } public class ProductSpecificationConfiguration : IEntityTypeConfiguration<ProductSpecificationEntity> { public void Configure(EntityTypeBuilder<ProductSpecificationEntity> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.ProductId).IsRequired(); builder.Property(x => x.Value).IsRequired(); builder.HasIndex(x => new { x.ProductId, x.Value }).IsUnique(); builder.Property(x => x.Price).IsRequired(); builder.HasOne(x => x.Product) .WithMany(p => p.ProductSpecifications) .HasForeignKey(x => x.ProductId); builder.ToTable("ProductSpecification"); } }
-
Create the dbContext
public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfiguration(new ProductTypeConfiguration()); builder.ApplyConfiguration(new ProductCategoryConfiguration()); builder.ApplyConfiguration(new ProductConfiguration()); builder.ApplyConfiguration(new ProductImageConfiguration()); builder.ApplyConfiguration(new ProductSpecificationConfiguration()); } public DbSet<ProductTypeEntity> ProductTypes { get; set; } public DbSet<ProductCategoryEntity> ProductCategories { get; set; } public DbSet<ProductEntity> Products { get; set; } public DbSet<ProductImageEntity> ProductImages { get; set; } public DbSet<ProductSpecificationEntity> ProductSpecifications { get; set; } }
-
Create a MVC application (or console if you like) and configure the dbContext in Startup
public class Startup { public IConfiguration Configuration { get; private set; } public Startup(IConfiguration configuration) { this.Configuration = configuration; } public void ConfigureServices(IServiceCollection services) { string dbConnectionString = this.Configuration.GetConnectionString("AppDbConnection"); string assemblyName = typeof(AppDbContext).Namespace; services.AddDbContext<AppDbContext>(options => options .UseSqlServer(dbConnectionString, optionsBuilder => optionsBuilder.MigrationsAssembly(assemblyName) ) ); services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); } }
-
Add the connection string in appsettings.json file
{ "ConnectionStrings": { "AppDbConnection": "Data Source=.\\SQLEXPRESS;Initial Catalog=[YOUR_DATABASE_NAME];Integrated Security=True;MultipleActiveResultSets=False;" } }
-
Run Add-Migration and Update-Database to generate tables, and then add some test data. Make sure you have products that have many images and specifications.
Add-Migration InitTables -Context AppDbContext Update-Database -Context AppDbContext
-
Create a HomeController and query products
public class HomeController : Controller { private readonly AppDbContext _dbContext; public HomeController(AppDbContext dbContext) { _dbContext = dbContext; } public IActionResult Index() { var products = _dbContext.Products .AsNoTracking() // This makes no difference on the issue .Include(x => x.ProductCategory) .ThenInclude(x => x.ProductType) .Include(x => x.ProductImages) .Include(x => x.ProductSpecifications) .ToList(); foreach (var product in products) { /* * You can put a breakpoint here to examine each product * For the first product: * - ProductCategory is loaded OK * - ProductType is loaded OK * - ProductImages collection NOT LOADED! * - ProductSpecifications collection NOT LOADED! * For other products: * - Everything is loaded OK! */ } return View(); } }
Further technical details
EF Core version: 2.0.2 Database Provider: Microsoft.EntityFrameworkCore.SqlServer Operating system: Windows 10 Version 1709 Build 16299.371 IDE: Visual Studio Community 2017 Version 15.6.7
Issue Analytics
- State:
- Created 5 years ago
- Comments:7 (2 by maintainers)
It’s still happening actually with a brand new staging/testing database @smitpatel . And I think the issue not necessary associates the first item off the list. It has to do with
lastestProductIds.Contains(x.Id)
filter.I will create a Github repro and see if I can reproduce the problem. By then I will open up another issue.
I guess I am closing this issue as I cannot reproduce it. I published my app to a brand new staging database and everything worked fine…
Thank you guys a lot for the time, esp @smitpatel !!!