如何通过 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。不知道为什么会这样;有时它只是有效;有时它只是不会。还有一件事,我有一个静态的单一上下文。我希望这不会导致这一切 是的,LeftRight 这两个术语在这里令人困惑,因为它们似乎表明它们与表中的列顺序有关。也许MapFirstEntityKeyMapSecondEntityKey 会更清楚地表明它们实际上是指映射中的实体顺序。当然,如果 EF 创建数据库本身 FirstLeft 匹配。但是如果你手动做,顺序可以正好相反。 【参考方案1】:

在使用 Fluent API 的多对多映射中,MapLeftKeyMapRightKey 中的术语 LeftRight 可能会被误解,我猜你的问题是由这种误解引起的。

人们可能会认为这意味着它们描述了多对多连接表中“左”和“右”的列。如果您让 EF Code-First 根据您的 Fluent 映射创建数据库和连接表,实际上就是这种情况。

但当您创建到现有数据库的映射时,情况不一定如此。

User-Role 模型的原型多对多示例来说明这一点,假设您有一个现有数据库,其中包含UsersRolesRoleUsers 表:

现在,您想将此表架构映射到一个简单模型:

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 中插入一行,其中 RoleId1UserId2 导致外键约束违规,因为没有用户UserId 2Users 表中。

换句话说,上面的映射已将列RoleId 配置为表Users 的外键,并将列UserId 配置为表Roles 的外键。为了更正映射,我们必须在MapRightKey 的连接表中使用“左”列名,在MapLeftKey 中使用“右”列:

        m.MapLeftKey("UserId");
        m.MapRightKey("RoleId");

实际上查看 Intellisense 的描述可以更清楚地了解“左”和“右”的真正含义:

MapLeftKey

为左外键配置列的名称。 该 左外键表示指定的导航属性 HasMany 调用。

MapRightKey

为右外键配置列的名称。 该 右外键表示指定的导航属性 WithMany 调用。

因此,“左”和“右”指的是实体在 Fluent 映射中出现的顺序,而不是连接表中的列顺序。表中的顺序实际上并不重要,您可以在不破坏任何内容的情况下更改它,因为 EF 发送的 INSERT 是一个“扩展”INSERT,它还包含列名,而不仅仅是值。

也许 MapFirstEntityKeyMapSecondEntityKey 会是这些方法名称的一个较少误导的选择 - 或者可能是 MapSourceEntityKeyMapTargetEntityKey

这是一篇关于两个词的长篇文章。

如果我的猜测是正确的,它与你的问题有任何关系,那么我会说你的第一个映射不正确,你只需要第二个正确的映射。

【讨论】:

+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 指定表名

如何从 Fluent Api 检索实体配置

Entity Framework Code First Fluent API - 配置属性/类型

Entity Framework Code First Fluent API - 配置关系

Entity Framework Fluent API

Entity Framework 6 0.1 到 0.1 与 fluent api 的关系