如何通过Fluent API实体框架定义多对多关系?

Man*_*hra 32 c# many-to-many entity-framework ef-fluent-api

以下是我的模型:

public class TMUrl
{
    //many other properties

    //only property with type Keyword
    public List<Keyword> Keywords{get;set;} 
}

public class Keyword
{
   //many other properties

   //only property with type TMUrl
   public List<TMUrl> Urls{get;set;}
}
Run Code Online (Sandbox Code Playgroud)

很明显,两个实体都有多对多的关系.我选择了流畅的api告诉实体框架关于这种关系,即

modelBuilder.Entity<TMUrl>
               .HasMany(s => s.Keywords)
               .WithMany(s => s.URLs).Map(s =>
                {
                    s.MapLeftKey("KeywordId");
                    s.MapRightKey("UrlId");
                    s.ToTable("KeywordUrlMapping");
                });
Run Code Online (Sandbox Code Playgroud)

但是当我这样做的时候

url.Keywords.Add(dbKey); //where url is object of TMUrl, 
                         //dbKey is an existing/new object of Keyword
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

我得到例外

An error occurred while saving entities that do not expose foreign key 
properties for their relationships....
Run Code Online (Sandbox Code Playgroud)

的InnerException:

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.
Run Code Online (Sandbox Code Playgroud)

但是当我从其他地方添加配置时,一切正常.即

modelBuilder.Entity<KeyWord>
         .HasMany(s => s.URLs)
         .WithMany(s => s.Keywords)
         .Map(s =>
               {
                  s.MapLeftKey("KeywordId");
                  s.MapRightKey("UrlId");
                  s.ToTable("KeywordUrlMapping");
               });
Run Code Online (Sandbox Code Playgroud)

为什么?.为什么我要从两个实体添加配置,我在这里阅读的地方和许多其他地方,其中一个实体的配置应该这样做.

是什么情况,我应该为这两个关系中涉及的实体添加配置?

我需要了解这一点.为什么.请帮忙.

Sla*_*uma 114

条款LeftRightMapLeftKeyMapRightKey在许多一对多映射用流利的API可能被误解,我想你的问题是由这种误解造成的.

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

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

要使用的的原型许多一对多的例子说明这一点User- Role模型假设你有一个现有的数据库Users,RolesRoleUsers表:

多对多数据库表

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

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; }
}
Run Code Online (Sandbox Code Playgroud)

并为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");
    });
Run Code Online (Sandbox Code Playgroud)

这种映射是错误的,如果你试图将"Anna"放入"营销"角色......

var anna = ctx.Users.Find(1);
var marketing = ctx.Roles.Find(2);

anna.Roles.Add(marketing);

ctx.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

... SaveChanges将完全抛出你所拥有的例外.当您捕获使用以下命令发送的SQL命令时,原因就变得清晰了SaveChanges:

exec sp_executesql N'insert [dbo].[RoleUsers]([RoleId], [UserId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2
Run Code Online (Sandbox Code Playgroud)

因此,EF想在这里插入一行到连接表RoleUsersRoleId1UserId2,这是造成外键约束违反,因为没有用户与UserId 2Users表中.

换言之,映射上述已配置了该列RoleId作为外键表Users和列UserId作为外键表Roles.为了纠正映射,我们必须使用连接表中的"左"列名称MapRightKey和"右"列中的MapLeftKey:

        m.MapLeftKey("UserId");
        m.MapRightKey("RoleId");
Run Code Online (Sandbox Code Playgroud)

实际上看Intellisense时,描述更清楚"左"和"右"的含义是什么:

MapLeftKey

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

MapRightKey

配置正确外键的列名称.右外键表示WithMany调用中指定的导航属性.

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

也许MapFirstEntityKey并且MapSecondEntityKey本来是一个不那么误导性的选择那些方法名称 - 或者也许MapSourceEntityKeyMapTargetEntityKey.

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

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

  • +1解释MapLeftKey和MapRightKey (10认同)
  • +1深入讨论和解释.我花了大约20左右调试堆栈跟踪视图,找出你在长篇文章中解释的内容. (3认同)