Свободный API, многие ко многим в Entity Framework Core

Я искал stackoverflow, чтобы найти правильное решение для генерации отношения " многие ко многим" с использованием EF Core, Code first и Fluent API.

Простой сценарий будет:

public class Person
{
    public Person() {
        Clubs = new HashSet<Club>();
    }
    public int PersonId { get; set; }
    public virtual ICollection<Club> Clubs { get; set; }
}

public class Club
{
    public Club() {
        Persons = new HashSet<Person>();
    }
    public int ClubId { get; set; }
    public virtual ICollection<Person> Persons { get; set; }
}

Пожалуйста, поправьте меня, если я ошибаюсь, но я честно не мог найти вопрос, который содержит подробное объяснение того, как сделать это, используя описанные инструменты. Кто-нибудь может объяснить, как это делается?

Ответ 1

Это еще не возможно в EF Core без использования явного класса для соединения. См. здесь для примера того, как это сделать.

В Github есть открытый issue, требующий возможности сделать это без необходимости явного класса, но он еще не завершено.

Используя ваш сценарий, пример, который я связал, будет рекомендовать следующие классы сущностей:

public class Person
{
    public int PersonId { get; set; }
    public virtual ICollection<PersonClub> PersonClubs { get; set; }
}

public class Club
{
    public int ClubId { get; set; }
    public virtual ICollection<PersonClub> PersonClubs { get; set; }
}

public class PersonClub
{
    public int PersonId { get; set; }
    public Person Person { get; set; }
    public int ClubId { get; set; }
    public Club Club { get; set; }
}

Затем для установки будет использоваться OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<PersonClub>()
        .HasKey(pc => new { pc.PersonId, pc.ClubId });

    modelBuilder.Entity<PersonClub>()
        .HasOne(pc => pc.Person)
        .WithMany(p => p.PersonClubs)
        .HasForeignKey(pc => pc.PersonId);

    modelBuilder.Entity<PersonClub>()
        .HasOne(pc => pc.Club)
        .WithMany(c => c.PersonClubs)
        .HasForeignKey(pc => pc.ClubId);
}

Обязательно перейдите к открытому вопросу, который я связал, и сообщите о своем разочаровании, если вы чувствуете необходимость.

EDIT: открытая проблема предполагает использование простой Select для навигации по этой несколько громоздкой иерархии. Чтобы получить из PersonId в коллекцию Club s, вы можете использовать SelectMany. например:.

var clubs = dbContext.People
    .Where(p => p.PersonId == id)
    .SelectMany(p => p.PersonClubs);
    .Select(pc => pc.Club);

Я не могу ручаться, действительно ли это "лучшая практика", но она, безусловно, должна сделать трюк, и я считаю, что это справедливо сказать, что это не слишком уродливо.

Ответ 2

Правильная "настройка" для этого:

public class Person
{
    public int PersonId { get; set; }
    public virtual ICollection<PersonClub> PersonClubs { get; set; }
}

public class Club
{
    public int ClubId { get; set; }
    public virtual ICollection<PersonClub> PersonClubs { get; set; }
}

public class PersonClub
{
    public int PersonId { get; set; }
    public Person Person { get; set; }
    public int ClubId { get; set; }
    public Club Club { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<PersonClub>()
        .HasKey(pc => new { pc.PersonId, pc.ClubId });
}

Итак, этот блок для настройки "таблицы клеев" имеет значение не, как в примере @Kirk:

modelBuilder.Entity<PersonClub>()
    .HasOne(pc => pc.Person)
    .WithMany(p => p.PersonClubs)
    .HasForeignKey(pc => pc.PersonId);

modelBuilder.Entity<PersonClub>()
    .HasOne(pc => pc.Club)
    .WithMany(c => c.PersonClubs)
    .HasForeignKey(pc => pc.ClubId);

Ответ 3

Таким образом, каждый Person имеет ноль или более Clubs, и каждый Club имеет ноль или более Persons. Как вы правильно сказали, это правильное отношение "многие ко многим" .

Вероятно, вы знаете, что реляционной базе данных нужна дополнительная таблица для реализации этого отношения "многие ко многим" . Самое приятное в инфраструктуре сущности - это то, что он распознает эти отношения и создает для вас эту дополнительную таблицу.

На первый взгляд кажется, что эта дополнительная таблица не является dbSet в вашем DbContext: "Как выполнить соединение с этой дополнительной таблицей, если у меня нет dbSet для этого?".

К счастью, вам не нужно упоминать эту дополнительную таблицу в ваших запросах.

Если вам нужен запрос типа "Дайте мне все" Клубы ", которые... от каждого" Человека ", который..." не думает вступать. Вместо этого используйте ICollections!

Получите всех "John Doe" со всеми загородными клубами, в которых они участвуют:

var result = myDbContext.Persons
    .Where(person => person.Name == "John Doe")
    .Select(person => new
    {
        PersonId = person.Id,
        PersonName = person.Name,
        AttendedCountryClubs = person.Clubs
            .Where(club => club.Type = ClubType.CountryClub),
    };

Entity framework будет распознавать, что требуется соединение с таблицей "много-ко-многим" и будет выполнять это соединение без упоминания этой дополнительной таблицы.

В противоположном направлении: получите все загородные клубы с персонажами "John Doe":

var result = myDbContext.Clubs
    .Where(club => club.Type = ClubType.CountryClub)
    .Select(club => new
    {
         ClubId = club.Id,
         ClubName = club.Name,
         AnonymousMembers = club.Persons
             .Where(person => person.Name == "John Doe"),
    }

Я испытал, что как только я начал думать о полученных коллекциях, которые мне нужны, а не о соединениях, которые мне нужны для получения этих коллекций, я обнаружил, что я почти не использую объединения. Это относится к отношениям "один ко многим", а также отношениям "многие ко многим" . Структура Entity будет внутренне использовать соответствующие объединения.