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>