Code First约定-Fluent API配置
使用Fluent API 配置/映射属性和类型
简介
通常通过重写派生DbContext 上的OnModelCreating 方法来访问Code First Fluent API。以下示例旨在显示如何使用 Fluent API 执行各种任务,您可以将代码复制出来并进行自定义,使之适用于您的模型。
属性映射
Property 方法用于为每个属于实体或复杂类型的属性配置特性。Property 方法用于获取给定属性的配置对象。配置对象上的选项特定于要配置的类型;例如,IsUnicode 只能用于字符串属性。
配置主键
要显式将某个属性设置为主键,可使用 HasKey 方法。在以下示例中,使用了 HasKey 方法对 OfficeAssignment 类型配置 InstructorID 主键。
modelBuilder.Entity<OfficeAssignment>().HasKey(t =>t.InstructorID);
配置组合主键
以下示例配置要作为Department 类型的组合主键的DepartmentID 和 Name 属性。
modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });
关闭数值主键的标识
以下示例将DepartmentID 属性设置为System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None,以指示该值不由数据库生成。
modelBuilder.Entity<Department>().Property(t =>t.DepartmentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
指定属性的最大长度
在以下示例中,Name属性不应超过 50 个字符。如果其值超过 50 个字符,则出现 DbEntityValidationException 异常。如果 Code First 基于此模型创建数据库,它还会将 Name 列的最大长度设置为50 个字符。
modelBuilder.Entity<Department>().Property(t =>t.Name).HasMaxLength(50);
将属性配置为必需
在下面的示例中,Name属性是必需的。如果不指定 Name,则出现 DbEntityValidationException 异常。如果 Code First 基于此模型创建数据库,则用于存储此属性的列将不可为空。
modelBuilder.Entity<Department>().Property(t =>t.Name).IsRequired();
指定不将CLR 属性映射到数据库中的列
以下示例显示如何指定CLR 类型的属性不映射到数据库中的列。
modelBuilder.Entity<Department>().Ignore(t => t.Budget);
将CLR 属性映射到数据库中的特定列
以下示例将Name CLR 属性映射到DepartmentName 数据库列。
modelBuilder.Entity<Department>().Property(t =>t.Name).HasColumnName("DepartmentName");
重命名模型中未定义的外键
如果您选择不对CLR 类型定义外键,但希望指定它在数据库中应使用的名称,请编码如下:
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(t => t.Courses)
.Map(m => m.MapKey("ChangedDepartmentID"));
配置字符串属性是否支持Unicode 内容
默认情况下,字符串为Unicode(SQLServer 中的nvarchar)。您可以使用IsUnicode 方法指定字符串应为varchar 类型。
modelBuilder.Entity<Department>()
.Property(t => t.Name)
.IsUnicode(false);
配置数据库列的数据类型
HasColumnType 方法支持映射到相同基本类型的不同表示。使用此方法并不支持在运行时执行任何数据转换。请注意,IsUnicode 是将列设置为 varchar 的首选方法,因为它与数据库无关。
modelBuilder.Entity<Department>()
.Property(p => p.Name)
.HasColumnType("varchar");
配置复杂类型的属性
对复杂类型配置标量属性有两种方法。
可以对ComplexTypeConfiguration 调用Property。
modelBuilder.ComplexType<Details>()
.Property(t => t.Location)
.HasMaxLength(20);
也可以使用点表示法访问复杂类型的属性。
modelBuilder.Entity<OnsiteCourse>()
.Property(t => t.Details.Location)
.HasMaxLength(20);
将属性配置为用作乐观并发令牌
要指定实体中的某个属性表示并发令牌,可使用 ConcurrencyCheck 特性或 IsConcurrencyToken 方法。
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsConcurrencyToken();
也可以使用IsRowVersion 方法将属性配置为数据库中的行版本。将属性设置为行版本会自动将它配置为乐观并发令牌。
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsRowVersion();
类型映射
将类指定为复杂类型
按约定,没有指定主键的类型将被视为复杂类型。在一些情况下,Code First 不会检测复杂类型(例如,如果您有名为“ID”的属性,但不想将它用作主键)。在此类情况下,您将使用 Fluent API 显式指定某类型是复杂类型。
modelBuilder.ComplexType<Details>();
指定不将CLR 实体类型映射到数据库中的表
以下示例显示如何排除一个 CLR 类型,使之不映射到数据库中的表。
modelBuilder.Ignore<OnlineCourse>();
将CLR 实体类型映射到数据库中的特定表
Department 的所有属性都将映射到名为 t_ Department 的表中的列。
modelBuilder.Entity<Department>().ToTable("t_Department");
您也可以这样指定架构名称:
modelBuilder.Entity<Department>().ToTable("t_Department", "school");
映射“每个层次结构一张表(TPH)”继承
在 TPH 映射情形下,继承层次结构中的所有类型都将映射到同一个表。鉴别器列用于标识每行的类型。使用 Code First 创建模型时,TPH 参与继承层次结构的类型所用的默认策略。默认情况下,鉴别器列将添加到名为“Discriminator”的表,且层次结构中每个类型的 CLR 类型名称都将用作鉴别器值。可以使用 Fluent API 修改默认行为。
modelBuilder.Entity<Course>()
.Map<Course>(m=> m.Requires("Type").HasValue("Course"))
.Map<OnsiteCourse>(m=> m.Requires("Type").HasValue("OnsiteCourse"));
映射“每个类型一张表(TPT)”继承
在 TPT 映射情形下,所有类型分别映射到不同的表。仅属于某个基类型或派生类型的属性存储在映射到该类型的一个表中。映射到派生类型的表还会存储一个将派生表与基表联接的外键。
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");
映射“每个具体类一张表(TPC)”继承
在 TPC 映射情形下,层次结构中的所有非抽象类型分别映射到不同的表。映射到派生类的表与映射到数据库中基类的表并无关系。类的所有属性(包括继承属性)都将映射到相应表的列。
调用MapInheritedProperties 方法来配置每个派生类型。MapInheritedProperties 将继承自基类的所有属性重新映射到派生类的表中的新列。
注意:因为属于TPC 继承层次结构的表并不使用同一个主键,因此,如果您让数据库生成的值具有相同标识种子,则在映射到子类的表中执行插入操作时,会产生重复的实体键。要解决此问题,可以为每个表指定不同的初始种子值,或关闭主键属性的标识。当使用 Code First 时,标识就是整数键属性的默认值。
modelBuilder.Entity<Course>()
.Property(c => c.CourseID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<OnsiteCourse>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("OnsiteCourse");
});
modelBuilder.Entity<OnlineCourse>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("OnlineCourse");
});
将实体类型的CLR 属性映射到数据库中的多个表(实体拆分)
实体拆分允许一个实体类型的属性分散在多个表中。在以下示例中,Department 实体拆分到两个表中:Department 和DepartmentDetails。实体拆分通过多次调用 Map 方法将一部分属性映射到特定表。
modelBuilder.Entity<Department>()
.Map(m=>
{
m.Properties(t => new{ t.DepartmentID, t.Name });
m.ToTable("Department");
})
.Map(m=>
{
m.Properties(t=> new { t.DepartmentID, t.Administrator,t.StartDate, t.Budget });
m.ToTable("DepartmentDetails");
});
将多个实体类型映射到数据库中的一个表(表拆分)
以下示例将使用同一个主键的两个实体类型映射到同一个表。
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal(t =>t.Instructor);
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");
使用FluentAPI配置关系
简介
使用FluentAPI配置关系的时候,首先要获得一个EntityTypeConfiguration实例,然后使用其上的HasRequired, HasOptional或者 HasMany方法来指定当前实体参与的关系类型。HasRequired 和HasOptional方法需要一个lambda表达式来指定一个导航属性,HasMany方法需要一个lambda表达式指定一个集合导航属性。然后可以使用WithRequired, WithOptional和WithMany方法来指定反向导航属性,这些方法有不带参数的重载用来指定单向导航。
之后还可以使用HasForeignKey方法来指定外键属性。
配置【必须-可选】关系(1-0..1)
OfficeAssignment的键属性不符合命名约定,所以需要我们显式指定。下面的关系表明,OfficeAssignment的Instructor必须存在,但是Instructor的OfficeAssignment不是必须存在的。
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
// Map one-to-zero or one relationship
modelBuilder.Entity<OfficeAssignment>()
.HasRequired(t => t.Instructor)
.WithOptional(t => t.OfficeAssignment);
配置两端都是必须的关系(1-1)
大多数情况下,EF都能推断哪一个类型是依赖项或者是主体项。然而当关系的两端都是必须的或者都是可选的,那么EF就不能识别依赖项或者是主体项。如果关系两端都是必须的,那么在HasRequired方法后使用WithRequiredPrincipal或者WithRequiredDependent来确定主体。如果关系两端都是可选的,那么在HasRequired方法后使用WithOptionalPrincipal和WithOptionalDependent。
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal(t => t.Instructor);
配置多对多关系
下面的代码配置了一个多对多关系,CodeFirst会使用命名约定来创建连接表,命名约定会使用Course_CourseID 和 Instructor_InstructorID作为连接表的列。
modelBuilder.Entity<Course>()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses);
如果想指定连接表的表名和列名,需要使用Map方法,如下:
modelBuilder.Entity<Course>()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses)
.Map(m =>
{
m.ToTable("CourseInstructor");
m.MapLeftKey("CourseID");
m.MapRightKey("InstructorID");
});
配置单向导航属
所谓单向导航属性指的是只在关系的一端定义了导航属性。按照约定,CodeFirst将单向导航理解为一对多关系,如果需要一对一的单向导航属性,需要使用如下方法:
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal();
启用级联删除
使用WillCascadeOnDelete方法来配置关系是否允许级联删除。如果外键是不可空的,CodeFirst默认会设置级联删除;否则,不会设置级联删除,当主体被删除后,外键将会被置空。
可以使用如下代码移除此约定:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
下面的代码片段配置为外键不能为空,而且禁用了级联删除。
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);
配置组合外键
下面的代码配置了组合外键
modelBuilder.Entity<Department>()
.HasKey(d => new{ d.DepartmentID, d.Name });
// Composite foreign key
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });
配置不符合命名约定的外键属性
SomeDepartmentID属性不符合外键命名约定,需要使用如下方法将其设置为外键属性:
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(c =>c.SomeDepartmentID);