EF Core - загрузка не всех дочерних объектов - PullRequest
2 голосов
/ 23 января 2020

У меня есть 3 объекта:

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 wi sh для возврата массива категорий с дочерним массивом BookCategory, который имеет один к одному с книгой.

Используя такой вызов;

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

У меня есть фиктивные данные примерно так:

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)

Однако ответ, который я получаю на этот запрос, дает мне только одну запись для категории 2, а не ожидается два.

[
    {
        "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);
            }
        }
    }

Три конфигурации следующие

    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 Ответ

5 голосов
/ 28 января 2020

Модель представляет стандартное отношение «многие ко многим» через явный объект объединения.

Проблема заключается в быстром отображении ключа объекта объединения:

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

HasKey Метод (как большинство, если не все свободно распространяемые API) является , а не аддитивным Последний вызов выигрывает (заменяет предыдущий).

Эффект приведенного выше кода заключается в том, что EF Core рассматривает CategoryID уникальный уникальный ключ таблицы BookCategory, следовательно, загружает только 1 запись на CategoryID.

Конечно, идея состояла в том, чтобы определить стандартный составной первичный ключ объекта объединения. Это достигается (аналогично всем свободно распространяемым API, допускающим несколько свойств) с помощью анонимного типа (показан в разделе с составным ключом примера EF Core Ключи topi c) документации.

Применение его чтобы ваш сценарий заменял 2 строки на

builder.HasKey(x => new { x.BookID, x.CategoryID });
...