- 实体框架教程
- 实体框架 - 主页
- 实体框架 - 概述
- 实体框架 - 架构
- 实体 F - 环境设置
- 实体框架 - 数据库设置
- 实体框架 - 数据模型
- 实体框架-DbContext
- 实体框架 - 类型
- 实体框架 - 关系
- 实体框架 - 生命周期
- 实体 F - 代码优先方法
- 实体 F - 模型优先方法
- 实体 F - 数据库优先方法
- 实体框架 - DEV 方法
- 实体F——数据库操作
- 实体框架 - 并发
- 实体框架 - 事务
- 实体框架 - 视图
- 实体框架 - 索引
- 实体 F - 存储过程
- 实体 F - 断开连接的实体
- 实体 F - 表值函数
- 实体框架 - 本机 SQL
- 实体框架 - 枚举支持
- 实体F - 异步查询
- 实体框架 - 持久性
- 实体 F - 投影查询
- 实体 F - 命令记录
- 实体F——命令拦截
- 实体框架 - 空间数据类型
- 实体框架 - 继承
- 实体框架 - 迁移
- 实体框架 - 预加载
- 实体框架 - 延迟加载
- 实体框架 - 显式加载
- 实体框架 - 验证
- 实体框架 - 跟踪更改
- 实体框架 - 彩色实体
- 实体 F - 代码优先方法
- 实体框架 - 第一个示例
- 实体框架 - 数据注释
- 实体框架 - 流畅的 API
- 实体框架-种子数据库
- 实体 F - 代码优先迁移
- 实体 F - 多个 DbContext
- 实体 F - 嵌套实体类型
- 实体框架资源
- 实体框架 - 快速指南
- 实体框架 - 有用的资源
- 实体框架 - 讨论
实体框架 - 流畅的 API
Fluent API 是一种指定模型配置的高级方法,除了数据注释无法实现的一些更高级配置之外,它涵盖了数据注释可以执行的所有操作。数据注释和 Fluent API 可以一起使用,但 Code First 优先级为 Fluent API > 数据注释 > 默认约定。
Fluent API 是配置域类的另一种方法。
最常通过重写派生 DbContext 上的 OnModelCreating 方法来访问 Code First Fluent API。
Fluent API 提供了比 DataAnnotations 更多的配置功能。Fluent API 支持以下类型的映射。
在本章中,我们将继续使用一个简单的示例,其中包含 Student、Course 和 Enrollment 类以及一个名为 MyContext 的上下文类,如以下代码所示。
using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EFCodeFirstDemo { class Program { static void Main(string[] args) {} } public enum Grade { A, B, C, D, F } public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } public Grade? Grade { get; set; } public virtual Course Course { get; set; } public virtual Student Student { get; set; } } public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } public class Course { public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } public class MyContext : DbContext { public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } } }
要访问 Fluent API,您需要重写 DbContext 中的 OnModelCreating 方法。让我们看一个简单的示例,其中我们将学生表中的列名称从 FirstMidName 重命名为 FirstName,如以下代码所示。
public class MyContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName) .HasColumnName("FirstName");} public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } }
DbModelBuilder 用于将 CLR 类映射到数据库架构。它是主类,您可以在其上配置所有域类。这种以代码为中心的构建实体数据模型 (EDM) 的方法称为“代码优先”。
Fluent API 提供了许多重要的方法来配置实体及其属性,以覆盖各种 Code First 约定。以下是其中一些。
先生。没有。 | 方法名称和描述 |
---|---|
1 | 复杂类型<TComplexType> 在模型中将类型注册为复杂类型并返回可用于配置复杂类型的对象。同一类型可以多次调用该方法,以进行多行配置。 |
2 | 实体<TEntityType> 将实体类型注册为模型的一部分并返回可用于配置实体的对象。同一实体可以多次调用该方法来执行多行配置。 |
3 | HasKey<TKey> 配置此实体类型的主键属性。 |
4 | HasMany<TTargetEntity> 配置此实体类型的多个关系。 |
5 | 具有可选<TTargetEntity> 配置此实体类型的可选关系。无需指定此关系,实体类型的实例就可以保存到数据库中。数据库中的外键可以为空。 |
6 | HasRequired<TTargetEntity> 配置此实体类型所需的关系。除非指定此关系,否则实体类型的实例将无法保存到数据库中。数据库中的外键不可为空。 |
7 | 忽略<TProperty> 从模型中排除属性,以便它不会映射到数据库。(继承自 StructuralTypeConfiguration<TStructuralType>) |
8 | 属性<T> 配置在此类型上定义的结构体属性。(继承自 StructuralTypeConfiguration<TStructuralType>) |
9 | 到表(字符串) 配置此实体类型映射到的表名称。 |
Fluent API 允许您配置实体或其属性,无论您想要更改它们映射到数据库的方式还是它们彼此关联的方式。您可以使用配置影响各种各样的映射和建模。以下是 Fluent API 支持的主要映射类型 -
- 实体映射
- 属性映射
实体映射
实体映射只是一些简单的映射,它们会影响实体框架对类如何映射到数据库的理解。所有这些我们都在数据注释中讨论过,在这里我们将看到如何使用 Fluent API 来实现相同的事情。
因此,我们可以在上下文内部执行此操作,而不是进入域类来添加这些配置。
第一件事是重写 OnModelCreating 方法,该方法使模型构建器可以使用。
默认架构
生成数据库时默认架构为 dbo。您可以使用 DbModelBuilder 上的 HasDefaultSchema 方法来指定用于所有表、存储过程等的数据库架构。
让我们看一下以下应用管理架构的示例。
public class MyContext : DbContext { public MyContext() : base("name = MyContextDB") {} protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); } public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } }
将实体映射到表
按照默认约定,Code First 将使用上下文类中的 DbSet 属性名称(例如课程、注册和学生)创建数据库表。但是,如果您想要不同的表名称,则可以覆盖此约定,并可以提供与 DbSet 属性不同的表名称,如以下代码所示。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); //Map entity to table modelBuilder.Entity<Student>().ToTable("StudentData"); modelBuilder.Entity<Course>().ToTable("CourseDetail"); modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo"); }
生成数据库后,您将看到 OnModelCreating 方法中指定的表名称。
实体拆分(将实体映射到多个表)
实体拆分允许您将来自多个表的数据合并到一个类中,并且它只能用于它们之间具有一对一关系的表。让我们看一下下面的示例,其中学生信息映射到两个表中。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); //Map entity to table modelBuilder.Entity<Student>().Map(sd ⇒ { sd.Properties(p ⇒ new { p.ID, p.FirstMidName, p.LastName }); sd.ToTable("StudentData"); }) .Map(si ⇒ { si.Properties(p ⇒ new { p.ID, p.EnrollmentDate }); si.ToTable("StudentEnrollmentInfo"); }); modelBuilder.Entity<Course>().ToTable("CourseDetail"); modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo"); }
在上面的代码中,您可以看到,通过使用 Map 方法将一些属性映射到 StudentData 表,并将一些属性映射到 StudentEnrollmentInfo 表,Student 实体被拆分为以下两个表。
StudentData - 包含学生名字和姓氏。
StudentEnrollmentInfo - 包含注册日期。
生成数据库后,您会在数据库中看到以下表格,如下图所示。
属性映射
Property 方法用于为属于实体或复杂类型的每个属性配置属性。Property 方法用于获取给定属性的配置对象。您还可以使用 Fluent API 映射和配置域类的属性。
配置主键
主键的默认约定是 -
- 类定义了一个名为“ID”或“Id”的属性
- 类名后跟“ID”或“Id”
如果您的班级不遵循主键的默认约定,如以下 Student 班级代码所示 -
public class Student { public int StdntID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } }
然后,要将属性显式设置为主键,可以使用 HasKey 方法,如以下代码所示 -
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); // Configure Primary Key modelBuilder.Entity<Student>().HasKey<int>(s ⇒ s.StdntID); }
配置列
在实体框架中,默认情况下 Code First 将为具有相同名称、顺序和数据类型的属性创建一个列。但您也可以覆盖此约定,如以下代码所示。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); //Configure EnrollmentDate Column modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate) .HasColumnName("EnDate") .HasColumnType("DateTime") .HasColumnOrder(2); }
配置 MaxLength 属性
在以下示例中,课程标题属性不应超过 24 个字符。当用户指定的值超过 24 个字符时,用户将收到 DbEntityValidationException 异常。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); modelBuilder.Entity<Course>().Property(p ⇒ p.Title).HasMaxLength(24); }
配置 Null 或 NotNull 属性
在以下示例中,课程标题属性是必需的,因此使用 IsRequired 方法创建 NotNull 列。同样,Student EnrollmentDate 是可选的,因此我们将使用 IsOptional 方法允许此列中存在空值,如以下代码所示。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); modelBuilder.Entity<Course>().Property(p ⇒ p.Title).IsRequired(); modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional(); //modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName) //.HasColumnName("FirstName"); }
配置关系
在数据库上下文中,关系是两个关系数据库表之间存在的一种情况,其中一个表具有引用另一个表的主键的外键。使用 Code First 时,您可以通过定义域 CLR 类来定义模型。默认情况下,实体框架使用 Code First 约定将类映射到数据库架构。
如果您使用 Code First 命名约定,则在大多数情况下,您可以依靠 Code First 根据外键和导航属性设置表之间的关系。
如果它们不符合这些约定,您还可以使用一些配置来影响类之间的关系,以及当您在 Code First 中添加配置时如何在数据库中实现这些关系。
其中一些在数据注释中可用,您可以使用 Fluent API 应用一些更复杂的注释。
配置一对一关系
当您在模型中定义一对一关系时,您可以在每个类中使用引用导航属性。在数据库中,两个表在关系的任一侧只能有一条记录。每个主键值仅与相关表中的一条记录(或没有记录)相关。
如果两个相关列都是主键或具有唯一约束,则会创建一对一关系。
在一对一关系中,主键还充当外键,并且两个表都没有单独的外键列。
这种类型的关系并不常见,因为以这种方式相关的大多数信息都在一张表中。
让我们看一下下面的示例,我们将在模型中添加另一个类以创建一对一的关系。
public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual StudentLogIn StudentLogIn { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } public class StudentLogIn { [Key, ForeignKey("Student")] public int ID { get; set; } public string EmailID { get; set; } public string Password { get; set; } public virtual Student Student { get; set; } }
正如您在上面的代码中看到的,Key 和foreignKey 属性用于StudentLogIn 类中的ID 属性,以便将其标记为主键和外键。
要使用 Fluent API 配置 Student 和 StudentLogIn 之间的一对零或一对关系,您需要重写 OnModelCreating 方法,如以下代码所示。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); // Configure ID as PK for StudentLogIn modelBuilder.Entity<StudentLogIn>() .HasKey(s ⇒ s.ID); // Configure ID as FK for StudentLogIn modelBuilder.Entity<Student>() .HasOptional(s ⇒ s.StudentLogIn) //StudentLogIn is optional .WithRequired(t ⇒ t.Student); // Create inverse relationship }
在大多数情况下,实体框架可以推断哪个类型是依赖关系,哪个类型是关系中的主体。但是,当关系的两端都是必需的或两端都是可选的时,实体框架无法识别依赖项和主体。当关系的两端都需要时,可以使用 HasRequired,如以下代码所示。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); // Configure ID as PK for StudentLogIn modelBuilder.Entity<StudentLogIn>() .HasKey(s ⇒ s.ID); // Configure ID as FK for StudentLogIn modelBuilder.Entity<Student>() .HasRequired(r ⇒ r.Student) .WithOptional(s ⇒ s.StudentLogIn); }
生成数据库后,您将看到已创建关系,如下图所示。
配置一对多关系
主键表仅包含一条记录,该记录与相关表中的无记录、一条或多条记录相关。这是最常用的关系类型。
在这种类型的关系中,表 A 中的一行可以在表 B 中具有多个匹配行,但表 B 中的一行在表 A 中只能有一个匹配行。
外键在代表关系多端的表上定义。
例如,上图中的Student和Enrollment表是一对多的关系,每个学生可能有多个学号,但每个学号只属于一个学生。
下面是具有一对多关系的 Student 和 Enrollment,但 Enrollment 表中的外键不遵循默认的 Code First 约定。
public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } //StdntID is not following code first conventions name public int StdntID { get; set; } public Grade? Grade { get; set; } public virtual Course Course { get; set; } public virtual Student Student { get; set; } } public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual StudentLogIn StudentLogIn { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } }
在这种情况下,要使用 Fluent API 配置一对多关系,您需要使用 HasForeignKey 方法,如以下代码所示。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); //Configure FK for one-to-many relationship modelBuilder.Entity<Enrollment>() .HasRequired<Student>(s ⇒ s.Student) .WithMany(t ⇒ t.Enrollments) .HasForeignKey(u ⇒ u.StdntID); }
生成数据库后,您将看到已创建关系,如下图所示。
在上面的示例中,HasRequired 方法指定 Student 导航属性必须为 Null。因此,每次添加或更新注册时,您都必须为学生分配注册实体。为了处理这个问题,我们需要使用 HasOptional 方法而不是 HasRequired 方法。
配置多对多关系
两个表中的每条记录都可以与另一个表中任意数量的记录(或没有记录)相关。
您可以通过定义第三个表(称为联结表)来创建此类关系,该表的主键由表 A 和表 B 的外键组成。
例如,Student 表和 Course 表具有多对多关系。
以下是 Student 和 Course 类,其中 Student 和 Course 具有多对多关系,因为这两个类都具有作为集合的导航属性 Students 和 Courses。换句话说,一个实体有另一个实体集合。
public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection<Course> Courses { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } public class Course { public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual ICollection<Student> Students { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } }
要配置Student和Course之间的多对多关系,您可以使用Fluent API,如以下代码所示。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); // Configure many-to-many relationship modelBuilder.Entity<Student>() .HasMany(s ⇒ s.Courses) .WithMany(s ⇒ s.Students); }
默认的 Code First 约定用于在生成数据库时创建连接表。因此,将使用 Course_CourseID 和 Student_ID 列创建 StudentCourses 表,如下图所示。
如果要指定连接表名称和表中的列名称,则需要使用 Map 方法进行额外配置。
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Configure default schema modelBuilder.HasDefaultSchema("Admin"); // Configure many-to-many relationship modelBuilder.Entity<Student>() .HasMany(s ⇒ s.Courses) .WithMany(s ⇒ s.Students) .Map(m ⇒ { m.ToTable("StudentCoursesTable"); m.MapLeftKey("StudentID"); m.MapRightKey("CourseID"); }); }
您可以看到,当生成数据库时,表和列名称是按照上面代码中指定的方式创建的。
我们建议您逐步执行上述示例,以便更好地理解。