先创建代码,多对多,并在关联表中增加字段

我有这样一个场景:

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 class Comment
{
public int CommentID { get; set; }
public string Message { get; set; }


public virtual ICollection Members { get; set; }
}


public class MemberComment
{
public int MemberID { get; set; }
public int CommentID { get; set; }
public int Something { get; set; }
public string SomethingElse { get; set; }
}

如何配置与fluent API的关联?或者是否有更好的方法来创建关联表?

147327 次浏览

不可能使用自定义连接表创建多对多关系。在多对多关系中,EF在内部和隐藏地管理连接表。它是一个在模型中没有Entity类的表。要使用这样一个具有其他属性的连接表,实际上必须创建两个一对多的关系。它可能是这样的:

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

例如,如果你现在想找到LastName = "Smith"成员的所有注释,你可以写这样的查询:

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

... 还是……

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

或者创建一个名为“Smith”的成员列表(我们假设有不止一个)以及他们的评论,你可以使用投影:

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

如果你想找到MemberId = 1成员的所有注释:

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

现在你还可以通过连接表中的属性进行过滤(这在多对多关系中是不可能的),例如:过滤成员1中Something属性为99的所有注释:

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

因为惰性加载,事情可能会变得更容易。如果你有一个加载的Member,你应该能够在没有显式查询的情况下获得注释:

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

我猜延迟加载会在幕后自动获取注释。

编辑

只是为了好玩一些例子,更多的如何添加实体和关系,以及如何删除他们在这个模型:

1)创建一个成员和该成员的两个注释:

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)加入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)创建新成员,并将其与现有的评论联系起来。

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)在现有的member2和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)重新删除此关系:

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)删除member1和它与注释的所有关系:

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

这也会删除MemberComments中的关系,因为MemberMemberComments之间以及CommentMemberComments之间的一对多关系是按照约定使用级联删除建立的。这是因为MemberComment中的MemberIdCommentId被检测为MemberComment导航属性的外键属性,而且由于FK属性属于非空类型Member0,因此需要这种关系,这最终导致了级联-删除-设置。我认为在这个模型中是有意义的。

我将使用流畅的API映射发布代码来做到这一点。

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

在你的DbContext派生类中,你可以这样做:

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

它与公认的答案具有相同的效果,但方法不同,没有更好也更差。

此答案提供的代码是正确的,但不完整,我已经测试过了。“用户邮箱”中缺少属性;类:

    public UserTest UserTest { get; set; }
public EmailTest EmailTest { get; set; }
如果有人感兴趣,我会发布我测试过的代码。 关于< / p >
using System.Data.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;


#region example2
public class UserTest
{
public int UserTestID { get; set; }
public string UserTestname { get; set; }
public string Password { get; set; }


public ICollection<UserTestEmailTest> UserTestEmailTests { get; set; }


public static void DoSomeTest(ApplicationDbContext context)
{


for (int i = 0; i < 5; i++)
{
var user = context.UserTest.Add(new UserTest() { UserTestname = "Test" + i });
var address = context.EmailTest.Add(new EmailTest() { Address = "address@" + i });
}
context.SaveChanges();


foreach (var user in context.UserTest.Include(t => t.UserTestEmailTests))
{
foreach (var address in context.EmailTest)
{
user.UserTestEmailTests.Add(new UserTestEmailTest() { UserTest = user, EmailTest = address, n1 = user.UserTestID, n2 = address.EmailTestID });
}
}
context.SaveChanges();
}
}


public class EmailTest
{
public int EmailTestID { get; set; }
public string Address { get; set; }


public ICollection<UserTestEmailTest> UserTestEmailTests { get; set; }
}


public class UserTestEmailTest
{
public int UserTestID { get; set; }
public UserTest UserTest { get; set; }
public int EmailTestID { get; set; }
public EmailTest EmailTest { get; set; }
public int n1 { get; set; }
public int n2 { get; set; }




//Call this code from ApplicationDbContext.ConfigureMapping
//and add this lines as well:
//public System.Data.Entity.DbSet<yournamespace.UserTest> UserTest { get; set; }
//public System.Data.Entity.DbSet<yournamespace.EmailTest> EmailTest { get; set; }
internal static void RelateFluent(System.Data.Entity.DbModelBuilder builder)
{
// Primary keys
builder.Entity<UserTest>().HasKey(q => q.UserTestID);
builder.Entity<EmailTest>().HasKey(q => q.EmailTestID);


builder.Entity<UserTestEmailTest>().HasKey(q =>
new
{
q.UserTestID,
q.EmailTestID
});


// Relationships
builder.Entity<UserTestEmailTest>()
.HasRequired(t => t.EmailTest)
.WithMany(t => t.UserTestEmailTests)
.HasForeignKey(t => t.EmailTestID);


builder.Entity<UserTestEmailTest>()
.HasRequired(t => t.UserTest)
.WithMany(t => t.UserTestEmailTests)
.HasForeignKey(t => t.UserTestID);
}
}
#endregion

TLDR;(与EF6/VS2012U5中的EF编辑器错误半相关)如果你从DB生成模型,你不能看到有属性的m:m表:删除两个相关的表->保存。edmx ->从数据库生成/添加->保存。

对于那些想知道如何在EF .edmx文件中显示属性列的多对多关系的人(因为它目前不会显示并被视为一组导航属性),并且您从数据库表(或者用MS行话来说是数据库优先)生成了这些类。

删除。edmx中的两个表(以OP为例,Member和Comment),并通过“从数据库生成模型”再次添加它们。(即不要试图让Visual Studio更新它们-删除,保存,添加,保存)

然后,它将根据这里的建议创建第三个表。

这与先添加纯多对多关系,然后在DB中设计属性的情况有关。

从这个帖子/谷歌上看,这个问题并没有立即得到解决。所以只是把它放在那里,因为这是谷歌上的链接#1,寻找问题,但首先来自DB方面。

解决这个错误的一种方法是将ForeignKey属性作为外键放在你想要的属性的顶部,并添加导航属性。

注意:在ForeignKey属性中,在括号和双引号之间,放置以这种方式引用的类的名称。

enter image description here

我想提出一种可以同时实现多对多配置的解决方案。

“catch"是我们需要创建一个以连接表为目标的视图,因为EF验证了每个EntitySet最多只能映射一次模式的表。

这个答案补充了之前的答案,并没有推翻任何这些方法,而是建立在它们的基础上。

模型:

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


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


public virtual ICollection<Comment> Comments { get; set; }
public virtual ICollection<MemberCommentView> MemberComments { get; set; }
}


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


public virtual ICollection<Member> Members { get; set; }
public virtual ICollection<MemberCommentView> 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; }
}

配置:

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;


public class MemberConfiguration : EntityTypeConfiguration<Member>
{
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<Comment>
{
public CommentConfiguration()
{
HasKey(x => x.CommentID);


Property(x => x.CommentID).HasColumnType("int").IsRequired();
Property(x => x.Message).HasColumnType("varchar(max)");
}
}


public class MemberCommentViewConfiguration : EntityTypeConfiguration<MemberCommentView>
{
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);
}
}

背景:

using System.Data.Entity;


public class MyContext : DbContext
{
public DbSet<Member> Members { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<MemberCommentView> 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);
}
}

从Saluma的(@Saluma) 回答

如果你现在想找到LastName =成员的所有注释 “Smith"例如,你可以这样写一个查询:

这仍然有效……

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

...但现在也可能是…

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

或创建一个名称为“smith”的成员列表;(我们假设有 你可以在他们的评论中使用投影:

这仍然有效……

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

...但现在也可能是…

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

如果要从成员中删除注释

var comment = ... // assume comment from member John Smith
var member = ... // assume member John Smith


member.Comments.Remove(comment);

如果你想Include()一个成员的注释

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

这一切都感觉像是语法糖,但是,如果您愿意进行额外的配置,它确实会给您带来一些好处。无论哪种方式,您似乎都能够获得两种方法的最佳效果。

我现在已经回到这里几次了,但似乎EF核心已经在过去的十年里做了一些更新,所以这是我目前在哪里与自定义连接实体设置多对多:

public class MemberModel
{
public int MemberId { get; set; }


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


public ICollection<CommentModel> Comments { get; set; }
}


public class CommentModel
{
public int CommentId { get; set; }
public string Message { get; set; }


public ICollection<MemberModel> Members { get; set; }
}


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


public int MembersId { get; set; }
[ForeignKey("MembersId")]
public MemberModel Member { get; set; }


public int CommentsId { get; set; }
[ForeignKey("CommentsId")]
public CommentModel Comment { get; set; }
}

然后在OnModelCreating中:

//Allows access directly from Comments or Members entities to the other
builder.Entity<MemberModel>()
.HasMany(x => x.Comments)
.WithMany(x => x.Members)
.UsingEntity<MemberCommentModel>();


//Defines the actual relationships for the middle table
builder.Entity<MemberCommentModel>()
.HasOne(x => x.Comment)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<MemberCommentModel>()
.HasOne(x => x.Member)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);