329 votes

Créer du code tout d’abord, plusieurs à plusieurs, avec des champs supplémentaires dans la table d’association

J’ai ce scénario :

Comment puis-je configurer ma liaison avec API fluent ? Ou y a-t-il une meilleure façon de créer la table d’association ?

Merci.

571voto

Slauma Points 76561

Il n'est pas possible de créer plusieurs-à-plusieurs relation avec une mesure de la table de jointure. Dans un plusieurs-à-plusieurs relation EF gère la table de jointure interne et caché. C'est un tableau sans une classe d'entités dans votre modèle. De travailler avec une table de jointure avec des propriétés supplémentaires, vous aurez à créer, en réalité, deux un-à-plusieurs relations. Il pourrait ressembler à ceci:

public class Member
{
    public int MemberID { get; set; }

    public string FirstName { get; set; }
    public string LastName { get; set; }

    public virtual ICollection<MemberComment> MemberComments { get; set; }
}

public class Comment
{
    public int CommentID { get; set; }
    public string Message { get; set; }

    public virtual ICollection<MemberComment> MemberComments { get; set; }
}

public class MemberComment
{
    [Key, Column(Order = 0)]
    public int MemberID { get; set; }
    [Key, Column(Order = 1)]
    public int CommentID { get; set; }

    public virtual Member Member { get; set; }
    public virtual Comment Comment { get; set; }

    public int Something { get; set; }
    public string SomethingElse { get; set; }
}

Maintenant, si vous voulez trouver tous les commentaires des membres avec LastName = "Smith" par exemple, vous pouvez écrire une requête comme ceci:

var commentsOfMembers = context.Members
    .Where(m => m.LastName == "Smith")
    .SelectMany(m => m.MemberComments.Select(mc => mc.Comment))
    .ToList();

...ou...

var commentsOfMembers = context.MemberComments
    .Where(mc => mc.Member.LastName == "Smith")
    .Select(mc => mc.Comment)
    .ToList();

Ou de créer une liste de membres avec le nom "Smith" (on suppose qu'il n'y a plus d'un) ainsi que leurs commentaires, vous pouvez utiliser une projection:

var membersWithComments = context.Members
    .Where(m => m.LastName == "Smith")
    .Select(m => new
    {
        Member = m,
        Comments = m.MemberComments.Select(mc => mc.Comment)
    })
    .ToList();

Si vous voulez trouver tous les commentaires d'un membre en MemberId = 1:

var commentsOfMember = context.MemberComments
    .Where(mc => mc.MemberId == 1)
    .Select(mc => mc.Comment)
    .ToList();

Maintenant, vous pouvez également filtrer par les propriétés de votre table de jointure (ce qui ne serait pas possible dans un plusieurs-à-plusieurs), par exemple: Filtre tous les commentaires de membre 1 qui ont un 99 dans des biens, Something:

var filteredCommentsOfMember = context.MemberComments
    .Where(mc => mc.MemberId == 1 && mc.Something == 99)
    .Select(mc => mc.Comment)
    .ToList();

En raison de lazy loading, les choses peuvent devenir plus facile. Si vous avez un chargé Member , vous devriez être en mesure d'obtenir les commentaires sans une requête explicite:

var commentsOfMember = member.MemberComments.Select(mc => mc.Comment);

Je suppose que le chargement paresseux va chercher les commentaires automatiquement derrière les coulisses.

Modifier

Juste pour le plaisir quelques exemples plus comment faire pour ajouter des entités et des relations et de la façon de les supprimer dans ce modèle:

1) Créez un membre et de deux commentaires de ce membre:

var member1 = new Member { FirstName = "Pete" };
var comment1 = new Comment { Message = "Good morning!" };
var comment2 = new Comment { Message = "Good evening!" };
var memberComment1 = new MemberComment { Member = member1, Comment = comment1,
                                         Something = 101 };
var memberComment2 = new MemberComment { Member = member1, Comment = comment2,
                                         Something = 102 };

context.MemberComments.Add(memberComment1); // will also add member1 and comment1
context.MemberComments.Add(memberComment2); // will also add comment2

context.SaveChanges();

2) Ajouter un troisième commentaire de member1:

var member1 = context.Members.Where(m => m.FirstName == "Pete")
    .SingleOrDefault();
if (member1 != null)
{
    var comment3 = new Comment { Message = "Good night!" };
    var memberComment3 = new MemberComment { Member = member1,
                                             Comment = comment3,
                                             Something = 103 };

    context.MemberComments.Add(memberComment3); // will also add comment3
    context.SaveChanges();
}

3) Créer un nouveau membre et de le rapporter à l'existant comment2:

var comment2 = context.Comments.Where(c => c.Message == "Good evening!")
    .SingleOrDefault();
if (comment2 != null)
{
    var member2 = new Member { FirstName = "Paul" };
    var memberComment4 = new MemberComment { Member = member2,
                                             Comment = comment2,
                                             Something = 201 };

    context.MemberComments.Add(memberComment4);
    context.SaveChanges();
}

4) Créer des relations entre les member2 et comment3:

var member2 = context.Members.Where(m => m.FirstName == "Paul")
    .SingleOrDefault();
var comment3 = context.Comments.Where(c => c.Message == "Good night!")
    .SingleOrDefault();
if (member2 != null && comment3 != null)
{
    var memberComment5 = new MemberComment { Member = member2,
                                             Comment = comment3,
                                             Something = 202 };

    context.MemberComments.Add(memberComment5);
    context.SaveChanges();
}

5) Supprimer cette relation à nouveau:

var memberComment5 = context.MemberComments
    .Where(mc => mc.Member.FirstName == "Paul"
        && mc.Comment.Message == "Good night!")
    .SingleOrDefault();
if (memberComment5 != null)
{
    context.MemberComments.Remove(memberComment5);
    context.SaveChanges();
}

6) Supprimer member1 et tous ses relationsships pour les commentaires:

var member1 = context.Members.Where(m => m.FirstName == "Pete")
    .SingleOrDefault();
if (member1 != null)
{
    context.Members.Remove(member1);
    context.SaveChanges();
}

Cette opération supprime les relations en MemberComments trop parce que l'un-à-plusieurs relations entre Member et MemberComments et entre Comment et MemberComments sont le programme d'installation avec suppression en cascade par la convention. Et c'est le cas, parce qu' MemberId et CommentId en MemberComment sont détectés comme des propriétés de clés étrangères pour l' Member et Comment propriétés de navigation et depuis le FK propriétés sont de type non nullable int la relation est nécessaire, ce qui finalement provoque la cascade-supprimer-le programme d'installation. A de sens que dans ce modèle, je pense.

106voto

Esteban Points 1268

Excellente réponse par Slauma.

Je vais juste poster le code pour faire cela à l'aide de l'api fluent de la cartographie.

public class User {
    public int UserID { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }

    public ICollection<UserEmail> UserEmails { get; set; }
}

public class Email {
    public int EmailID { get; set; }
    public string Address { get; set; }

    public ICollection<UserEmail> UserEmails { get; set; }
}

public class UserEmail {
    public int UserID { get; set; }
    public int EmailID { get; set; }
    public bool IsPrimary { get; set; }
}

Sur votre DbContext de la classe dérivée, vous pourriez faire ceci:

public class MyContext : DbContext {
    protected override void OnModelCreating(DbModelBuilder builder) {
        // Primary keys
        builder.Entity<User>().HasKey(q => q.UserID);
        builder.Entity<Email>().HasKey(q => q.EmailID);
        builder.Entity<UserEmail>().HasKey(q => 
            new { 
                q.UserID, q.EmailID
            });

        // Relationships
        builder.Entity<UserEmail>()
            .HasRequired(t => t.Email)
            .WithMany(t => t.UserEmails)
            .HasForeignKey(t => t.EmailID)

        builder.Entity<UserEmail>()
            .HasRequired(t => t.User)
            .WithMany(t => t.UserEmails)
            .HasForeignKey(t => t.UserID)
    }
}

Il a le même effet que la accepté de répondre, avec une approche différente, qui n'est pas mieux, ni pire.

EDIT: J'ai changé CreatedDate de bool DateTime.

EDIT 2: En raison du manque de temps que j'ai mis un exemple à partir d'une application que je suis en train de travailler pour être sûr que cela fonctionne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X