35

下面是我的模型:

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

很明显,这两个实体都有多对多的关系。我选择了 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.

但是当我从另一边添加配置时,一切正常。IE

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

为什么?。为什么我必须从两个实体中添加配置,我在这里和许多其他地方都读过,其中一个实体的配置应该做。

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

我需要明白这一点。为什么。请帮忙。

4

1 回答 1

116

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

有人可能认为这意味着它们描述了多对多连接表中“左”和“右”的列。如果您让 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; }
}

然后为实体添加 Fluent 映射Users(你必须这样做,因为按照惯例,上面的模型是一对多的,你不能从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将完全抛出您遇到的异常。当您捕获使用以下命令发送的 SQL 命令时,原因就很清楚了SaveChanges

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

因此,EF 想要在连接表中插入一行,其中RoleUsersa和 a导致外键约束违反,因为表中没有用户。RoleId1UserId2UserId 2Users

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

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

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

地图左键

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

地图右键

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

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

或许MapFirstEntityKeyandMapSecondEntityKey会是这些方法名称的误导性选择 - 或者也许MapSourceEntityKeyand MapTargetEntityKey

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

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

于 2013-05-11T20:18:05.047 回答