EntityFramework Code-First 简易教程(七)-------领域类配置之Fluent API

Fluent API配置:

前面我们已经了解到使用DataAnotations特性来覆写Code-First默认约定,现在我们来学习Fluent API。

Fluent API是另一种配置领域类的方法,它比DataAnnotations特性提供更多的配置方法,下表是Fluent API支持的类型映射。

映射种类配置数据库
模型(Model-wide)映射
  • 设置默认架构
  • 设置自定义约定
实体(Entity)映射
  • 设置单表或多表和设置架构
  • 设置复杂类型
  • 设置继承层次结构
属性(Property)映射
  • 设置列,列名、类型、是否可空、大小、排序
  • 设置并发列
  • 设置外键列
  • 配置关系

下面,我们开始使用Fluent API来配置领域类。

我们首先创建Student和Standard两个领域类,同样也创建出DbContext类,DbContext类中有个OnModelCreating方法,这里我们在它的继承类中把它覆写出来。

代码如下:

public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure domain classes using modelBuilder here

        base.OnModelCreating(modelBuilder);
    }
}

使用Fluent API配置领域类时,所有的配置代码都要写在OnModelCreating方法里面,所有的领域类都可以在这个方法里面写上他们的初始化代码。程序初始化的时候,DataAnnotation和Fluent API的优先级是:Fluent API > DataAnnotations > 默认约定。

DbModelBuilder类包含了重要的用于配置的属性和方法,更多详情请翻阅MSDN文档。

接下来我们详细讲一些常用的Fluent API配置方法。

 

一、EntityTypeConfiguration类:

EntityTypeConfiguration类在Fluent API中有着重要的作用,它提供了一系列重要的方法和属性来覆写默认约定。

EntityTypeConfiguration类可以运行DbModelBuilder类的Entity<TEntity>()方法获得,如下所示:

EntityTypeConfiguration

EntityTypeConfiguration有下面这些重要的方法:

方法名返回类型描述
HasKey<TKey> EntityTypeConfiguration 为这个实体类型配置主键
HasMany<TTargetEntity> ManyNavigationPropertyConfiguration 为实体类型配置多对多关系
HasOptional<TTargetEntity> OptionalNavigationPropertyConfiguration

为实体类配置可选关系。没有指定关系的实体类型的实例会被存入数据库。数据库里外键可为空(nullable)。

HasRequired<TTargetEntity> RequiredNavigationPropertyConfiguration

为实体类型配置必须关系。除非关系确定,否则实体类型的实例不能存入数据库。数据库中的外键将不能为空(non-nullable)。

Ignore<TProperty> Void

从模型中排除一个属性,这个属性将不会映射到数据库。

Map EntityTypeConfiguration

允许高级配置有关该实体类型映射到数据库模式。

Property<T> StructuralTypeConfiguration

配置一个定义了这种类型的结构属性

ToTable Void

配置实体类型映射的表名

可以访问MSDN查询更多关于 EntityTypeConfiguration 类的信息。

 

下面介绍怎么用Fluent API配置实体类

二、Entity Mappings:

我们继续使用在学校应用里面的Student和Standard两个领域类

代码如下:

public class Student
{
    public Student()
    { 
        
    }
    public int StudentID { 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 Standard Standard { get; set; }
}
    
public class Standard
{
    public Standard()
    { 
        
    }
    public int StandardId { get; set; }
    public string StandardName { get; set; }
    
    public ICollection<Student> Students { get; set; }
   
    }
}

 

配置默认架构:

当我们想为一组特殊的表设置一个不同的构架时,可以使用HasDefaultSchema方法:

public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure default schema
        modelBuilder.HasDefaultSchema("Admin");
    }
}

 

映射实体到表:

Code-First将会为context类中的所有DbSet属性创建数据库表,表名就是属性名,比如上面的Students和Standards。我们也可以给表配置一个不同于DbSet属性名的表名,如下代码所示:

namespace CodeFirst_FluentAPI_Tutorials
{
        
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
                //Configure default schema
            modelBuilder.HasDefaultSchema("Admin");
                    
            //Map entity to table
            modelBuilder.Entity<Student>().ToTable("StudentInfo");
            modelBuilder.Entity<Standard>().ToTable("StandardInfo","dbo");

        }
    }
}

如上代码,我们配置表名的ToTable方法是接在Entity<TEntity>()方法后面,上面代码已经把映射Student实体的表名改成StudentInfo,把映射Standard实体的表名改成了StandardInfo,特别留意的是,虽然我们把默认的架构名改成了Admin,但是Standard实体又特别指定了dbo架构,所以生成的数据库如下所示:

table mapping with fluent api Entity Framework code-first

映射实体到多张表:

下面的代码演示了怎样把Student实体映射到数据库的多张表:

namespace CodeFirst_FluentAPI_Tutorials
{
        
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Map(m =>
            {
                m.Properties(p => new { p.StudentId, p.StudentName});
                m.ToTable("StudentInfo");

            }).Map(m => {
                m.Properties(p => new { p.StudentId, p.Height, p.Weight, p.Photo, p.DateOfBirth});
                m.ToTable("StudentInfoDetail");

            });

            modelBuilder.Entity<Standard>().ToTable("StandardInfo");

        }
    }
}

如上所示,使用Map()方法可以映射Student实体的一些属性到StudentInfo表中,另一些属性到StudentInfoDetail表中,我们把Student实体分裂成了两张表,生成的数据库如下所示:

multiple table mapping with fluent api Entity Framework code-first

Map method need the delegate method as a parameter. You can pass  or in Map method, as shown below.

Map方法的传入参数是一个委托,具体可以参考 Action delegate 和 lambda expression

完成代码如下:

using System.Data.Entity.ModelConfiguration.Configuration;

namespace CodeFirst_FluentAPI_Tutorials
{
        
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Map(delegate(EntityMappingConfiguration<Student> studentConfig)
            {
                studentConfig.Properties(p => new { p.StudentId, p.StudentName });
                studentConfig.ToTable("StudentInfo");
            });

            Action<EntityMappingConfiguration<Student>> studentMapping = m =>
            {
                m.Properties(p => new { p.StudentId, p.Height, p.Weight, p.Photo, p.DateOfBirth });
                m.ToTable("StudentInfoDetail");
            };
            modelBuilder.Entity<Student>().Map(studentMapping);

            modelBuilder.Entity<Standard>().ToTable("StandardInfo");

        }
    }
}
    
View Code

 

 

三、Property Mappings:

下面我们来介绍怎样用Fluent API配置实体类的属性

我们仍然使用学校的例子,如下两个Student和Standard领域类:

public class Student
{
    public 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 Standard Standard { get; set; }
}
    
public class Standard
{
    public Standard()
    { 
        
    }
    public int StandardKey { get; set; }
    public string StandardName { get; set; }
    
    public ICollection<Student> Students { get; set; }
   
    }
}

 

配置主键和混合主键:

上面的两个领域类,不能依据Code-First默认约定生成主键,因为它们没有Id或者{类名}+Id的属性,所以这里使用EntityTypeConfiguration类里的HasHey()方法来创建主键。

注意,modelBuilder.Entity<TEntity>()返回的是EntityTypeConfiguration对象。

public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure primary key
        modelBuilder.Entity<Student>().HasKey<int>(s => s.StudentKey);
        modelBuilder.Entity<Standard>().HasKey<int>(s => s.StandardKey);

        //Configure composite primary key
        modelBuilder.Entity<Student>().HasKey<int>(s => new { s.StudentKey, s.StudentName }); 
    }
}

 

配置列名、数据类型和排序:

Code-First默认约定以属性名为列名,下面的代码覆写了这一约定:

public class SchoolContext: DbContext 
{
    public SchoolDBContext(): base() 
    {
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure Column
        modelBuilder.Entity<Student>()
                    .Property(p => p.DateOfBirth)
                    .HasColumnName("DoB")
                    .HasColumnOrder(3)
                    .HasColumnType("datetime2");
    }
}

如上所示,我们使用一个实体的Property()方法来配置列名、排序和数据类型

modelBuilder.Entity<TEntity>().Property(expression) 可以使用不同的方法来配置特别的属性,如下图所示:

configure property with fluent api Entity Framework code-first

为属性配置可空或不为空的列:

Code-First将为主键的数据类型创建一个不为空的值,因为主键本身就不能为空,除非它的属性上使用了?号或者标记了Nullable<T>。

使用IsOptional方法可以创建一个可为空的列,同样,使用IsRequired也可以创建一个不为空的列。

namespace CodeFirst_FluentAPI_Tutorials
{
        
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
                //Configure Null Column
            modelBuilder.Entity<Student>()
                    .Property(p => p.Heigth)
                    .IsOptional();
                        
                //Configure NotNull Column
                modelBuilder.Entity<Student>()
                    .Property(p => p.Weight)
                    .IsRequired();
        }
    }
}

 

配置列的大小:

Code-First默认约定是给列创建最大的数据类型大小,用HasMaxLength()方法覆写之。

namespace CodeFirst_FluentAPI_Tutorials
{
        
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //Set StudentName column size to 50
            modelBuilder.Entity<Student>()
                    .Property(p => p.StudentName)
                    .HasMaxLength(50);
                        
            //Set StudentName column size to 50 and change datatype to nchar 
            //IsFixedLength() change datatype from nvarchar to nchar
            modelBuilder.Entity<Student>()
                    .Property(p => p.StudentName)
                    .HasMaxLength(50).IsFixedLength();
                        
            //Set size decimal(2,2)
                modelBuilder.Entity<Student>()
                    .Property(p => p.Height)
                    .HasPrecision(2, 2);
        }
    }
}     

 

IsFixedLength方法把列的类型从nvarchar转变为nchar,HasPrecision方法改变了数据类型为decimal列的精度。

配置并发列:

使用ConcurrencyToken方法把一个属性设置为并发列,代码如下:

namespace CodeFirst_FluentAPI_Tutorials
{
        
    public class SchoolContext: DbContext 
    {
        public SchoolDBContext(): base() 
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //Set StudentName as concurrency column
            modelBuilder.Entity<Student>()
                    .Property(p => p.StudentName)
                    .IsConcurrencyToken();
        }
    }
}

如上代码所示,StudentName被设置成为了并发列,每当update和delete操作的时候都会把StudentName中的值加入到SQL语句中的"where"子句中。

对于byte[]类型的属性,我们也可以用IsRowVersion()方法来将其配置成并发列。

 

 

到此,Fluent API的主要内容就讲的差不多了,下面开始会讲一对一,一对多,多对多的领域类配置,和数据库迁移等。

 

posted @ 2016-05-28 21:38  唐大兵  阅读(2349)  评论(0编辑  收藏  举报