EF CodeFirst系列(4)---FluentApi
FluentApi总结
1.FluentApi简介
EF中的FluentApi作用是通过配置领域类来覆盖默认的约定。在EF中,我们通过DbModelBuilder类来使用FluentApi,它的功能比数据注释属性更强大。
使用FluentApi时,我们在context类的OnModelCreating()方法中重写配置项,一个栗子:
public class SchoolContext: DbContext { public DbSet<Student> Students { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Write Fluent API configurations here } }
我们可以把FluentApi和数据注释属性一起使用,当FluentApi和数据注释属性都配置了同一个项时,采用FluentApi中的配置。
在EF6中FluentApi可以配置领域类的以下几个方面,下表也列出了一些常用的FluentApi方法及其作用:
配置 | Fluent API 方法 | 作用 |
---|---|---|
架构相关配置 | HasDefaultSchema() | 数据库的默认架构 |
ComplexType() | 把一个类配置为复杂类型 | |
实体相关配置 | HasIndex() | 实体的的索引 |
HasKey() | 实体的主键(可其实现复合主键,[Key]在EF core中不能实现复合主键) | |
HasMany() | 1对多的或者 多对多关系 | |
HasOptional() | 一个可选的关系,这样配置会在数据库中生成一个可空的外键 | |
HasRequired() | 一个必有的关系,这样配置会在数据库中生成一个不能为空的外键 | |
Ignore() | 实体或者实体的属性不映射到数据库 | |
Map() | 设置一些优先的配置 | |
MapToStoredProcedures() | 实体的CUD操作使用存储过程 | |
ToTable() | 为实体设置表名 | |
属性相关配置 | HasColumnAnnotation() | 给属性设置注释 |
IsRequired() | 在调用SaveChanges()方法时,属性不能为空 | |
IsOptional() | 可选的,在数据库生成可空的列 | |
HasParameterName() | 配置用于该属性的存储过程的参数名 | |
HasDatabaseGeneratedOption() | 配置数据库中对应列的值怎样生成的,如计算,自增等 | |
HasColumnOrder() | 配置数据库中对应列的排列顺序 | |
HasColumnType() | 配置数据库中对应列的数据类型 | |
HasColumnName() | 配置数据库中对应列的列名 | |
IsConcurrencyToken() | 配置数据库中对应列用于乐观并发检测 |
2.实体相关配置
1.实体简单配置
直接上栗子:
我们新建一个EF6Demo的控制台应用程序,添加Student和Grade实体,以及上下文类SchoolContext,代码如下:
//学生类 public class Student { public int StudentId { get; set; } public string StudentName { get; set; } public string StudentNo { get; set; } public virtual Grade Grade{get;set;} } //年级类 public class Grade { public int GradeId { get; set; } public string GradeName { get; set; } public virtual ICollection<Student> Students { get; set; } } //上下文类 public class SchoolContext:DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet <Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema("Admin");//添加默认架构名 modelBuilder.Entity<Student>().ToTable("StudentInfo"); modelBuilder.Entity<Grade>().ToTable("GradeInfo","NewAdmin");//设置表名和架构 } }
在Main函数中执行代码:
class Program { static void Main(string[] args) { using (SchoolContext context=new SchoolContext()) { context.Students.Add(new Student() { StudentId = 1, StudentName = "Jack" }); context.SaveChanges(); } } }
这时在内置的SqlServer中生成数据库,如下图所示,我们看到Student表名为StudentInfo,架构是Admin;Grade表名是GradeInfo,架构是NewAdmin,覆盖了默认的约定(默认表名为dbo.Students和dbo.Grades)
2.实体映射到多张表
有时候我们希望一个实体的属性分在两种表中,那么该怎么配置呢?还用上边的栗子,我们把学生的姓名和Id存在一张表,学号和Id放在另一张表中,代码如下:
public class SchoolContext:DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet <Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Map(m => { //配置第一张表,包含学生Id和学生姓名 m.Properties(p => new { p.StudentId, p.StudentName }); m.ToTable("StudentInfo"); }).Map(m => { //配置第二张表,包含学生Id和学生学号 m.Properties(p => new { p.StudentId, p.StudentNo }); m.ToTable("StudentInfo2"); }); //配置年级表名 modelBuilder.Entity<Grade>().ToTable("GradeInfo"); } }
运行一下Main函数,生成了新的数据库,如下所示:
我们看到,通过Map()方法,我们把Student实体的属性被分在了两个表中。modelBuilder.Entity<T>()方法返回的是一个EntityTypeConfiguration<T>类型,Map()方法的参数是一个委托类型,委托的输入参数是EntityMappingConfiguration的实例。我们可以自定义一个委托来实现配置,下边的代码运行后生成的数据库和和上边一样:
public class SchoolContext : DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet<Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //先定义一个Action委托备用,委托的输入参数是一个实体映射配置(EntityMappingConfiguration)的实例 Action<EntityMappingConfiguration<Student>> studentMapping = m => { m.Properties(p => new { p.StudentId, p.StudentNo }); m.ToTable("StudentInfo2"); }; modelBuilder.Entity<Student>() //第一张表Map()方法参数是delegate形式委托 .Map(delegate (EntityMappingConfiguration<Student> studentConfig) { //map参数是lambda表达式 studentConfig.Properties(p => new { p.StudentId, p.StudentName }); studentConfig.ToTable("StudentInfo"); }) //第二张表Map()方法参数是Action委托 .Map(studentMapping); modelBuilder.Entity<Grade>().ToTable("GradeInfo"); } }
3.属性相关配置
属性的配置比较简单,这里简单总结了主键,列基本属性,是否可空,数据长度,高并发的配置。
一个栗子:
public class Student { public int StudentKey { get; set; }//主键 public string StudentName { get; set; }//姓名 public DateTime DateOfBirth { get; set; }//生日 public byte[] Photo { get; set; }//照片 public decimal Height { get; set; }//身高 public float Weight { get; set; }//体重 public Grade Grade{ get; set; }//年级 } public class Grade { public int GradeKey { get; set; }//主键 public string GradeName { get; set; }//年级名 public ICollection<Student> Students { get; set; } }
使用FluentApi对领域类做了以下配置:
public class SchoolContext : DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet<Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //设置默认架构 modelBuilder.HasDefaultSchema("Admin"); //设置主键 modelBuilder.Entity<Student>().HasKey<int>(s => s.StudentKey); //设置不映射的属性 modelBuilder.Entity<Student>().Ignore(s => s.Height); //设置DateOfBirth modelBuilder.Entity<Student>().Property(p => p.DateOfBirth) .HasColumnName("birthday") //列名为birthday .HasColumnType("datetime2") //数据类型是datetime类型 .HasColumnOrder(3) //顺序编号是3 .IsOptional(); //可以为null //设置姓名 modelBuilder.Entity<Student>().Property(s => s.StudentName) .HasMaxLength(20) //最长20 .IsRequired() //不能为null .IsConcurrencyToken(); //用于乐观并发检测,delete或者update时,这个属性添加到where上判断是否并发 } }
执行程序后生成的数据库如下: