EF Core - Not loading all child entities

.net-core c# entity-framework-core

Question

I have 3 entities:

public partial class Category
{
    public int CategoryID { get; set; }
    public string? CategoryName { get; set; }

    public ICollection<BookCategory> BookCategory { get; set; }
}

public partial class BookCategory
{
    public int CategoryID { get; set; }
    public int BookID { get; set; }

    public Category Category { get; set; }
    public Book Book { get; set; }
}

public partial class Book
{
    public int BookID { get; set; }
    public string? Title { get; set; }
}

I wish to return an array of Category's, with a child array of BookCategory which has a one to one with a Book.

Using a call like this;

public async Task<List<Category>> test()
{
    var query = dbContext.Category
                .Include(p => p.BookCategory) 
                .ThenInclude(pc => pc.Book)
                .OrderBy(p => p.CategoryID) as IQueryable<Category>;

    var data = await query.ToListAsync();

    return data;
}

I have dummy data like so:

insert into kiosk.Category (CategoryID, CategoryName) values (1, 'Horror')
insert into kiosk.Category (CategoryID, CategoryName) values (2, 'Fantasy')

insert into kiosk.Book (BookID, Title) values (1, 'Space shooty')
insert into kiosk.Book (BookID, Title) values (2, 'Elf shooty')

insert into kiosk.BookCategory (BookID, CategoryID) values (1, 2)
insert into kiosk.BookCategory (BookID, CategoryID) values (2, 2)

However the response I receive for this request only gives me one record for category 2 rather the expected two.

[
    {
        "categoryID": 1,
        "categoryName": "Horror",
        "bookCategory": []
    },
    {
        "categoryID": 2,
        "categoryName": "Fantasy",
        "bookCategory": [
            {
                "categoryID": 2,
                "bookID": 1,
                "book": {
                    "bookID": 1,
                    "title": "Space shooty"
                }
            }
        ]
    }
]

DBContext:

    public class BooksDbContext : DbContext
    {

        public DbSet<Book> book { get; set; } 
        public DbSet<Category> Category { get; set; } 
        public DbSet<BookCategory> BookCategory { get; set; } 

        public BooksDbContext()
        { }

        public BooksDbContext(DbContextOptions options) : base(options)
        { }

        public BooksDbContext(string connectionString)
        {
            this.connectionString = connectionString;
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.HasDefaultSchema("dbo");

            modelBuilder.ApplyConfiguration(new BookCategoryConfiguration());
            modelBuilder.ApplyConfiguration(new CategoryTestConfiguration());
            modelBuilder.ApplyConfiguration(new BookConfiguration());

        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);
            if (!string.IsNullOrWhiteSpace(connectionString))
            {
                optionsBuilder.UseSqlServer(connectionString);
            }
        }
    }

The Three Configurations are as follow

    public class BookCategoryConfiguration : IEntityTypeConfiguration<BookCategory>
    {
        public void Configure(EntityTypeBuilder<BookCategory> builder)
        {
            builder.ToTable("BookCategory");
            builder.HasKey(x => x.BookID);
            builder.HasKey(x => x.CategoryID);         

            builder.HasOne(a => a.Category).WithMany(b => b.BookCategory).HasForeignKey(c => c.CategoryID); // FK_POSCategoryProduct_POSCategory
        }
    }


    public class CategoryTestConfiguration : IEntityTypeConfiguration<Category>
    {
        public void Configure(EntityTypeBuilder<Category> builder)
        {
            builder.ToTable("Category");
            builder.HasKey(x => x.CategoryID);

            builder.Property(x => x.CategoryID).HasColumnName(@"CategoryID").HasColumnType("int").IsRequired();
            builder.Property(x => x.CategoryName).HasColumnName(@"CategoryName").HasColumnType("nvarchar").HasMaxLength(50);          

        }
    }


    public class BookConfiguration : IEntityTypeConfiguration<Book>
    {
        public void Configure(EntityTypeBuilder<Book> builder)
        {
            builder.ToTable("Book");
            builder.HasKey(x => x.BookID);

            builder.Property(x => x.BookID).HasColumnName(@"BookID").HasColumnType("int").IsRequired();
            builder.Property(x => x.Title).HasColumnName(@"Title").HasColumnType("nvarchar").HasMaxLength(50);
        }
    }
1
2
1/28/2020 3:32:37 AM

Accepted Answer

The model represents a standard many-to-many relationship via explicit join entity.

The problem is the fluent mapping of the join entity key:

builder.HasKey(x => x.BookID);
builder.HasKey(x => x.CategoryID);

The HasKey method (as most if not all of the fluent APIs) is not additive. The later call wins (replaces the previous).

The effect of the above code is that EF Core considers the CategoryID to be an unique key of BookCategory table, hence loads only 1 record per CategoryID.

Of course the idea was to define a standard composite primary key of the join entity. This is achieved (similar to all fluent APIs allowing multiple properties) by using anonymous type (shown in composite key section example of the EF Core Keys documentation topic).

Applying it to your scenario would be replacing the above 2 lines with

builder.HasKey(x => new { x.BookID, x.CategoryID });
5
1/28/2020 8:56:45 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow