【问题标题】:Conventions determining join table foreign key column names确定连接表外键列名的约定
【发布时间】:2013-02-02 05:48:55
【问题描述】:

我注意到 EF 的一个奇怪现象以及它在评估 .Include(x=x.T) 语句时确定将使用哪些字段的方式。

在我们的项目中,我们所有的数据库表(以及 EF 的 POCO)都以 DB 为前缀,而有问题的两个表是 DBTemplatesDBItems。每个项目都有一个(可选)与之关联的模板。

我们DbContext 中的相关行是这样的:

public IDbSet<DBTemplate> Templates {get;set;}
public IDbSet<DBItem> Items {get;set;}

为简单起见,假设两个表都包含 Id 和 Name 属性,并且 DBItems 表具有返回 DBTemplates 的外键。这(现在)称为Template_Id(我稍后会解释原因),但一般来说我们会命名为DBTemplateId

一般来说,我们有一个名为db 的变量,它是我们DbContext 的一个实例。

最初创建DBItem 定义的开发人员添加了链接到相关DBTemplate 的虚拟属性,但省略了外键。属性如下所示:

public class DBItem
{
    public System.Guid Id { get; set; }
    public string Name { get; set; }
    public virtual DBTemplate Template { get; set; }
}

public class DBTemplate
{
    public System.Int32 Id { get; set; }
    public string Name { get; set; }
}

查询Item时,命中如下语句

db.Items.Include(i=>i.Template)

并生成如下SQL

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent2].[Id] AS [Id1], 
[Extent2].[Name] AS [Name1], 
FROM  [dbo].[DBItems] AS [Extent1]
LEFT OUTER JOIN [dbo].[DBTemplates] AS [Extent2] ON [Extent1].[Template_Id] = [Extent2].[Id] 

到目前为止,一切都很好。但是,我现在需要(并且应该拥有) DBItem 对象上的Template_Id。当我将它添加到 DBItem 类时,它看起来像这样:

public class DBItem
{
    public System.Guid Id { get; set; }
    public string Name { get; set; }
    public Nullable<System.Int32> Template_Id { get; set; }
    public virtual DBTemplate Template { get; set; }
}

命中同一行,但生成的 SQL 现在看起来像这样

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent1].[Template_Id] AS [Template_Id],
[Extent2].[Id] AS [Id1], 
[Extent2].[Name] AS [Name1], 
FROM  [dbo].[DBItems] AS [Extent1]
LEFT OUTER JOIN [dbo].[DBTemplates] AS [Extent2] ON [Extent1].[Template_Id1] = [Extent2].[Id] 

(注意连接的 Template_Id1

现在,这很容易解决,因为我可以告诉模型构建器查找名为 Template_Id 的外键,一切都很好。

builder.Entity<DBItem>().HasOptional(x => x.Template).WithMany().HasForeignKey(x => x.Template_Id);

但是,如果遵循约定,并且名为DBTemplateId 的字段和 DBItem 类看起来像这样:

public class DBItem
{
    public System.Guid Id { get; set; }
    public string Name { get; set; }
    public Nullable<System.Int32> DBTemplateId { get; set; }
    public virtual DBTemplate Template { get; set; }
}

在不修改 ModelBuilder 属性(让 EF 完全按照约定接管)的情况下,它正确地将 DBTemplateId 属性作为外键,并生成如下 SQL:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent1].[DBTemplateId] AS [DBTemplateId],
[Extent2].[Id] AS [Id1], 
[Extent2].[Name] AS [Name1], 
FROM  [dbo].[DBItems] AS [Extent1]
LEFT OUTER JOIN [dbo].[DBTemplates] AS [Extent2] ON [Extent1].[DBTemplateId] = [Extent2].[Id] 

并且查询按预期工作(使用不易出错的 C# 代码)。

显然这里的教训是遵循字段命名的模式,但是为什么 EF 在命名为 DBTemplateId 时会自动查找并使用属性名称作为连接键,但在存在 Template_Id 时会查找重复的 Template_Id1 POCO?

【问题讨论】:

    标签: c# entity-framework-4 ef-code-first


    【解决方案1】:

    当 Entity Framework Code First 检测到关系时,它会尝试通过遵循一些约定来发现应该用作外键的属性。

    它将查找名称与以下 3 条规则之一匹配的属性:

    • [Targettype key property name]
    • [Targettype name]+[Targettype key property name]
    • [Foreignkey navigation property name]+[Targettype key property name]

    因此,当您添加一个名为 DBTemplateId 的属性时,将匹配第二条规则,并且 Code First 将正确使用该属性作为外键并在数据库中生成一个具有该名称的外键列。

    如果您改为调用属性Template_Id,则不会匹配任何规则,Code First 将自动为外键生成一个名称。

    按照惯例,生成的名称将是 [Navigation property name]_[Targettype key property name],在您的情况下会生成 Template_Id。但是因为已经存在同名的属性,所以会追加数字 1。

    在我看来,Code First 有一个第四条规则来匹配它自己自动创建的相同名称是有意义的。如果这样做,Template_Id 将是一个合法名称,您不必使用 HasForeignKey 方法对其进行配置。

    【讨论】:

    • 对我来说很有意义,有记录的地方吗?
    • 不知道微软官方文档在哪里。我在 Programming Entity Framework: Code First 一书中读到了它,这本书很好地解释了这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 2015-03-04
    • 1970-01-01
    • 2012-03-22
    相关资源
    最近更新 更多