Fluent API, wiele do wielu w jądrze Entity Framework

Przeszukałem stackoverflow w poszukiwaniu odpowiedniego rozwiązania w zakresie generowania relacji many-to-many , używając EF Core, Code first i Fluent API.

Prosty scenariusz to:

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

Proszę mnie poprawić, jeśli się mylę, ale szczerze nie mogłem znaleźć pytania, które zawiera szczegółowe wyjaśnienie, jak to zrobić za pomocą opisanych narzędzi. Czy ktoś może wyjaśnić jak to się robi?

Author: Anonymous, 2017-09-12

3 answers

EF Core 5.0 RC1 +

Od wersji EF Core 5.0 RC1 można to zrobić bez jawnej tabeli join. EF Core jest w stanie skonfigurować mapowanie dla relacji wiele do wielu pokazanych w pytaniu bez konieczności tworzenia typu PersonClub.

Aby uzyskać więcej informacji, zobacz Co nowego w EF Core 5.0, RC1, Many-to-many w oficjalnych dokumentach.

Poprzednie Wersje

Nie jest to jeszcze możliwe w EF Core bez użycia jawnego zajęcia dla join. Zobacz tutaj dla przykładu, jak to zrobić.

Jest otwarty problem na Githubie z pytaniem o możliwość zrobienia tego bez potrzeby użycia jawnej klasy, ale nie został jeszcze ukończony.

Używając Twojego scenariusza, przykład, który podlinkowałem, poleciłby następujące klasy encji:

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

Następująca OnModelCreating zostanie użyta do Ustawienia:

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

Pamiętaj, aby przejść do otwartego numeru, który połączyłem i wyrazić swoją frustrację, jeśli Poczuj potrzebę.

EDIT: otwarty problem sugeruje użycie prostej Select do poruszania się po tej nieco uciążliwej hierarchii. Aby przejść z PersonId do zbioru Clubs, można użyć SelectMany. np.:

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

Nie mogę ręczyć za to, czy jest to naprawdę "najlepsza praktyka", ale z pewnością powinno zadziałać i myślę, że można powiedzieć, że nie jest zbyt brzydka.

 51
Author: Kirk Larkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-29 08:28:35

Prawidłowa "konfiguracja" to:

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

Więc ten blok do konfiguracji "glue-table" jest Nie konieczny jak w przykładzie @ 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);
 25
Author: paul van bladel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-13 09:42:55

Więc każdy Person ma zero lub więcej Clubs, a każdy Club mA zero lub więcej Persons. Jak pan słusznie stwierdził, Jest to właściwa relacja wielu do wielu.

Prawdopodobnie wiesz, że relacyjna baza danych potrzebuje dodatkowej tabeli, aby zaimplementować tę relację wiele do wielu. Fajną rzeczą w entity framework jest to, że rozpoznaje tę relację i tworzy dla Ciebie dodatkową tabelę.

Na pierwszy rzut oka wydaje się problemem, że ta dodatkowa tabela nie jest dbSet w Twoim DbContext: "Jak wykonać połączenie z tym dodatkowym stołem, jeśli nie mam DbSet do niego?".

Na szczęście nie musisz wspominać o tej dodatkowej tabeli w zapytaniach.

Jeśli potrzebujesz zapytania typu "Daj mi wszystkie 'kluby' to ... od każdej "osoby", która ..."nie myśl w myślach. Zamiast tego użyj ICollections!

Get all "John Doe" persons with all Country clubs they attending:

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 rozpozna, że potrzebne jest połączenie z dodatkową tabelą many-to-many, oraz wykona to połączenie, bez wspominania o tej dodatkowej tabeli.

W drugą stronę: Zdobądź wszystkie kluby z kraju z ich" John Doe " osoby:

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"),
    }

Doświadczyłem tego, że kiedy zacząłem myśleć w wynikowych kolekcjach, że chcę zamiast joinów, które potrzebowałem, aby uzyskać te kolekcje, stwierdziłem, że prawie nie używam joinów. Dotyczy to zarówno relacji jeden do wielu, jak i relacji wielu do wielu. Entity framework będzie wewnętrznie używać odpowiednich połączeń.

 2
Author: Harald Coppoolse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-13 09:30:15