Entity Framework 4.1 Database First 不会向 DbContext T4 生成的类添加主键

发布于 2024-12-20 10:08:39 字数 4006 浏览 2 评论 0原文

我刚刚开始使用 Entity Framework 4.1,尝试“数据库优先”模式。当 EF 使用“ADO.Net DbContext Generator”生成模型类时,它不应该使用 [Key] 属性来标识该类的主键吗?如果没有这个,它看起来与 T4 MVCScaffolding 不兼容。

详细信息如下:

使用实体数据模型设计器 GUI,我从现有数据库向模型添加了一个简单的“国家/地区”表。 GUI 正确地将名为“PK”的单个整数身份键字段识别为我的主键。 (唉!我是新用户,所以无法添加屏幕截图。我在下面包含了 CSDL。)但是,当 EF 使用“ADO.Net DbContext Generator”生成代码时,它不会识别 PK字段作为生成的类中的关键字段(请参阅下面的代码摘录)。

“国家/地区”表的 CSDL:

<edmx:ConceptualModels>
  <Schema Namespace="EpiDataModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
    <EntityContainer Name="EpiModelEntities" annotation:LazyLoadingEnabled="true">
      <EntitySet Name="countries" EntityType="EpiDataModel.country" />
    </EntityContainer>
    <EntityType Name="country">
      <Key>
        <PropertyRef Name="PK" />
      </Key>
      <Property Name="PK" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
      <Property Name="Abbreviation" Type="String" Nullable="false" MaxLength="200" Unicode="false" FixedLength="false" />
      <Property Name="Name" Type="String" MaxLength="1024" Unicode="false" FixedLength="false" />
      <Property Name="Description" Type="String" MaxLength="1024" Unicode="false" FixedLength="false" />
      <Property Name="Sequence" Type="Int32" />
    </EntityType>
  </Schema>
</edmx:ConceptualModels>

这是自动生成的代码:

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;

namespace MvcApplication1.Areas.Epi.Models
{
    public partial class country
    {
        public int PK { get; set; }
        public string Abbreviation { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public Nullable<int> Sequence { get; set; }
    }
}

当我尝试使用 MVCScaffolding T4 模板搭建控制器时,这会导致问题。我收到错误“没有属性似乎是主键。” NuGet 包管理器控制台的命令和输出如下:

PM> scaffold controller MvcApplication1.Areas.Epi.Models.country -Area Epi -NoChildItems -DbContextType MvcApplication1.Areas.Epi.Models.EpiModelEntities -Force
Scaffolding countriesController...
Get-PrimaryKey : Cannot find primary key property for type 'MvcApplication1.Areas.Epi.Models.country'. No properties appear to be primary keys.
At C:\work\EPI\EPIC_MVC3\sandbox\MvcApplication1\packages\MvcScaffolding.1.0.6\tools\Controller\MvcScaffolding.Controller.ps1:74 char:29
+ $primaryKey = Get-PrimaryKey <<<<  $foundModelType.FullName -Project $Project -ErrorIfNotFound
    + CategoryInfo          : NotSpecified: (:) [Get-PrimaryKey], Exception
    + FullyQualifiedErrorId : T4Scaffolding.Cmdlets.GetPrimaryKeyCmdlet

但是,如果我手动更改生成的类以向字段添加 [Key] 属性,则上面显示的完全相同的脚手架命令可以正常工作:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; // manually added

namespace MvcApplication1.Areas.Epi.Models
{
    public partial class country
    {
        [Key]                        // manually added
        public int PK { get; set; }
        public string Abbreviation { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public Nullable<int> Sequence { get; set; }
    }
}

那么为什么不使用 EF Database First 和 T4 MVCScaffolding 一起玩得好吗?即使没有脚手架问题,EF 类不需要知道关键字段是什么吗?

I am just getting started with Entity Framework 4.1, trying out the "database first" mode. When EF generates a Model class with the "ADO.Net DbContext Generator," shouldn't it identify the primary key for the class with a [Key] attribute? Without this, it appears incompatible with the T4 MVCScaffolding.

Here are the details:

Using the Entity Data Model Designer GUI, I have added a simple "country" table to the model from my existing database. The GUI correctly identifies a single integer identity key field named "PK" as my primary key. (Alas! I'm a new user so I can't add a screenshot. I've included the CSDL instead below.) However, when EF generates code using the "ADO.Net DbContext Generator", it does not identify the PK field as the key field in the generated class (see code excerpt below).

The CSDL for the "country" table:

<edmx:ConceptualModels>
  <Schema Namespace="EpiDataModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
    <EntityContainer Name="EpiModelEntities" annotation:LazyLoadingEnabled="true">
      <EntitySet Name="countries" EntityType="EpiDataModel.country" />
    </EntityContainer>
    <EntityType Name="country">
      <Key>
        <PropertyRef Name="PK" />
      </Key>
      <Property Name="PK" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
      <Property Name="Abbreviation" Type="String" Nullable="false" MaxLength="200" Unicode="false" FixedLength="false" />
      <Property Name="Name" Type="String" MaxLength="1024" Unicode="false" FixedLength="false" />
      <Property Name="Description" Type="String" MaxLength="1024" Unicode="false" FixedLength="false" />
      <Property Name="Sequence" Type="Int32" />
    </EntityType>
  </Schema>
</edmx:ConceptualModels>

Here's the autogenerated code:

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;

namespace MvcApplication1.Areas.Epi.Models
{
    public partial class country
    {
        public int PK { get; set; }
        public string Abbreviation { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public Nullable<int> Sequence { get; set; }
    }
}

This causes a problem when I try to scaffold a controller using the MVCScaffolding T4 template. I get an error "No properties appear to be primary keys." The command and output from the NuGet Package Manager Console is below:

PM> scaffold controller MvcApplication1.Areas.Epi.Models.country -Area Epi -NoChildItems -DbContextType MvcApplication1.Areas.Epi.Models.EpiModelEntities -Force
Scaffolding countriesController...
Get-PrimaryKey : Cannot find primary key property for type 'MvcApplication1.Areas.Epi.Models.country'. No properties appear to be primary keys.
At C:\work\EPI\EPIC_MVC3\sandbox\MvcApplication1\packages\MvcScaffolding.1.0.6\tools\Controller\MvcScaffolding.Controller.ps1:74 char:29
+ $primaryKey = Get-PrimaryKey <<<<  $foundModelType.FullName -Project $Project -ErrorIfNotFound
    + CategoryInfo          : NotSpecified: (:) [Get-PrimaryKey], Exception
    + FullyQualifiedErrorId : T4Scaffolding.Cmdlets.GetPrimaryKeyCmdlet

However, if I manually change the generated class to add a [Key] attribute to the field, then the exact same scaffolding command shown above works fine:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; // manually added

namespace MvcApplication1.Areas.Epi.Models
{
    public partial class country
    {
        [Key]                        // manually added
        public int PK { get; set; }
        public string Abbreviation { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public Nullable<int> Sequence { get; set; }
    }
}

So why aren't EF Database First and the T4 MVCScaffolding playing nice together? And even without the scaffolding issue, don't the EF classes need to know what the key field(s) are?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

绾颜 2024-12-27 10:08:39

T4 模板不使用数据注释,因为从模板生成的类不需要它们。 EF 也不需要它们,因为映射是在 XML 文件中定义的,而不是在代码中定义的。如果您需要数据注释,则必须:

  • 修改 T4 模板以使用它们(这需要了解 EF 元数据模型)
  • 不要使用模板并首先使用代码
  • 使用 好友类手动添加数据注释并希望脚手架能够识别 他们

T4 Templates doesn't use data annotations because classes generated from templates don't need them. EF also don't need them because mapping is defined in XML files not in code. If you need data annotations you must either:

  • Modify T4 template to use them (this requires understanding of EF metadata model)
  • Don't use templates and use code first instead
  • Use buddy classes to manually add data annotations and hope that scaffolding will recognize them
隱形的亼 2024-12-27 10:08:39

如果有人确实想这样做,我发现了一些很好的有趣的模板
詹姆斯·曼宁斯 github
这些模板具有更多功能,但我从中提取的内容是:
1) 将 Entity.tt 顶部的 usings 替换为

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
<#
    if (efHost.EntityFrameworkVersion >= new Version(4, 4))
    {
       WriteLine("using System.ComponentModel.DataAnnotations.Schema;");
    }
#>

2) 然后找到这一行(打印出属性)

    <#= Accessibility.ForProperty(property) #> <#= typeUsage #> <#= code.Escape(property) #> { get; set; }

3) 并在前面添加此模板代码

    var attributes = new List<string>();
    var isPartOfPrimaryKey = efHost.EntityType.KeyMembers.Contains(property);
    var primaryKeyHasMultipleColumns = efHost.EntityType.KeyMembers.Count > 1;

    if (isPartOfPrimaryKey)
    {
        if (primaryKeyHasMultipleColumns)
        {
            var columnNumber = efHost.EntityType.KeyMembers.IndexOf(property);
            attributes.Add(String.Format("[Key, Column(Order = {0})]", columnNumber));
        }
        else
        {
            attributes.Add("[Key]");
        }
    }
    PushIndent(new string(' ', 8));
    foreach (var attribute in attributes)
    {
        WriteLine(attribute);
    }
    ClearIndent();

If someone does want to do this, I found some good interesting templates on
james mannings github
Those templates have more functionality, but the bit I pulled out of these was:
1) Replace the usings at the top of Entity.tt with

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
<#
    if (efHost.EntityFrameworkVersion >= new Version(4, 4))
    {
       WriteLine("using System.ComponentModel.DataAnnotations.Schema;");
    }
#>

2) Then find this line (that prints out the properties)

    <#= Accessibility.ForProperty(property) #> <#= typeUsage #> <#= code.Escape(property) #> { get; set; }

3) and prepend this template code

    var attributes = new List<string>();
    var isPartOfPrimaryKey = efHost.EntityType.KeyMembers.Contains(property);
    var primaryKeyHasMultipleColumns = efHost.EntityType.KeyMembers.Count > 1;

    if (isPartOfPrimaryKey)
    {
        if (primaryKeyHasMultipleColumns)
        {
            var columnNumber = efHost.EntityType.KeyMembers.IndexOf(property);
            attributes.Add(String.Format("[Key, Column(Order = {0})]", columnNumber));
        }
        else
        {
            attributes.Add("[Key]");
        }
    }
    PushIndent(new string(' ', 8));
    foreach (var attribute in attributes)
    {
        WriteLine(attribute);
    }
    ClearIndent();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文