c# - 如何通过 Fluent API Entity Framework 定义多对多关系?

标签 c# entity-framework many-to-many 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;}
}

很明显,两个实体都具有多对多关系。 我选择了流利的 api 来告诉 Entity Framework 这种关系,即

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和许多其他地方,其中一个实体的配置应该做。

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

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

最佳答案

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

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

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

为了使用 User-Role 模型的原型(prototype)多对多示例来说明这一点,假设您有一个包含 UsersRolesRoleUsers 表:

Many-to-many database tables

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

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

此映射是错误的,如果您尝试将“Anna”放入角色“Marketing”...

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 导致违反外键约束,因为 UsersUserId 2 的用户表。

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

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

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

MapLeftKey

Configures the name of the column(s) for the left foreign key. The left foreign key represents the navigation property specified in the HasMany call.

MapRightKey

Configures the name of the column(s) for the right foreign key. The right foreign key represents the navigation property specified in the WithMany call.

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

也许 MapFirstEntityKeyMapSecondEntityKey 是那些方法名称的误导性较小的选择 - 或者 MapSourceEntityKeyMapTargetEntityKey.

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

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

关于c# - 如何通过 Fluent API Entity Framework 定义多对多关系?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16490334/

相关文章:

c# - 使我的 COM 程序集调用异步

c# - 如何在 MasterPage 中包含 JavaScript?

c# - LINQ to Entities 无法识别该方法

mysql - 选择一个没有特定标签的帖子

c# - 条件运算符和比较委托(delegate)

c# - 将 C# 类序列化为 XML 文本的更简单方法

asp.net-mvc-3 - MVC 3 Razor将父/子表显示为一张表

c# - 使用 new 进行 linq 查询的更好方法

database - Sequelize.js 多对多预加载

django - 与 Django 的唯一外键对