确定连接表外键列名称的约定

时间:2013-01-31 18:40:54

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

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

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

我们DbContext中的相关行是:

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

为简单起见,假设两个表都包含Id和Name属性以及具有返回DBTemplates的外键的DBItems表。这是(现在)称为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; }
}

查询项目时,会触及以下语句

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_Id1时查找重复的Template_Id {1}}存在于POCO上?

1 个答案:

答案 0 :(得分:2)

当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方法对其进行配置。