使用 Entity Framework Code First 创建主键






4.92/5 (25投票s)
探索用于创建主键的 Entity Framework Code First 约定和配置。
引言
本文介绍 Entity Framework Code First 约定和配置在创建主键列中的作用。
Entity Framework 要求每个实体都有一个键。通过使用此键,Entity Framework 可以跟踪该实体发生的变化。
以下是使用 Code First 创建主键的三种不同方法:
- 约定:名称为“
Id
”的属性,或名称为 {类名} + “Id
”的属性(两者都不区分大小写) - 数据注解:[Key] 属性
- Fluent API:
Entity<T>.HasKey(p => p.PropertyName)
函数
Using the Code
本文中的示例是通过使用 Visual Studio 2013 和 SQL Server 2008 创建的。
首先,让我们创建一个控制台应用程序。
- 在 Visual Studio 中,点击文件 -> 新建 -> 项目。或者,您也可以从 Visual Studio 开始页(视图 -> 开始页)点击“新建项目...”链接。无论哪种方式,都会打开“新建项目”窗口。
在“新建项目”窗口中,从已安装的模板中选择“Visual C#”。在中间面板中,列出了 Visual C# 模板,选择“控制台应用程序”。
- 将“
EntityFramework
” nuget 包添加到控制台应用程序。- 如果您想从程序包管理器控制台(视图 -> 其他窗口 -> 程序包管理器控制台)安装,请使用命令:
Install-Package EntityFramework - 您也可以通过管理 Nuget 程序包 GUI 安装。右键单击控制台项目并选择“管理 Nuget 程序包”。这将打开“管理 Nuget 程序包”窗口。在左侧选择“联机”选项卡,然后搜索“EntityFramework”。选择正确的程序包,然后单击“安装”。
- 如果您想从程序包管理器控制台(视图 -> 其他窗口 -> 程序包管理器控制台)安装,请使用命令:
- 在控制台应用程序的根目录下添加一个名为“Models”的文件夹。在“Models”文件夹中添加两个类文件:Student.cs 和 EducationContext.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
”。您可以更改为任何您喜欢的名称。 - 在 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; } }
- 修改 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
的数据库,其中包含两个表:_MigrationHistory
和Students
。_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; }
}
如果您运行应用程序,它将创建 _MigrationHistory
和 Students
表,其中“StudentId
”是 Students
表的主键。
有时,Code First 无法删除数据库并生成如下异常。这通常发生在某个进程正在使用 SQL Server 数据库,而在该过程中,Code First 尝试删除该数据库。如果发生这种情况,请从 SQL Server 中手动删除数据库。
SqlException无法删除数据库“Education”,因为它当前正在使用中。
要手动删除数据库,请右键单击数据库,然后从上下文菜单中选择“删除”选项。或者,选择数据库并按 Delete 键。无论哪种方式,都会弹出“删除对象”窗口。选中底部的“关闭现有连接”复选框,然后单击“确定”按钮。
注意
创建主键的约定是不区分大小写的,即,如果您更改属性的大小写(例如 studentid
)、大写(STUDENTID
)或混合大小写(例如 STuDentiD
),它仍会将该属性视为主键,但数据库中的列名将是属性的大小写。因此,如果您将属性命名为 STudenTiD
,那么在 Students
表中,它将是主键,并且列名将是 STudenTiD
。
c) 让我们修改 Student.cs 文件并添加两个属性,Id
和 StudentId
,如下所示:
public class Student
{
public int Id { get; set; }
public int StudentId { get; set; }
}
在这里,Id
和 StudentId
都符合创建主键的约定。现在运行应用程序以查看数据库中的效果。
如下面的截图所示,它创建了 Students
表,并将 Id
作为表的主键。
如果您更改属性的顺序,这不会改变主键,即 Id
仍将是主键。
2) 使用 [Key] 数据注解属性
a) [Key]
属性用于创建主键。它优先于约定。
让我们在 Student.cs 文件中添加两个属性,Id
和 StudentId
,并向 StudentId
属性添加 [Key]
属性,如下所示:
public class Student
{
public int Id { get; set; }
[Key]
public int StudentId { get; set; }
}
如果您运行应用程序,它将创建“Students
”表,并将 StudentId
作为主键。在这里,数据注解属性优先于约定。
b) 复合主键:复合键是两个或多个列的组合,它们唯一标识表中的一行。
让我们向控制台应用程序添加一个新的“Passport.cs”文件,其中包含两个属性:PassportNumber
和 CountryCode
。PassportNumber
和 CountryCode
唯一标识 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
表,并将 PassportNumber
和 CountryCode
作为复合主键。
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);
}
在这里,我们创建了一个复合主键,使用 PassportNumber
和 CountryCode
属性。如果您运行应用程序,它将创建 Passports
表,并带有复合主键,如下所示:
注意
每当 Code First 按约定、数据注解或 fluent API 创建主键(对于像 int
、long
等整型数据类型)时,它都会创建一个具有标识开启的主键列。您可以使用 DatabaseGeneratedOption.None enum
关闭标识。对于数据类型为 Guid
的主键属性,Code First 默认不为此列创建标识。您可以使用 DatabaseGeneratedOption.Identity enum
开启标识。Entity Framework 使用 DatabaseGeneratedOption
来决定在插入/更新记录时如何处理键属性的值。
DatabaseGeneratedOption enum
有三个成员(参见文档):
Computed
:数据库在插入或更新行时生成值。Identity
:数据库在插入行时生成值。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 日:初始版本