简体   繁体   中英

EF Core - Not loading all child entities

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);
        }
    }

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 });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM