I have this scenario:
public class Member
{
public int MemberID { get; set; }
public string FirstName { get; set; }
public string LastName { get
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.