熟知 API-配置和映射属性和类型Fluent API - Configuring and Mapping Properties and Types

在使用实体框架时 Code First 默认行为是使用一组约定融入为 EF 将 POCO 类映射到表。 但有时,您不能或不想遵循这些约定,也不需要将实体映射到约定规定的内容。

可以通过两种主要方式将 EF 配置为使用约定以外的其他内容,即批注或 EFs Fluent API。 批注仅涵盖 Fluent API 功能的子集,因此存在无法使用批注实现的映射方案。 本文旨在演示如何使用 Fluent API 来配置属性。

通过重写派生DbContext上的OnModelCreating方法,最常访问的代码优先 Fluent API。 下面的示例旨在演示如何使用流畅 api 完成各种任务,并允许你复制代码并对其进行自定义以适合你的模型,如果你希望查看可以按原样使用的模型,则在本文末尾提供。

模型范围内的设置Model-Wide Settings

默认架构(EF6 向前)Default Schema (EF6 onwards)

从 EF6 开始,可以对 DbModelBuilder 使用 HasDefaultSchema 方法,以指定要用于所有表、存储过程等的数据库架构。对于为其显式配置不同架构的任何对象,将重写此默认设置。

  1. modelBuilder.HasDefaultSchema("sales");

自定义约定(EF6)Custom Conventions (EF6 onwards)

从 EF6 开始,你可以创建自己的约定来补充 Code First 中包含的约定。 有关更多详细信息,请参阅自定义 Code First 约定

属性映射Property Mapping

属性方法用于为属于实体或复杂类型的每个属性配置特性。 属性方法用于获取给定属性的配置对象。 配置对象上的选项特定于正在配置的类型;IsUnicode 仅可用于字符串属性(例如)。

配置主键Configuring a Primary Key

主键的实体框架约定为:

  1. 类定义的属性的名称为 “ID” 或 “Id”
  2. 或类名后跟 “ID” 或 “Id”

若要将属性显式设置为主键,可以使用 HasKey 方法。 在下面的示例中,HasKey 方法用于在 OfficeAssignment 类型上配置 InstructorID 主键。

  1. modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

配置组合主键Configuring a Composite Primary Key

下面的示例将 DepartmentID 和 Name 属性配置为部门类型的复合主键。

  1. modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });

关闭数字主键的标识Switching off Identity for Numeric Primary Keys

下面的示例将 DepartmentID 属性设置为 System.componentmodel,以指示数据库将不会生成此值。

  1. modelBuilder.Entity<Department>().Property(t => t.DepartmentID)
  2. .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

指定属性的最大长度Specifying the Maximum Length on a Property

在下面的示例中,Name 属性的长度不应超过50个字符。 如果将值设置为长度超过50个字符,则会收到DbEntityValidationException异常。 如果 Code First 通过此模型创建数据库,则还会将 “名称” 列的最大长度设置为50个字符。

  1. modelBuilder.Entity<Department>().Property(t => t.Name).HasMaxLength(50);

将属性配置为必填Configuring the Property to be Required

在下面的示例中,Name 属性是必需的。 如果未指定名称,则会收到 DbEntityValidationException 异常。 如果 Code First 通过此模型创建数据库,则用于存储此属性的列通常不能为 null。

备注

在某些情况下,即使属性是必需的,也无法使数据库中的列不可为 null。 例如,对多个类型使用 TPH 继承战略数据时,会将其存储在一个表中。 如果派生的类型包含所需的属性,则列不能为 null,因为并不是层次结构中的所有类型都具有此属性。

  1. modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();

对一个或多个属性配置索引Configuring an Index on one or more properties

备注

Ef 6.1 仅往上-索引属性是在实体框架6.1 中引入的。 如果你使用的是早期版本,则本部分中的信息不适用。

流畅的 API 不支持创建索引,但你可以通过流畅的 API 使用对IndexAttribute的支持。 索引属性的处理方式是在模型中包含模型批注,然后在管道中的后续部分将其转换为数据库中的索引。 可以使用熟知的 API 手动添加这些相同的注释。

执行此操作的最简单方法是创建一个IndexAttribute实例,其中包含新索引的所有设置。 然后,你可以创建一个IndexAnnotation实例,该实例是一个 EF 特定类型,它将IndexAttribute设置转换为可存储在 EF 模型上的模型注释。 然后,可以将这些方法传递到熟知 API 上的HasColumnAnnotation方法,并指定该批注的名称索引

  1. modelBuilder
  2. .Entity<Department>()
  3. .Property(t => t.Name)
  4. .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));

有关IndexAttribute中可用设置的完整列表,请参阅Code First 数据批注索引部分。 这包括自定义索引名称、创建唯一索引以及创建多列索引。

可以通过将IndexAttribute数组传递到IndexAnnotation的构造函数,在单个属性上指定多个索引批注。

  1. modelBuilder
  2. .Entity<Department>()
  3. .Property(t => t.Name)
  4. .HasColumnAnnotation(
  5. "Index",
  6. new IndexAnnotation(new[]
  7. {
  8. new IndexAttribute("Index1"),
  9. new IndexAttribute("Index2") { IsUnique = true }
  10. })));

指定不将 CLR 属性映射到数据库中的列Specifying Not to Map a CLR Property to a Column in the Database

下面的示例演示如何指定 CLR 类型的属性未映射到数据库中的列。

  1. modelBuilder.Entity<Department>().Ignore(t => t.Budget);

将 CLR 属性映射到数据库中的特定列Mapping a CLR Property to a Specific Column in the Database

下面的示例将 Name CLR 属性映射到 DepartmentName 数据库列。

  1. modelBuilder.Entity<Department>()
  2. .Property(t => t.Name)
  3. .HasColumnName("DepartmentName");

重命名未在模型中定义的外键Renaming a Foreign Key That Is Not Defined in the Model

如果选择不在 CLR 类型上定义外键,但要指定它应在数据库中具有的名称,请执行以下操作:

  1. modelBuilder.Entity<Course>()
  2. .HasRequired(c => c.Department)
  3. .WithMany(t => t.Courses)
  4. .Map(m => m.MapKey("ChangedDepartmentID"));

配置字符串属性是否支持 Unicode 内容Configuring whether a String Property Supports Unicode Content

默认情况下,字符串为 Unicode (SQL Server 中为 nvarchar)。 可以使用 IsUnicode 方法来指定字符串应为 varchar 类型。

  1. modelBuilder.Entity<Department>()
  2. .Property(t => t.Name)
  3. .IsUnicode(false);

配置数据库列的数据类型Configuring the Data Type of a Database Column

HasColumnType方法允许映射到相同基本类型的不同表示形式。 使用此方法不能在运行时执行数据的任何转换。 请注意,IsUnicode 是将列设置为 varchar 的首选方式,因为它是数据库不可知的。

  1. modelBuilder.Entity<Department>()
  2. .Property(p => p.Name)
  3. .HasColumnType("varchar");

配置复杂类型的属性Configuring Properties on a Complex Type

可以通过两种方法来配置复杂类型的标量属性。

可以在 ComplexTypeConfiguration 上调用属性。

  1. modelBuilder.ComplexType<Details>()
  2. .Property(t => t.Location)
  3. .HasMaxLength(20);

你还可以使用点表示法访问复杂类型的属性。

  1. modelBuilder.Entity<OnsiteCourse>()
  2. .Property(t => t.Details.Location)
  3. .HasMaxLength(20);

配置要用作开放式并发标记的属性Configuring a Property to Be Used as an Optimistic Concurrency Token

若要指定实体中的属性表示并发标记,可以使用 ConcurrencyCheck 特性或 IsConcurrencyToken 方法。

  1. modelBuilder.Entity<OfficeAssignment>()
  2. .Property(t => t.Timestamp)
  3. .IsConcurrencyToken();

还可以使用 IsRowVersion 方法将属性配置为数据库中的行版本。 将属性设置为行版本会自动将它配置为开放式并发标记。

  1. modelBuilder.Entity<OfficeAssignment>()
  2. .Property(t => t.Timestamp)
  3. .IsRowVersion();

类型映射Type Mapping

指定类为复杂类型Specifying That a Class Is a Complex Type

按照约定,未指定主键的类型将被视为复杂类型。 在某些情况下,Code First 不会检测到复杂类型(例如,如果你有一个名为 ID 的属性,但你并不意味着它是主键)。 在这种情况下,可以使用 Fluent API 显式指定类型为复杂类型。

  1. modelBuilder.ComplexType<Details>();

指定不将 CLR 实体类型映射到数据库中的表Specifying Not to Map a CLR Entity Type to a Table in the Database

下面的示例演示如何将 CLR 类型从映射到数据库中的表。

  1. modelBuilder.Ignore<OnlineCourse>();

将实体类型映射到数据库中的特定表Mapping an Entity Type to a Specific Table in the Database

部门的所有属性都将映射到名为 t_ 部门的表中的列。

  1. modelBuilder.Entity<Department>()
  2. .ToTable("t_Department");

你还可以指定架构名称,如下所示:

  1. modelBuilder.Entity<Department>()
  2. .ToTable("t_Department", "school");

映射每个层次结构一个表(TPH)继承Mapping the Table-Per-Hierarchy (TPH) Inheritance

在 TPH 映射方案中,继承层次结构中的所有类型都映射到单个表。 鉴别器列用于标识每行的类型。 在 Code First 创建模型时,TPH 是参与继承层次结构的类型的默认策略。 默认情况下,鉴别器列将添加到名称为 “鉴别器” 的表中,层次结构中的每个类型的 CLR 类型名称将用于鉴别器值。 您可以使用 Fluent API 修改默认行为。

  1. modelBuilder.Entity<Course>()
  2. .Map<Course>(m => m.Requires("Type").HasValue("Course"))
  3. .Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));

映射每种类型一个表(TPT)继承Mapping the Table-Per-Type (TPT) Inheritance

在 TPT 映射方案中,所有类型都映射到单个表。 仅属于某个基类型或派生类型的属性存储在映射到该类型的一个表中。 映射到派生类型的表还存储将派生表与基表联接的外键。

  1. modelBuilder.Entity<Course>().ToTable("Course");
  2. modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");

映射每个具体的表类(TPC)继承Mapping the Table-Per-Concrete Class (TPC) Inheritance

在 TPC 映射方案中,层次结构中的所有非抽象类型都将映射到单个表。 映射到派生类的表与映射到数据库中的基类的表没有关系。 类的所有属性(包括继承的属性)都映射到对应表的列。

调用 MapInheritedProperties 方法来配置每个派生类型。 对于派生类,MapInheritedProperties 将从基类继承的所有属性重新映射到表中的新列。

备注

请注意,由于参与了 TPC 继承层次结构的表不共享主键,因此,如果数据库生成的值具有相同的标识种子,则在映射到子类的表中插入时,将会出现重复的实体键。 若要解决此问题,可以为每个表指定不同的初始种子值,或者在 primary key 属性上关闭 “标识”。 当使用 Code First 时,标识是整数键属性的默认值。

  1. modelBuilder.Entity<Course>()
  2. .Property(c => c.CourseID)
  3. .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
  4. modelBuilder.Entity<OnsiteCourse>().Map(m =>
  5. {
  6. m.MapInheritedProperties();
  7. m.ToTable("OnsiteCourse");
  8. });
  9. modelBuilder.Entity<OnlineCourse>().Map(m =>
  10. {
  11. m.MapInheritedProperties();
  12. m.ToTable("OnlineCourse");
  13. });

将实体类型的属性映射到数据库中的多个表(实体拆分)Mapping Properties of an Entity Type to Multiple Tables in the Database (Entity Splitting)

实体拆分允许将某个实体类型的属性分布到多个表中。 在下面的示例中,部门实体拆分为两个表:部门和 DepartmentDetails。 实体拆分对 Map 方法使用多个调用,以将属性的子集映射到特定的表。

  1. modelBuilder.Entity<Department>()
  2. .Map(m =>
  3. {
  4. m.Properties(t => new { t.DepartmentID, t.Name });
  5. m.ToTable("Department");
  6. })
  7. .Map(m =>
  8. {
  9. m.Properties(t => new { t.DepartmentID, t.Administrator, t.StartDate, t.Budget });
  10. m.ToTable("DepartmentDetails");
  11. });

将多个实体类型映射到数据库中的一个表(表拆分)Mapping Multiple Entity Types to One Table in the Database (Table Splitting)

下面的示例将共享主键的两个实体类型映射到一个表。

  1. modelBuilder.Entity<OfficeAssignment>()
  2. .HasKey(t => t.InstructorID);
  3. modelBuilder.Entity<Instructor>()
  4. .HasRequired(t => t.OfficeAssignment)
  5. .WithRequiredPrincipal(t => t.Instructor);
  6. modelBuilder.Entity<Instructor>().ToTable("Instructor");
  7. modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");

将实体类型映射到插入/更新/删除存储过程(EF6)Mapping an Entity Type to Insert/Update/Delete Stored Procedures (EF6 onwards)

从 EF6 开始,可以将实体映射为使用存储过程来执行插入更新和删除操作。 有关更多详细信息,请参阅Code First 插入/更新/删除存储过程

示例中使用的模型Model Used in Samples

以下 Code First 模型用于此页上的示例。

  1. using System.Data.Entity;
  2. using System.Data.Entity.ModelConfiguration.Conventions;
  3. // add a reference to System.ComponentModel.DataAnnotations DLL
  4. using System.ComponentModel.DataAnnotations;
  5. using System.Collections.Generic;
  6. using System;
  7. public class SchoolEntities : DbContext
  8. {
  9. public DbSet<Course> Courses { get; set; }
  10. public DbSet<Department> Departments { get; set; }
  11. public DbSet<Instructor> Instructors { get; set; }
  12. public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
  13. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  14. {
  15. // Configure Code First to ignore PluralizingTableName convention
  16. // If you keep this convention then the generated tables will have pluralized names.
  17. modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
  18. }
  19. }
  20. public class Department
  21. {
  22. public Department()
  23. {
  24. this.Courses = new HashSet<Course>();
  25. }
  26. // Primary key
  27. public int DepartmentID { get; set; }
  28. public string Name { get; set; }
  29. public decimal Budget { get; set; }
  30. public System.DateTime StartDate { get; set; }
  31. public int? Administrator { get; set; }
  32. // Navigation property
  33. public virtual ICollection<Course> Courses { get; private set; }
  34. }
  35. public class Course
  36. {
  37. public Course()
  38. {
  39. this.Instructors = new HashSet<Instructor>();
  40. }
  41. // Primary key
  42. public int CourseID { get; set; }
  43. public string Title { get; set; }
  44. public int Credits { get; set; }
  45. // Foreign key
  46. public int DepartmentID { get; set; }
  47. // Navigation properties
  48. public virtual Department Department { get; set; }
  49. public virtual ICollection<Instructor> Instructors { get; private set; }
  50. }
  51. public partial class OnlineCourse : Course
  52. {
  53. public string URL { get; set; }
  54. }
  55. public partial class OnsiteCourse : Course
  56. {
  57. public OnsiteCourse()
  58. {
  59. Details = new Details();
  60. }
  61. public Details Details { get; set; }
  62. }
  63. public class Details
  64. {
  65. public System.DateTime Time { get; set; }
  66. public string Location { get; set; }
  67. public string Days { get; set; }
  68. }
  69. public class Instructor
  70. {
  71. public Instructor()
  72. {
  73. this.Courses = new List<Course>();
  74. }
  75. // Primary key
  76. public int InstructorID { get; set; }
  77. public string LastName { get; set; }
  78. public string FirstName { get; set; }
  79. public System.DateTime HireDate { get; set; }
  80. // Navigation properties
  81. public virtual ICollection<Course> Courses { get; private set; }
  82. }
  83. public class OfficeAssignment
  84. {
  85. // Specifying InstructorID as a primary
  86. [Key()]
  87. public Int32 InstructorID { get; set; }
  88. public string Location { get; set; }
  89. // When Entity Framework sees Timestamp attribute
  90. // it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
  91. [Timestamp]
  92. public Byte[] Timestamp { get; set; }
  93. // Navigation property
  94. public virtual Instructor Instructor { get; set; }
  95. }