1. CodeFirst的默认约定
1.领域类和数据库架构的映射约定
在介绍数据库的初始化之前我们需要先了解领域类和数据库之间映射的一些约定。在CodeFirst模式中,约定指的是根据领域类(如Student,Grade类)自动配置概念模型的一些默认规则。在上一节的小栗子中,我们没有在领域类中做任何配置,但是EF API帮我们配置了主外键、关系、列的数据类型等,这就是约定在起作用。下表中列除了一些默认的CodeFirst约定:
默认规则 | 描述 |
Schema | EF创建所有的DB对象都放在dbo架构中。dbo.Students |
Table Name | 实体名的复数,如Student->Students |
Foreign key | 默认情况,EF会找和主实体的主键名一样名字的列 如果没有的话EF创建一个导航属性名_导航属性主键形式的外键, 如:在dbo.Students表中,外键是Grade_GradeId |
列顺序 |
EF创建的数据库列的数据和领域类属性的顺序一致,唯一可能不一致的是会把主键放在第一位 |
映射(mapping) |
默认EF会把领域类的所有属性都映射到数据库,可以通过[NotMapped]实现领域类/属性的不映射 |
级联删除 |
默认启用所有的类型关系 |
下表显示了C#数据类型到SqlServer数据类型的映射:
bool | bit |
byte | tinyint |
short | smallint |
int | int |
long | bigint |
float | real |
double | float |
decimal | decimal(18,2) |
string | nvrchar(Max) |
datetime | datetime |
byte[] | varbinary(Max) |
下图显示了领域类和数据库架构的映射:
2.一些补充
当我们使用导航属性时,EF6中把1对多关系作为默认关系。
注意:EF6中不包含1对1,多对多的默认关系,我们需要自己通过Fluent API或者注释属性进行配置。这些以后会介绍。当EFAPI找不到主键时,CodeFist模式会给这个类创建为复杂类型(Complex Type)。
2.EF确定数据库的名字的方式
前边我们知道了数据库中表名,列名,主外键名是怎么来的,但是数据库的名字是怎么确定的呢?下图展示了数据库初始化的工作流程,在我们创建SchoolContext(继承于DbContext)时,通过给父类的构造函数传值来确定数据库的名字
通过上图,上下文类的父构造函数可以接受如下的参数:
1.无参数
2.数据库名字
3.连接字符串名字
1.无参数
如果不向父构造函数传参,EF会在local SQLEXPRESS中创建数据库,名字是{NameSpace}.{Context Name}。我们上节的栗子中,创建的数据库名字就是:EF6Console.SchoolContext
namespace EF6Console { public class SchoolContext : DbContext { public SchoolContext():base() { } public virtual DbSet<Student> Students { get; set; } public virtual DbSet<Grade> Grades { get; set; } } }
2.数据库名字作为参数
namespace EF6Console { public class SchoolContext : DbContext { public SchoolContext():base("MySchoolDb") { } public virtual DbSet<Student> Students { get; set; } public virtual DbSet<Grade> Grades { get; set; } } }
如上边的代码所示, 我们把自己想取的名字(如MySchoolDb)传入base即可,EF会在local SQLEXPRESS中帮我们创建一个名字为MySchoolDb的数据库。
3.连接字符串名字
我们也可以通过给base传入连接字符串名字来确定数据库名字,传入连接字符串的格式是:"name=yourConnectionString",注意这个是固定格式,如果不加“name=”的话,EF会认为我们传入的是数据库名字。
namespace EF6Console { public class SchoolContext : DbContext { public SchoolContext():base("name=SchoolDbConnectionString") { } public virtual DbSet<Student> Students { get; set; } public virtual DbSet<Grade> Grades { get; set; } } }
App.config:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings> <add name="SchoolDbConnectionString" connectionString="Data Source=.;Initial Catalog=SchoolDB-ByConnectionString;Integrated Security=true" providerName="System.Data.SqlClient"/> </connectionStrings> </configuration>
在上边的SchoolContext类中我们把连接字符串的名字作为参数传入,EF会在app.config或web.config找到连接字符串,获取数据库的名字(SchoolDB-ByConnectionString),然后使用现有的数据库,连接字符串中的数据库不存在就在本地Sql Server数据库中新建一个名为SchoolDB-ByConnectionString的数据库(不一定都要是Sql Server,后边会介绍在MySql中生成数据库)。
3.数据库初始化策略
前边我们已经在运行程序时生成了数据库,也知道了数据库初始化的基本流程,但是有一些新的问题:我们再次运行程序会创建一个新的数据库吗?生产环境怎么去配置?数据库生成后如果我们改变了领域类怎么办?为了解决这些问题我们必须有一个数据库迁移策略。
1.四种初始化器
EF中有四种数据库的初始化器:
1.CreateDatabaseIfNotExists:这是默认的初始化器。这种初始化器在第一次运行程序时会创建数据库,再次运行不会再创建新的数据库。但是如果我们改变了领域类,运行程序时会抛出一个异常,这在前边的小栗子中已经演示过了。
2.DropCreateDatabaseIfModelChanges:如果领域类发生了改变,删除以前的数据库,然后重建一个新的。采用这种初始化器我们不用再担心领域类改变影响数据库架构的问题。
3.DropCreateDatabaseAlways:使用这种初始化器,我们每次运行程序都会删除以前的数据库,重建新的数据库。如果在开发过程中每次都想使用最新的数据库,那么可以采用这种初始化器。
4.Custom DB Initializer:自定义初始化器,如果上边几种都不能满足要求,那么我们可以自己定义一个初始化器。
1.通过代码配置初始化器
使用上边四种任意一个初始化策略我们都要使用Database类,如下:
public class SchoolDBContext: DbContext { public SchoolDBContext(): base("SchoolDBConnectionString") { Database.SetInitializer<SchoolDBContext>(new CreateDatabaseIfNotExists<SchoolDBContext>()); //Database.SetInitializer<SchoolDBContext>(new DropCreateDatabaseIfModelChanges<SchoolDBContext>()); //Database.SetInitializer<SchoolDBContext>(new DropCreateDatabaseAlways<SchoolDBContext>()); //Database.SetInitializer<SchoolDBContext>(new SchoolDBInitializer());//自定义初始化器 } public DbSet<Student> Students { get; set; } public DbSet<Standard> Standards { get; set; } }
自定义的初始化器SchoolDBInitializer继承以上几种初始化器(这里继承CreateDatabaseIfNotExists),如下:
public class SchoolDbInitializer : CreateDatabaseIfNotExists<SchoolDBContext> { protected override void Seed(SchoolDbContext context) { base.Seed(context); } }
2.通过配置文件配置初始化器
我们可以在配置文件中配置初始化器:
①内置的初始化器
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="DatabaseInitializerForType EF6Console.SchoolDBContext, EF6Console" value="System.Data.Entity.DropCreateDatabaseAlways`1[[EF6Console.SchoolDBContext, EF6Console]], EntityFramework" /> </appSettings> </configuration>
②自定义初始化器
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="DatabaseInitializerForType EF6Console.SchoolDBContext, EF6Console" value="EF6Console.SchoolDBInitializer, EF6Console" /> </appSettings> </configuration>
2.关闭初始化器
① 通过代码关闭
public class SchoolDBContext: DbContext { public SchoolDBContext() : base("SchoolDBConnectionString") { //关闭初始化器 Database.SetInitializer<SchoolDBContext>(null); } public DbSet<Student> Students { get; set; } public DbSet<Standard> Standards { get; set; } }
② 通过配置文件关闭
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="DatabaseInitializerForType EF6Console.SchoolDBContext,EF6Console" value="Disabled" /> </appSettings> </configuration>