如何通过 Fluent API Entity Framework 定义多对多关系?
Posted
技术标签:
【中文标题】如何通过 Fluent API Entity Framework 定义多对多关系?【英文标题】:How to define Many-to-Many relationship through Fluent API Entity Framework? 【发布时间】:2013-05-05 14:45:11 【问题描述】:下面是我的模型:
public class TMUrl
//many other properties
//only property with type Keyword
public List<Keyword> Keywordsget;set;
public class Keyword
//many other properties
//only property with type TMUrl
public List<TMUrl> Urlsget;set;
很明显,这两个实体都具有多对多关系。 我选择了 fluent api 来告诉实体框架这种关系,即
modelBuilder.Entity<TMUrl>
.HasMany(s => s.Keywords)
.WithMany(s => s.URLs).Map(s =>
s.MapLeftKey("KeywordId");
s.MapRightKey("UrlId");
s.ToTable("KeywordUrlMapping");
);
但是当我这样做时
url.Keywords.Add(dbKey); //where url is object of TMUrl,
//dbKey is an existing/new object of Keyword
db.SaveChanges();
我得到异常
An error occurred while saving entities that do not expose foreign key
properties for their relationships....
内部异常:
The INSERT statement conflicted with the FOREIGN KEY constraint
"KeywordMaster_Keyword". The conflict occurred in database "DbName",
table "dbo.KeywordMaster", column 'Id'.The statement has been terminated.
但是当我从另一边添加配置时,一切正常。 即
modelBuilder.Entity<KeyWord>
.HasMany(s => s.URLs)
.WithMany(s => s.Keywords)
.Map(s =>
s.MapLeftKey("KeywordId");
s.MapRightKey("UrlId");
s.ToTable("KeywordUrlMapping");
);
为什么?为什么我必须从两个实体中添加配置,我已经阅读了 here 和许多其他地方,其中一个实体的配置应该这样做。
什么情况,什么时候应该为关系中涉及的两个实体添加配置?
我需要明白这一点。为什么。请帮忙。
【问题讨论】:
"查看 InnerException 了解详情"。你是否?对于这个很少有用的异常,了解内部异常非常重要。 @Slauma,我已经用内部异常更新了问题,但请注意,从双方添加关系使其工作。 有趣的问题是,如果只有第二个映射(没有第一个映射)可以使其工作。如果是,我怀疑映射表中的列KeywordId
实际上是表URLs
而不是Keywords
的外键。 (仅当这是具有手动创建关系的现有数据库时才可能,而不是在您使用 Code-First 创建数据库时。)
否@Slauma,我检查了 fk_relation 并且 KeywordId 映射到 KeywordMaster 的 Id。不知道为什么会这样;有时它只是有效;有时它只是不会。还有一件事,我有一个静态的单一上下文。我希望这不会导致这一切
是的,Left
和 Right
这两个术语在这里令人困惑,因为它们似乎表明它们与表中的列顺序有关。也许MapFirstEntityKey
和MapSecondEntityKey
会更清楚地表明它们实际上是指映射中的实体顺序。当然,如果 EF 创建数据库本身 First
和 Left
匹配。但是如果你手动做,顺序可以正好相反。
【参考方案1】:
在使用 Fluent API 的多对多映射中,MapLeftKey
和 MapRightKey
中的术语 Left
和 Right
可能会被误解,我猜你的问题是由这种误解引起的。
人们可能会认为这意味着它们描述了多对多连接表中“左”和“右”的列。如果您让 EF Code-First 根据您的 Fluent 映射创建数据库和连接表,实际上就是这种情况。
但当您创建到现有数据库的映射时,情况不一定如此。
用User
-Role
模型的原型多对多示例来说明这一点,假设您有一个现有数据库,其中包含Users
、Roles
和RoleUsers
表:
现在,您想将此表架构映射到一个简单模型:
public class User
public User()
Roles = new List<Role>();
public int UserId get; set;
public string UserName get; set;
public ICollection<Role> Roles get; set;
public class Role
public int RoleId get; set;
public string RoleName get; set;
然后为 Users
实体添加 Fluent 映射(您必须这样做,因为按照惯例,上面的模型是一对多的,您不能从 Role
实体开始,因为它没有Users
集合):
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany()
.Map(m =>
m.MapLeftKey("RoleId"); // because it is the "left" column, isn't it?
m.MapRightKey("UserId"); // because it is the "right" column, isn't it?
m.ToTable("RoleUsers");
);
此映射是错误的,如果您尝试将“安娜”放入角色“营销”...
var anna = ctx.Users.Find(1);
var marketing = ctx.Roles.Find(2);
anna.Roles.Add(marketing);
ctx.SaveChanges();
...SaveChanges
将完全抛出您遇到的异常。当您捕获使用SaveChanges
发送的SQL 命令时,原因就很清楚了:
exec sp_executesql N'insert [dbo].[RoleUsers]([RoleId], [UserId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2
因此,EF 想要在连接表 RoleUsers
中插入一行,其中 RoleId
的 1
和 UserId
的 2
导致外键约束违规,因为没有用户UserId
2
在Users
表中。
换句话说,上面的映射已将列RoleId
配置为表Users
的外键,并将列UserId
配置为表Roles
的外键。为了更正映射,我们必须在MapRightKey
的连接表中使用“左”列名,在MapLeftKey
中使用“右”列:
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
实际上查看 Intellisense 的描述可以更清楚地了解“左”和“右”的真正含义:
MapLeftKey
为左外键配置列的名称。 该 左外键表示指定的导航属性 HasMany 调用。
MapRightKey
为右外键配置列的名称。 该 右外键表示指定的导航属性 WithMany 调用。
因此,“左”和“右”指的是实体在 Fluent 映射中出现的顺序,而不是连接表中的列顺序。表中的顺序实际上并不重要,您可以在不破坏任何内容的情况下更改它,因为 EF 发送的 INSERT
是一个“扩展”INSERT
,它还包含列名,而不仅仅是值。
也许 MapFirstEntityKey
和 MapSecondEntityKey
会是这些方法名称的一个较少误导的选择 - 或者可能是 MapSourceEntityKey
和 MapTargetEntityKey
。
这是一篇关于两个词的长篇文章。
如果我的猜测是正确的,它与你的问题有任何关系,那么我会说你的第一个映射不正确,你只需要第二个正确的映射。
【讨论】:
+1 进行深入讨论和解释。我花了大约 20 个调试堆栈跟踪视图来弄清楚你在长篇文章中解释的内容。 根据我的经验,它只是对立,LeftKey 用于 WithMany 单元格,RightKey 用于 HasMany 单元格。这里也提到了:msdn.microsoft.com/en-us/data/hh134698.aspx 为 MapLeftKey 和 MapRightKey 命名方法名称 MapSourceEntityKey 和 MapTargetEntityKey 说明了一切!你只能写这两个词来解释一切 我很抱歉,但这不是真的。它更像是随机的:我有一个模型,您无法找到 MapLeftKey 是否与 HasMany() 或 WithMany() 相关的规则,两种变体都会出现(并且双方都没有定义多对多关系)和找出答案的唯一方法似乎是通过适当的单元测试(使用每边都有主键“1”的新数据进行测试是不正确的)。 MapLeftKey的描述应该是“左外键指向HasMany调用中指定的导航属性的父实体”,MapRightKey应该是“右外键指向父实体” WithMany 调用中指定的导航属性。"以上是关于如何通过 Fluent API Entity Framework 定义多对多关系?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Entity Framework Code First Fluent API 指定表名
Entity Framework Code First Fluent API - 配置属性/类型