question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Eager loading fails to include collections for the first item in the source collection

See original GitHub issue

Here 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

  1. 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; }
    }
    
  2. 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");
        }
    }
    
  3. 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; }
    }
    
  4. 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();
        }
    }
    
  5. Add the connection string in appsettings.json file

    {
        "ConnectionStrings": {
            "AppDbConnection": "Data Source=.\\SQLEXPRESS;Initial Catalog=[YOUR_DATABASE_NAME];Integrated Security=True;MultipleActiveResultSets=False;"
        }
    }
    
  6. 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
    
  7. 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:closed
  • Created 5 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
davidliang2008commented, May 3, 2018

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.

1reaction
davidliang2008commented, May 1, 2018

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

Read more comments on GitHub >

github_iconTop Results From Across the Web

Eager loading fails to include collections for the first item in ...
For the first product in the list, I also notice that its product category and even the product type of its product category...
Read more >
Eager Loading of Related Data - EF Core
Eager loading a collection navigation in a single query may cause performance issues. For more information, see Single vs. split queries.
Read more >
Pitfalls with eager loading of collections in EF Core - Haacked
Eager loading of collections can come with pitfalls when it's not clear if the collection has been loaded or not.
Read more >
EF Eager loading. How to retrieve just first element of ...
I'm trying to eager load just first record of the related collection by this query: public async Task<IEnumerable<SparePart>> GetFront() ...
Read more >
Collections and Eager-Loading - What's New in Craft CMS 4
The first place you'll want to apply collection support is anywhere you're using eager loading. The result will be one block of code...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found