Entity Framework使用Code First模式管理數據庫
一、管理數據庫連接
1、使用配置文件管理連接之約定
在數據庫上下文類中,如果我們隻繼承瞭無參數的DbContext,並且在配置文件中創建瞭和數據庫上下文類同名的連接字符串,那麼EF會使用該連接字符串自動計算出數據庫的位置和數據庫名。比如,我們的數據庫上下文定義如下:
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConventionConfigure.EF { /// <summary> /// 繼承無參數的DbContext /// </summary> public class SampleDbEntities :DbContext { public SampleDbEntities() { // 數據庫不存在時創建數據庫 Database.CreateIfNotExists(); } } }
在配置文件中定義的連接字符串如下:
<connectionStrings> <add name="SampleDbEntities" connectionString="Data Source=.;Initial Catalog=TestDb;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
定義的連接字符串中name的value值和創建的數據庫上下文類的類名相同,這樣EF會使用該連接字符串執行數據庫操作,究竟會發生什麼呢?
運行程序,Program類定義如下:
using ConventionConfigure.EF; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConventionConfigure { class Program { static void Main(string[] args) { using (var context = new SampleDbEntities()) { } Console.WriteLine("創建成功"); Console.ReadKey(); } } }
當運行應用程序時,EF會尋找我們的數據庫上下文類,即“SampleDbEntities”,並在配置文件中尋找和它同名的連接字符串,然後它會使用該連接字符串計算出應該使用哪個數據庫provider,之後檢查數據庫位置,之後會在指定的位置創建一個名為TestDb.mdf的數據庫文件,同時根據連接字符串的Initial Catalog屬性創建瞭一個名為TestDb的數據庫。創建的數據庫結構如下:
查看創建後的數據庫,會發現隻有一張遷移記錄表。
2、使用已經存在的ConnectionString
如果我們已經有瞭一個定義數據庫位置和名稱的ConnectionString,並且我們想在數據庫上下文類中使用這個連接字符串,連接字符串如下:
<connectionStrings> <add name="AppConnection" connectionString="Data Source=.;Initial Catalog=TestDb;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
以上面創建的數據庫TestDb作為已經存在的數據庫,新添加實體類Student,使用已經存在的ConnectionString查詢數據庫的Student表,Student實體類定義如下:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExistsConnectionString.Model { [Table("Student")] public class Student { public int Id { get; set; } public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } } }
我們將該連接字符串的名字傳入數據庫上下文DbContext的有參構造函數中,數據庫上下文類定義如下:
using ExistsConnectionString.Model; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExistsConnectionString.EF { public class SampleDbEntities : DbContext { public SampleDbEntities() : base("name=AppConnection") { } // 添加到數據上下文中 public virtual DbSet<Student> Students { get; set; } } }
上面的代碼將連接字符串的名字傳給瞭DbContext類的有參構造函數,這樣一來,我們的數據庫上下文就會開始使用該連接字符串瞭,在Program類中輸出Name和Age字段的值:
using ExistsConnectionString.EF; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExistsConnectionString { class Program { static void Main(string[] args) { using (var context = new SampleDbEntities()) { foreach (var item in context.Students) { Console.WriteLine("姓名:"+item.Name+" "+"年齡:"+item.Age); } } } } }
運行程序,發現會報下面的錯誤:
出現上面報錯的原因是因為數據庫上下文發生瞭改變,與現有數據庫不匹配。解決方案:
把數據庫裡面的遷移記錄表刪掉或者重命名即可。
重新運行程序,結果如下:
註意:如果在配置文件中還有一個和數據庫上下文類名同名的ConnectionString,那麼就會使用這個同名的連接字符串。無論我們對傳入的連接字符串名稱如何改變,都是無濟於事的,也就是說和數據庫上下文類名同名的連接字符串優先權更大。(即約定大於配置)
3、使用已經存在的連接
通常在一些老項目中,我們隻會在項目中的某個部分使用EF Code First,同時,我們想對數據上下文類使用已經存在的數據庫連接,如果要實現這個,可將連接對象傳給DbContext類的構造函數,數據上下文定義如下:
using ExistsDbConnection.Model; using System; using System.Collections.Generic; using System.Data.Common; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExistsDbConnection.EF { public class SampleDbEntities :DbContext { public SampleDbEntities(DbConnection con) : base(con, contextOwnsConnection: false) { } public virtual DbSet<Student> Students { get; set; } } }
這裡要註意一下contextOwnsConnection參數,之所以將它作為false傳入到上下文,是因為它是從外部傳入的,當上下文超出瞭范圍時,可能會有人想要使用該連接。如果傳入true的話,那麼一旦上下文出瞭范圍,數據庫連接就會立即關閉。
Program類定義如下:
using System; using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Configuration; using ExistsDbConnection.EF; namespace ExistsDbConnection { class Program { static void Main(string[] args) { // 讀取連接字符串 string conn = ConfigurationManager.ConnectionStrings["AppConnection"].ConnectionString; // DbConnection是抽象類,不能直接實例化,聲明子類指向父類對象 DbConnection con = new SqlConnection(conn); using (var context = new SampleDbEntities(con)) { foreach (var item in context.Students) { Console.WriteLine("姓名:" + item.Name + " " + "年齡:" + item.Age); } } Console.WriteLine("讀取完成"); Console.ReadKey(); } } }
運行程序,結果如下:
二、管理數據庫創建
首次運行EF Code First應用時,EF會做下面的這些事情:
1、檢查正在使用的DbContext類。
2、找到該上下文類使用的connectionString。
3、找到領域實體並提取模式相關的信息。
4、創建數據庫。
5、將數據插入系統。
一旦模式信息提取出來,EF會使用數據庫初始化器將該模式信息推送給數據庫。數據庫初始化器有很多可能的策略,EF默認的策略是如果數據庫不存在,那麼就重新創建;如果存在的話就使用當前存在的數據庫。當然,我們有時也可能需要覆蓋默認的策略,可能用到的數據庫初始化策略如下:
CreateDatabaseIfNotExists:CreateDatabaseIfNotExists:顧名思義,如果數據庫不存在,那麼就重新創建,否則就使用現有的數據庫。如果從領域模型中提取到的模式信息和實際的數據庫模式不匹配,那麼就會拋出異常。
DropCreateDatabaseAlways:如果使用瞭該策略,那麼每次運行程序時,數據庫都會被銷毀。這在開發周期的早期階段通常很有用(比如設計領域實體時),從單元測試的角度也很有用。
DropCreateDatabaseIfModelChanges:這個策略的意思就是說,如果領域模型發生瞭變化(具體而言,從領域實體提取出來的模式信息和實際的數據庫模式信息失配時),就會銷毀以前的數據庫(如果存在的話),並創建新的數據庫。
MigrateDatabaseToLatestVersion:如果使用瞭該初始化器,那麼無論什麼時候更新實體模型,EF都會自動地更新數據庫模式。這裡很重要的一點是:這種策略更新數據庫模式不會丟失數據,或者是在已有的數據庫中更新已存在的數據庫對象。MigrateDatabaseToLatestVersion初始化器隻有從EF4.3才可用。
1、設置初始化策略
EF默認使用CreateDatabaseIfNotExists作為默認初始化器,如果要覆蓋這個策略,那麼需要在DbContext類中的構造函數中使用Database.SetInitializer方法,下面的例子使用DropCreateDatabaseIfModelChanges策略覆蓋默認的策略。數據庫上下文類定義如下:
using InitializationStrategy.Model; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace InitializationStrategy.EF { public class SampleDbEntities : DbContext { public SampleDbEntities() : base("name=AppConnection") { // 使用DropCreateDatabaseIfModelChanges策略覆蓋默認的策略 Database.SetInitializer<SampleDbEntities>(new DropCreateDatabaseIfModelChanges<SampleDbEntities>()); } // 添加到數據上下文中 public virtual DbSet<Student> Students { get; set; } } }
這樣一來,無論什麼時候創建上下文類,Database.SetInitializer()方法都會被調用,並且將數據庫初始化策略設置為DropCreateDatabaseIfModelChanges。
Student領域實體類新增加Email和Address兩個屬性:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace InitializationStrategy.Model { [Table("Student")] public class Student { public int Id { get; set; } public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } public string Email { get; set; } public string Address { get; set; } } }
Program類定義如下:
using InitializationStrategy.EF; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace InitializationStrategy { class Program { static void Main(string[] args) { using (var context = new SampleDbEntities()) { foreach (var item in context.Students) { } } Console.WriteLine("創建成功"); Console.ReadKey(); } } }
運行程序後,數據庫表結構如下:
註意:如果處於生產環境,那麼我們肯定不想丟失已經存在的數據。這時我們就需要關閉該初始化器,隻需要將null傳給Database.SetInitlalizer()方法,如下所示:
public SampleDbEntities(): base("name=AppConnection") { Database.SetInitializer<SampleDbEntities>(null); }
2、填充種子數據
到目前為止,無論我們選擇哪種策略初始化數據庫,生成的數據庫都是一個空的數據庫。但是許多情況下我們總想在數據庫創建之後、首次使用之前就插入一些數據。此外,開發階段可能想以admin的資格為其填充一些數據,或者為瞭測試應用在特定的場景中表現如何,想要偽造一些數據。
當我們使用DropCreateDatabaseAlways和DropCreateDatabaseIfModelChanges初始化策略時,插入種子數據非常重要,因為每次運行應用時,數據庫都要重新創建,每次數據庫創建之後在手動插入數據非常乏味。接下來我們看一下當數據庫創建之後如何使用EF來插入種子數據。
為瞭向數據庫插入一些初始化數據,我們需要創建滿足下列條件的數據庫初始化器類:
1、從已存在的數據庫初始化器類中派生數據。
2、在數據庫創建期間種子化。
下面演示如何初始化種子數據
1、定義領域實體類
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace InitializationSeed.Model { [Table("Employee")] public class Employee { public int EmployeeId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } }
2、創建數據庫上下文
使用EF的Code First方式對上面的模型創建數據庫上下文:
public class SampleDbEntities : DbContext { public virtual DbSet<Employee> Employees { get; set; } }
3、創建數據庫初始化器類
假設我們使用的是DropCreateDatabaseAlways數據庫初始化策略,那麼初始化器類就要從該泛型類繼承,並傳入數據庫上下文作為類型參數。接下來,要種子化數據庫就要重寫DropCreateDatabaseAlways類的Seed()方法,而Seed()方法拿到瞭數據庫上下文,因此我們可以使用它來將數據插入數據庫:
using InitializationSeed.Model; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace InitializationSeed.EF { /// <summary> /// 數據庫初始化器類 /// </summary> public class SeedingDataInitializer : DropCreateDatabaseAlways<SampleDbEntities> { /// <summary> /// 重寫DropCreateDatabaseAlways的Seed方法 /// </summary> /// <param name="context"></param> protected override void Seed(SampleDbEntities context) { for (int i = 0; i < 6; i++) { var employee = new Employee { FirstName="測試"+(i+1), LastName="工程師" }; context.Employees.Add(employee); } base.Seed(context); } } }
上面的代碼通過for循環創建瞭6個Employee對象,並將它們添加給數據庫上下文類的Employees集合屬性。這裡值得註意的是我們並沒有調用DbContext.SaveChanges()方法,因為它會在基類中自動調用。
4、將數據庫初始化器類用於數據庫上下問類
using InitializationSeed.Model; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace InitializationSeed.EF { public class SampleDbEntities :DbContext { public SampleDbEntities() : base("name=AppConnection") { // 類型傳SeedingDataInitializer Database.SetInitializer<SampleDbEntities>(new SeedingDataInitializer()); } // 領域實體添加到數據上下文中 public virtual DbSet<Employee> Employees { get; set; } } }
5、Main方法中訪問數據庫
using InitializationSeed.EF; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace InitializationSeed { class Program { static void Main(string[] args) { using (var context = new SampleDbEntities()) { foreach (var item in context.Employees) { Console.WriteLine("FirstName:"+item.FirstName+" "+"LastName:"+item.LastName); } } Console.WriteLine("讀取完成"); Console.ReadKey(); } } }
6、運行程序,查看結果
查看數據庫
種子數據填充完成。
7、使用數據遷移的方式填充種子數據
使用數據遷移的方式會生成Configuration類,Configuration類定義如下:
namespace DataMigration.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration : DbMigrationsConfiguration<DataMigration.SampleDbEntities> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(DataMigration.SampleDbEntities context) { // This method will be called after migrating to the latest version. // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. } } }
重寫Configuration類的Seed()方法也可以實現插入種子數據,重寫Seed()方法:
namespace DataMigration.Migrations { using DataMigration.Model; using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration : DbMigrationsConfiguration<DataMigration.SampleDbEntities> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(DataMigration.SampleDbEntities context) { // This method will be called after migrating to the latest version. // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. context.Employees.AddOrUpdate( new Employee { FirstName = "測試1", LastName = "工程師" }, new Employee { FirstName = "測試2", LastName = "工程師" } ); } } }
使用數據遷移,然後查看數據庫結果:
發現使用數據遷移的方式也將種子數據插入到瞭數據庫中。
代碼下載地址:點此下載
到此這篇關於Entity Framework使用Code First模式管理數據庫的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Entity Framework使用LINQ操作實體
- Entity Framework使用Code First的實體繼承模式
- Entity Framework實體拆分多個表
- Entity Framework管理一對二實體關系
- Entity Framework使用Code First模式管理視圖