Create code first, many to many, with additional fields in association table

前端 未结 6 1655
星月不相逢
星月不相逢 2020-11-21 10:21

I have this scenario:

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

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


        
6条回答
  •  渐次进展
    2020-11-21 10:43

    I want to propose a solution where both flavors of a many-to-many configuration can be achieved.

    The "catch" is we need to create a view that targets the Join Table, since EF validates that a schema's table may be mapped at most once per EntitySet.

    This answer adds to what's already been said in previous answers and doesn't override any of those approaches, it builds upon them.

    The model:

    public class Member
    {
        public int MemberID { get; set; }
    
        public string FirstName { get; set; }
        public string LastName { get; set; }
    
        public virtual ICollection Comments { get; set; }
        public virtual ICollection MemberComments { get; set; }
    }
    
    public class Comment
    {
        public int CommentID { get; set; }
        public string Message { get; set; }
    
        public virtual ICollection Members { get; set; }
        public virtual ICollection MemberComments { get; set; }
    }
    
    public class MemberCommentView
    {
        public int MemberID { get; set; }
        public int CommentID { get; set; }
        public int Something { get; set; }
        public string SomethingElse { get; set; }
    
        public virtual Member Member { get; set; }
        public virtual Comment Comment { get; set; }
    }
    

    The configuration:

    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.ModelConfiguration;
    
    public class MemberConfiguration : EntityTypeConfiguration
    {
        public MemberConfiguration()
        {
            HasKey(x => x.MemberID);
    
            Property(x => x.MemberID).HasColumnType("int").IsRequired();
            Property(x => x.FirstName).HasColumnType("varchar(512)");
            Property(x => x.LastName).HasColumnType("varchar(512)")
    
            // configure many-to-many through internal EF EntitySet
            HasMany(s => s.Comments)
                .WithMany(c => c.Members)
                .Map(cs =>
                {
                    cs.ToTable("MemberComment");
                    cs.MapLeftKey("MemberID");
                    cs.MapRightKey("CommentID");
                });
        }
    }
    
    public class CommentConfiguration : EntityTypeConfiguration
    {
        public CommentConfiguration()
        {
            HasKey(x => x.CommentID);
    
            Property(x => x.CommentID).HasColumnType("int").IsRequired();
            Property(x => x.Message).HasColumnType("varchar(max)");
        }
    }
    
    public class MemberCommentViewConfiguration : EntityTypeConfiguration
    {
        public MemberCommentViewConfiguration()
        {
            ToTable("MemberCommentView");
            HasKey(x => new { x.MemberID, x.CommentID });
    
            Property(x => x.MemberID).HasColumnType("int").IsRequired();
            Property(x => x.CommentID).HasColumnType("int").IsRequired();
            Property(x => x.Something).HasColumnType("int");
            Property(x => x.SomethingElse).HasColumnType("varchar(max)");
    
            // configure one-to-many targeting the Join Table view
            // making all of its properties available
            HasRequired(a => a.Member).WithMany(b => b.MemberComments);
            HasRequired(a => a.Comment).WithMany(b => b.MemberComments);
        }
    }
    

    The context:

    using System.Data.Entity;
    
    public class MyContext : DbContext
    {
        public DbSet Members { get; set; }
        public DbSet Comments { get; set; }
        public DbSet MemberComments { get; set; }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
    
            modelBuilder.Configurations.Add(new MemberConfiguration());
            modelBuilder.Configurations.Add(new CommentConfiguration());
            modelBuilder.Configurations.Add(new MemberCommentViewConfiguration());
    
            OnModelCreatingPartial(modelBuilder);
         }
    }
    

    From Saluma's (@Saluma) answer

    If you now want to find all comments of members with LastName = "Smith" for example you can write a query like this:

    This still works...

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

    ...but could now also be...

    var commentsOfMembers = context.Members
        .Where(m => m.LastName == "Smith")
        .SelectMany(m => m.Comments)
        .ToList();
    

    Or to create a list of members with name "Smith" (we assume there is more than one) along with their comments you can use a projection:

    This still works...

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

    ...but could now also be...

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

    If you want to remove a comment from a member

    var comment = ... // assume comment from member John Smith
    var member = ... // assume member John Smith
    
    member.Comments.Remove(comment);
    

    If you want to Include() a member's comments

    var member = context.Members
        .Where(m => m.FirstName == "John", m.LastName == "Smith")
        .Include(m => m.Comments);
    

    This all feels like syntactic sugar, however it does get you a few perks if you're willing to go through the additional configuration. Either way you seem to be able to get the best of both approaches.

提交回复
热议问题