web-dev-qa-db-fra.com

API Fluent, plusieurs à plusieurs dans Entity Framework Core

J'ai recherché dans stackoverflow une solution appropriée pour générer une relation plusieurs-à-plusieurs, en utilisant EF Core, Code first et API Fluent.

Un scénario simple serait:

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

S'il vous plaît, corrigez-moi si je me trompe, mais honnêtement, je ne pourrais pas trouver une question contenant une explication détaillée sur la façon de procéder en utilisant les outils décrits. Quelqu'un peut-il expliquer comment cela est fait?

28
Anonymous

Cela n'est pas encore possible dans EF Core sans utiliser une classe explicite pour la jointure. Voir ici pour un exemple montrant comment faire cela.

Il y a un open issue sur Github qui demande la possibilité de le faire sans avoir besoin d'une classe explicite, mais cela n'a pas encore été complété.

En utilisant votre scénario, l'exemple que j'ai lié recommanderait les classes d'entités suivantes:

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

Le OnModelCreating suivant serait alors utilisé pour la configuration:

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

Assurez-vous d’aller à la question ouverte que j’ai liée et exprimez votre frustration si vous en ressentez le besoin.

EDIT: La question ouverte suggère d'utiliser un simple Select pour naviguer dans cette hiérarchie quelque peu lourde. Pour passer d'un PersonId à une collection de Clubs, vous pouvez utiliser SelectMany . par exemple.:

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

Je ne peux pas garantir qu'il s'agisse vraiment d'une "meilleure pratique", mais cela devrait certainement faire l'affaire et je pense qu'il est juste de dire que ce n'est pas trop laid.

30
Kirk Larkin

La "configuration" correcte pour cela est:

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

Donc, ce bloc pour configurer la "table de collage" est pas nécessaire comme dans l'exemple de @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);
16
paul van bladel

Ainsi, chaque Person a zéro ou plus Clubs et chaque Club a zéro ou plus Persons. Comme vous l'avez bien dit, il s'agit d'une relation plusieurs à plusieurs appropriée.

Vous savez probablement qu'une base de données relationnelle a besoin d'une table supplémentaire pour implémenter cette relation plusieurs à plusieurs. La bonne chose à propos du framework entity, c'est qu'il reconnaît cette relation et crée cette table supplémentaire pour vous.

À première vue, il semble problématique que cette table supplémentaire ne soit pas un dbSet dans votre DbContext: "Comment effectuer une jointure avec cette table supplémentaire si je n'ai pas de DbSet pour ça?".

Heureusement, vous n'avez pas besoin de mentionner cette table supplémentaire dans vos requêtes.

Si vous avez besoin d'une requête du type "Donnez-moi tous les" clubs "qui ... de chaque" personne "qui ..." ne pensez pas aux jointures. Utilisez plutôt les ICollections!

Obtenez toutes les personnes "John Doe" dans tous les clubs de pays auxquels elles participent:

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 reconnaîtra qu'une jointure avec la table supplémentaire plusieurs-à-plusieurs est nécessaire et effectuera cette jointure sans que vous ne mentionniez cette table supplémentaire.

Inversement: obtenez tous les clubs de pays avec leur "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"),
    }

J'ai constaté qu'une fois que j'ai commencé à penser aux collections résultantes que je voulais, au lieu des jointures dont j'avais besoin pour obtenir ces collections, j'ai constaté que j'utilisais à peine les jointures. C'est le cas des relations un à plusieurs ainsi que des relations plusieurs à plusieurs. Entity Framework utilisera en interne les jointures appropriées.

0
Harald Coppoolse