65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Entity Framework Code First 创建主键

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (25投票s)

2014 年 9 月 5 日

CPOL

9分钟阅读

viewsIcon

216763

downloadIcon

1158

探索用于创建主键的 Entity Framework Code First 约定和配置。

引言

本文介绍 Entity Framework Code First 约定和配置在创建主键列中的作用。

Entity Framework 要求每个实体都有一个键。通过使用此键,Entity Framework 可以跟踪该实体发生的变化。

以下是使用 Code First 创建主键的三种不同方法:

  1. 约定:名称为“Id”的属性,或名称为 {类名} + “Id”的属性(两者都不区分大小写
  2. 数据注解:[Key] 属性
  3. Fluent APIEntity<T>.HasKey(p => p.PropertyName) 函数

Using the Code

本文中的示例是通过使用 Visual Studio 2013 和 SQL Server 2008 创建的。

首先,让我们创建一个控制台应用程序

  1. 在 Visual Studio 中,点击文件 -> 新建 -> 项目。或者,您也可以从 Visual Studio 开始页(视图 -> 开始页)点击“新建项目...”链接。无论哪种方式,都会打开“新建项目”窗口。

    在“新建项目”窗口中,从已安装的模板中选择“Visual C#”。在中间面板中,列出了 Visual C# 模板,选择“控制台应用程序”。

  2. 将“EntityFramework” nuget 包添加到控制台应用程序。
    1. 如果您想从程序包管理器控制台视图 -> 其他窗口 -> 程序包管理器控制台)安装,请使用命令:
      Install-Package EntityFramework
    2. 您也可以通过管理 Nuget 程序包 GUI 安装。右键单击控制台项目并选择“管理 Nuget 程序包”。这将打开“管理 Nuget 程序包”窗口。在左侧选择“联机”选项卡,然后搜索“EntityFramework”。选择正确的程序包,然后单击“安装”。

  3. 在控制台应用程序的根目录下添加一个名为“Models”的文件夹。在“Models”文件夹中添加两个类文件:Student.csEducationContext.cs

    EducationContext.cs 文件将作为应用程序的上下文对象,它将跟踪实体上发生的所有更改。将 EducationContext 类继承自 DbContext,如下所示:

    public class EducationContext : DbContext

    EducationContext 类中创建一个带参数的构造函数,该参数接受连接字符串名称。此步骤用于指向“App.Config”文件中的正确数据库。

    public EducationContext()
       : base("EducationEntities")
    {
    }

    您可以将 base("EducationEntities") 替换为 base("name=EducationEntities"),效果相同。它告知 Code First 从 App.config 文件中使用名称为“EducationEntities”的连接字符串(因为这是一个控制台应用程序)。

    现在,在 App.config 文件中添加 <connectionStrings> 部分,如下所示:

    <connectionStrings>
      <add connectionString="Server={ServeName}; Database=Education;
            Integrated Security=SSPI" name="EducationEntities" 
            providerName="System.Data.SqlClient">
      </add>
    </connectionStrings>

    在我的情况下,{ServerName} 是 DUKHABANDHU-PC(计算机名称),因为我在我的 PC 上使用 Windows 凭据登录 MS SQL Server。请根据您的系统或服务器进行更改。如果您在设置连接字符串时遇到问题,可以参考此链接此链接

    在这里,我将数据库名称设置为“Education”。您可以更改为任何您喜欢的名称。

  4. Student.cs 文件中添加一个类型为 int 的名为“Id”的属性。
    public class Student
    {
      public int Id { get; set; }
    }

    Students 类将作为 EducationContext 的一个实体。因此,在 EducationContext.cs 文件中创建一个类型为 DbSet<Student> 的属性,如下所示:

    public class EducationContext : DbContext
    {
      public DbSet<Student> Students { get; set; }
    }
  5. 修改 Program.cs 文件,以便在每次应用程序运行时都删除并重新创建数据库。在这里删除并重新创建数据库不会有问题,因为我们不会保存任何重要数据。
    static void Main(string[] args)
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<EducationContext>());
    
        using (var context = new EducationContext())
        {
            context.Students.Add(new Student());
            context.SaveChanges();
        }
    
        Console.WriteLine("Database Created!!!");
        Console.ReadKey();
    }

    现在运行应用程序(在 Visual Studio 中,单击“开始”按钮或按 F5)。在 SQL Server 中,您可以看到一个名为 Education 的数据库,其中包含两个表:_MigrationHistoryStudents

    _MigrationHistory 表(EF 4.2 之前的 EdmMetadata 表)用于跟踪模型发生的所有更改。Students 表是根据我们在 EducationContext 类中创建的类型为 DbSet<Student> 的“Students”属性创建的。

关注点

1) 使用约定

Code First 主键约定是:名称为“Id”的属性,或 {类名} + “Id”的属性将作为该实体的​​主键。

a)Student.cs 文件中,有一个类型为 int 的单个属性“Id”。它符合 Code First 创建主键的约定。因此,“Students”表中有一个“Id”列,它是主键。请看下面的截图:

Students 表的 SQL 如下:

CREATE TABLE [dbo].[Students](
	[Id] [int] IDENTITY(1,1) NOT NULL,
 CONSTRAINT [PK_dbo.Students] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

b) 让我们从 Student.cs 文件中删除“Id”属性,并添加另一个名为“StudentId”的属性,它符合 {类名} + “Id”的约定,即“Student”+“Id”。现在的 Student 类如下:

public class Student
{
    public int StudentId { get; set; }
}

如果您运行应用程序,它将创建 _MigrationHistoryStudents 表,其中“StudentId”是 Students 表的主键。

有时,Code First 无法删除数据库并生成如下异常。这通常发生在某个进程正在使用 SQL Server 数据库,而在该过程中,Code First 尝试删除该数据库。如果发生这种情况,请从 SQL Server 中手动删除数据库。

SqlException

无法删除数据库“Education”,因为它当前正在使用中。

要手动删除数据库,请右键单击数据库,然后从上下文菜单中选择“删除”选项。或者,选择数据库并按 Delete 键。无论哪种方式,都会弹出“删除对象”窗口。选中底部的“关闭现有连接”复选框,然后单击“确定”按钮。

注意

创建主键的约定是不区分大小写的,即,如果您更改属性的大小写(例如 studentid)、大写(STUDENTID)或混合大小写(例如 STuDentiD),它仍会将该属性视为主键,但数据库中的列名将是属性的大小写。因此,如果您将属性命名为 STudenTiD,那么在 Students 表中,它将是主键,并且列名将是 STudenTiD

c) 让我们修改 Student.cs 文件并添加两个属性,IdStudentId,如下所示:

public class Student
{
   public int Id { get; set; }
   public int StudentId { get; set; }
}

在这里,IdStudentId 都符合创建主键的约定。现在运行应用程序以查看数据库中的效果。

如下面的截图所示,它创建了 Students 表,并将 Id 作为表的主键。

如果您更改属性的顺序,这不会改变主键,即 Id 仍将是主键。

2) 使用 [Key] 数据注解属性

a) [Key] 属性用于创建主键。它优先于约定。

让我们在 Student.cs 文件中添加两个属性,IdStudentId,并向 StudentId 属性添加 [Key] 属性,如下所示:

public class Student
{
   public int Id { get; set; }
   
   [Key]
   public int StudentId { get; set; }
}

如果您运行应用程序,它将创建“Students”表,并将 StudentId 作为主键。在这里,数据注解属性优先于约定。

b) 复合主键:复合键是两个或多个列的组合,它们唯一标识表中的一行。

让我们向控制台应用程序添加一个新的“Passport.cs”文件,其中包含两个属性:PassportNumberCountryCodePassportNumberCountryCode 唯一标识 Passports 表中的一条记录。该类如下:

public class Passport
{
  [Key]
  public string PassportNumber { get; set; }

  [Key]
  public string CountryCode { get; set; }  
}

EducationContext.cs 文件中添加另一个类型为 DbSet<Passport> 的“Passports”属性,如下所示:

public class EducationContext : DbContext
{
  public DbSet<Student> Students { get; set; }
  public DbSet<Passport> Passports { get; set; } 

  public EducationContext()
     : base("EducationEntities")
  {
  }
}

如果您运行应用程序,它将引发如下异常:

异常

无法确定类型 'CodeFirst.Models.Passport' 的复合主键排序。请使用 ColumnAttribute(参见 http://go.microsoft.com/fwlink/?LinkId=386388)或 HasKey 方法(参见 http://go.microsoft.com/fwlink/?LinkId=386387)来指定复合主键的顺序。

这清楚地表明,如果您使用 [Key] 数据注解属性创建复合键,您必须使用 [Column] 属性。

因此,让我们修改 Passport.cs 文件,通过指定列顺序来使用 [Column] 属性。列顺序是表中列的相对值。它不需要基于索引。

如下修改 Passport.cs 文件:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace CodeFirst.Models
{
   public class Passport
    {
       [Key]
       [Column(Order = 10)]
       public string PassportNumber { get; set; }

       [Key]
       [Column(Order = 20)]
       public string CountryCode { get; set; }  
    }
}

如果您运行应用程序,它将创建 Passports 表,并将 PassportNumberCountryCode 作为复合主键。

Passports 表的 SQL 如下:

CREATE TABLE [dbo].[Passports](
	[PassportNumber] [nvarchar](128) NOT NULL,
	[CountryCode] [nvarchar](128) NOT NULL,
 CONSTRAINT [PK_dbo.Passports] PRIMARY KEY CLUSTERED 
(
	[PassportNumber] ASC,
	[CountryCode] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,
 ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

3) 使用 Fluent API

Fluent API 是一种编程方法,当前使用的该方法为下一个调用提供有价值的方法 intellisense/选项。Entity Framework Code First Fluent API 提供了多种有用的方法来执行映射。要定义主键,它提供了 HasKey() 方法。Fluent API 优先于数据注解属性。

要使用 Code First Fluent API 指定映射,我们必须重写 OnModelCreating() 方法。OnModelCreating() 方法在模型创建之前被调用。

a) 让我们如下修改 EducationContext.cs 文件以使用 Fluent API 映射:

public class EducationContext : DbContext
{
   public DbSet<Student> Students { get; set; }
   public DbSet<Passport> Passports { get; set; } 

   public EducationContext()
	: base("EducationEntities")
   {
   }

   protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {
      modelBuilder.Entity<Student>().HasKey(s => s.Id);
      base.OnModelCreating(modelBuilder);
   }
}

在这里,我们将“Id”设置为 Students 表的主键。

Student 类如下:

public class Student
{
   public int Id { get; set; }

   [Key]
   public int StudentId { get; set; }
}

如果您运行应用程序,它将创建 Students 表,并将 Id 作为主键。请注意,我们在 Student 类中为 StudentId 属性分配了 [Key] 属性。在这里,Fluent API 优先于数据注解。

b) 使用 Fluent API 的复合主键:使用 Code First Fluent API 创建复合主键很容易。修改 EducationContext 类中的 OnModelCreating() 方法,如下所示:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
   modelBuilder.Entity<Passport>().HasKey(s => new { s.PassportNumber, s.CountryCode});
   base.OnModelCreating(modelBuilder);
}

在这里,我们创建了一个复合主键,使用 PassportNumberCountryCode 属性。如果您运行应用程序,它将创建 Passports 表,并带有复合主键,如下所示:

注意

每当 Code First 按约定、数据注解或 fluent API 创建主键(对于像 intlong 等整型数据类型)时,它都会创建一个具有标识开启的主键列。您可以使用 DatabaseGeneratedOption.None enum 关闭标识。对于数据类型为 Guid 的主键属性,Code First 默认不为此列创建标识。您可以使用 DatabaseGeneratedOption.Identity enum 开启标识。Entity Framework 使用 DatabaseGeneratedOption 来决定在插入/更新记录时如何处理键属性的值。

DatabaseGeneratedOption enum 有三个成员(参见文档):

  1. Computed:数据库在插入或更新行时生成值。
  2. Identity:数据库在插入行时生成值。
  3. None:数据库不生成值。

使用数据注解

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }

使用 Fluent API

modelBuilder.Entity<Student>().Property(s => s.Id).HasDatabaseGeneratedOption
(DatabaseGeneratedOption.None);

结论

有三种不同的方法可以使用 Code First 创建主键。但 Fluent API 方法是首选的,因为它将映射逻辑与域类分开。数据注解属性是有限的。有些映射无法使用数据注解属性完成,但可以使用 Fluent API 完成。对于创建表和列,Fluent API 的优先级最高。在创建模型之前,Code First 首先考虑约定,然后读取数据注解属性,最后读取 Fluent API 指定的映射。使用 Entity Framework Code First 创建主键的优先级顺序是:

约定 ---> 数据注解 ----> Fluent API(最高优先级)

使用附加源代码的注意事项

附加的源代码包含最终版本的演练。但是,其中有一些注释掉的代码可用于测试 Code First 约定和配置的创建主键。在运行应用程序之前,请根据您的系统或服务器更改连接字符串。

下载源代码(不含包)”压缩文件包含演示的轻量级版本,其中已排除程序包。如果您使用此版本,请恢复程序包

历史

  • 2014 年 9 月 5 日:初始版本
© . All rights reserved.