寫在前面:
EF 中 Code First 的數據遷移網上有很多資料,我這份並沒什么特別。Code First 創建視圖網上也有很多資料,但好像很麻煩,而且親測好像是無效的方法(可能是我太笨,沒搞成功),我摸索出了一種簡單有效的方法,這里分享給大家。
EF是Entity Framework(實體框架)的簡寫,是微軟出品的用來操作數據庫的一個框架,會ASP.NET MVC的朋友對他肯定都不陌生。由於學藝不精,我對EF存在一疑慮,就不以【提問】的方式來問了,我以【總結】的方式來表達,如果總結有誤的地方,還請看到的大神可以指正,並賜教我正確的認知,萬分感謝。
EF有三種使用方式:
1) Db First 數據庫優先
2) Model First 模型優先
3) Code First 代碼優先
上圖中,前三種分別是DbFirst、ModelFirst和CodeFirst,而第4種也是CodeFirst。
一、DbFirst、ModelFirst必須從app.config/web.config中讀取連接字符串,簡直無情
不知道是我功底太差,還是微軟真的就是這么設計的,我發現一個問題:DbFirst和ModelFirst這兩種模式,居然只能從App.config/Web.config中讀取數據庫連接字符串,這是一個致命的問題,這意味着連接字符串不能被加密必須暴露給客戶看(如果你是窗體應用程序的話)。(感謝 @lcs-帥 指正這一點) 這意味着我們必須要 這樣(利用ASP.NET加密和解密Web.config中連接字符串) 來對我們的連接字符串進行加密,好像有點太復雜了。
如果不利用上面紅色大字中提到的方法,那我們大概可以有兩種方式讀取數據庫連接字符串,第一種是在App.config/Web.config中讀取:
1 <connectionStrings> 2 <add name="Model1" connectionString="data source=(LocalDb)\v11.0;initial catalog=數據庫名;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="..略.." /> 3 </connectionStrings>
這種方式不太好,因為如果是窗體應用程序的話,等於就用明文告訴了別人你的數據庫密碼了。當然,你也可以對這一段字符串進行加密,就像這樣:
1 <connectionStrings> 2 <add name="Model1" connectionString="ZGF0YSUyMHNvdXJjZSUzRCUyOExvY2FsRGIlMjklNUN2MTEuMCUzQmluaXRpYWwlMjBjYXRhbG9nJTNEJXU2NTcwJXU2MzZFJXU1RTkzJXU1NDBEJTNCaW50ZWdyYXRlZCUyMHNlY3VyaXR5JTNEVHJ1ZSUzQk11bHRpcGxlQWN0aXZlUmVzdWx0U2V0cyUzRFRydWUlM0JBcHAlM0RFbnRpdHlGcmFtZXdvcms=" providerName="..略.." /> 3 </connectionStrings>
而當你像上面這樣對數據庫連接字符串進行加密,此時你就等同於選擇了使用第二種方式讀取連接字符串了,第二種讀讀取數據庫連接字符串的方式是:
1 string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["Model1"].ConnectionString; //可選 2 connStr = 解密(connStr); //可選 3 Model1 db = new Model1(connStr); //第二種設置數據庫連接字符串的方式
這第二種方式的前2句代碼是可選的,也就是說你不一定要從Web.config/App.config中讀取數據庫連接字符串,你也不一定要對數據庫連接字符串進行加解密。
這第二種方式最重要的是第3句代碼,也就是說你可以使用一個字符串變量來做為數據庫連接字符串,這意味着你不一定要從Web.config/App.config中讀取數據庫連接字符串。
然而!可惜的是:在DbFirst和ModelFirst中,並不支持第二種設置數據庫連接字符串的方式,這也就意味着DbFirst和ModelFirst的應用場景大打折扣!
在DbFirst或ModelFirst中,調用以下代碼會報錯,因為只有一個無參構造方法:
也許你(曾經我)自做聰明的想到一些辦法,寫一個【部分類】為 DbContext 添加一個有參構造函數:
事實證明是自做聰明,當你去操作任何一張表的時候,就報這個錯誤:
當你(我)看到這一個錯誤提示的時候,終於認命了:在DbFirst和ModelFirst中,數據庫連按字符串只能寫在App.config/Web.config中,簡直無情。
二、CodeFirst 可以從代碼中讀取數據庫連接字符串,不必從Web.config/App.config中讀取。
要在CodeFirst中對連接字符串進行加密,首先要添加一個帶參構造函數,就像這樣:
1 public Model1() 2 : base("name=Model11") 3 { 4 } 5 public Model1(string nameOrConnectionString) 6 : base(nameOrConnectionString) 7 { 8 }
然后就這樣:
1 string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["Model1"].ConnectionString; //可選 2 connStr = 解密(connStr); //可選 3 Model1 db = new Model1(connStr); //第二種設置數據庫連接字符串的方式
從配置文件中讀取出加了密的連接字符串,再進行解密,最后應用於 DbContext 之中。
在CodeFirst中進行這樣的操作程序不會報錯,簡直有愛。
三、【空 Code First 模型】與【來自數據庫的 Code First 】殊途同歸,是一樣的
關於Code First,有兩個可以選擇,下圖中的最后兩個:
這兩者是殊途同歸,是一樣的。
【空CodeFirst模型】就是新建一個空的模型,然后自己寫實體類代碼,根據這些實體類代碼來生成數據庫,生成數據庫了之后,就與【來自數據庫的CodeFirst】是一樣的。
【來自數據庫的CodeFirst】就是根據從數據庫中選擇的一些表/視圖來生成實體類代碼,這些自動生成的代碼和你自己手寫是一樣一樣的,也就是與【空CodeFirst模型+手寫實體類代碼】是一樣的。
然而,並不完全是!慢慢聊吧。
四、數據遷移
數據遷移,就是實體模型類發生變化之后(比如多一個字段,字段類型改變,或者多了一個實體類),數據庫的表結構也跟着一起變化的過程。
4.1 為什么要使用數據遷移
因為模型和實際的數據庫結構可能會不一樣,不一樣的話程序運行就會有問題,所以需要使用數據遷移這個功能來幫助我們解決問題。
用一個例子來說明,創建控制台應用程序,創建【空CodeFirst模型】:
1 //數據庫Model1 2 public class Model1 : DbContext 3 { 4 public Model1() : base("name=Model1") { } 5 public virtual DbSet<MyEntity> MyEntities { get; set; } 6 } 7 //實體類 (對應表:MyEntities) 8 public class MyEntity 9 { 10 public int Id { get; set; } 11 public string Name { get; set; } 12 }
修改數據庫連接字符串,改成一個我們熟悉的數據庫服務器名,以免數據庫新建了之后,找不到在哪里:
<add name="Model1" connectionString="server=服務器名;uid=用戶名;pwd=密碼;database=新的數據庫名;" providerName="System.Data.SqlClient" />
然后在Main方法中隨便訪問一下這個實體類:
1 using (var db = new Model1()) 2 { 3 //隨便訪問一下 MyEntities 表,以便讓 EF 自動創建此表 4 var x = db.MyEntities.Find(1); 5 }
運行程序,就會得到一個新的數據庫,新數據庫里面有一個新表 MyEntities。(還有另一個叫 __MigrationHistory 的表,是用於記錄代碼遷移的歷史記錄,也是代碼遷移模式的開關,后面慢慢聊)
到這里為止,一切都正常。
現在,我們再添加一個年齡Age字段:
1 //實體類 (對應表:MyEntities) 2 public class MyEntity 3 { 4 public int Id { get; set; } 5 public string Name { get; set; } 6 //添加了年齡字段 7 public int? Age { get; set; } 8 }
這時,再運行程序,就會報一個錯誤:
錯誤的大概意思就是:模型與實際數據庫結構不一致,需要使用 Code First 中的數據遷移功能來解決問題。
4.2 數據遷移的用法
當模型與實際數據庫結構不一致時,就需要使用數據遷移。數據遷移我們只需要掌握三個方法就可以了,它們分別是:
1) 在當前項目中啟用數據遷移 Enable-Migrations
2) 添加遷移版本 Add-Migration 版本名稱
3) 更新數據庫 Update-Database
首先第一步,需要在當前項目中啟用數據遷移,打開【視圖->其它窗口->程序包管理器控制台】快捷鍵是 Alt+V,E,O,然后輸入命令:
Enable-Migrations
這樣就可以在當前項目中啟用數據遷移了,其效果是項目中會多出 Migrations 文件夾以及一些代碼:
Migrations 這個文件夾就是數據遷移的相關文件夾,做為初學者,不要亂動里面的代碼。
由於我們的模型已經發生了改變,所以需要添加一個新的遷移版本,在程序包管理控制台執行如下命令:
Add-Migration v1
其中的v1表示版本號,自己隨便取一個名字就可以了。此時,項目中多出一個文件:
這個文件的代碼是:
1 //版本 v1 2 public partial class v1 : DbMigration 3 { 4 //版本升級 5 public override void Up() 6 { 7 //向 MyEntities 表添加 Age 字段 8 AddColumn("dbo.MyEntities", "Age", c => c.Int()); 9 } 10 11 //版本降級 12 public override void Down() 13 { 14 //向 MyEntities 表刪除 Age 字段 15 DropColumn("dbo.MyEntities", "Age"); 16 } 17 }
代碼中的注釋是我寫上去的,大概的為大家解釋了一下是什么意思。到這里為止,數據庫依然沒有發生變化。
想要數據庫發生變化,多出一個 Age 字段,那就要用到第三個命令:
Update-Database
執行成功之后,數據庫就多出一個 Age 字段了。
此時,再運行程序,程序就不會報錯了。因為此時的模型與實際數據庫結構已經一致。
至此,例子已演示完畢。
這里有一個非常非常非常重要的細節,要注意:
如果你使用了 new Model1(connStr) 自定義數據庫連接字符串,在使用數據遷移的命令時,所訪問的數據庫是你 web.config/app.config 中配置的數據庫,並不是你自定義連接的那個數據庫。
這是一個巨坑,一定要注意:
使用了自定義數據庫連接字符串時,雖然 web.config/app.config 中的明文數據庫連接字符串並不一定要被發布,但在開發時也一定要在里面填寫上正確的數據庫連接信息。
五、關閉數據遷移
數據遷移的開啟與關閉分兩個層面來說:
1) Visual Studio 項目是否開啟了數據遷移
2) SQL Server 數據庫是否開啟了數據遷移
對於項目中是否開啟了數據遷移,識別方法是:有沒有 Migrations 這個文件夾以及里面的類文件。如果有就表示開啟了,沒有就沒開啟。
對於數據庫中是否開啟了數據遷移,識別方法是:有沒有 __MIgrationHistory 這個表。如果有就表示開啟了,沒有就沒開啟。
以下討論的數據遷移開啟和關閉,都是指數據庫中數據遷移的開啟和關閉。
實際上 Code First 又有兩種分支用法:
1) 數據庫中 啟用了數據遷移的 Code First
2) 數據庫中 關閉了數據遷移的 Code First
這個開關並不在於你的項目中有沒有 Migrations 這個文件夾,而在於:你的數據庫里面,有沒有 __MigrationHistory 這個表。
當你使用【空 Code First 模型】創建出來的數據庫,默認就是開啟了數據遷移的,關閉它的方法自然就是刪除 __MigrationHistory 這個表。
而你使用【來自數據庫的 Code First】默認這個數據庫就是關閉了數據遷移的 ,用一個例子來說明一下吧。
示例:
先確保你的數據庫實例中有一個數據庫,這個庫里面有一些表,比如 Student 表,並且這個庫沒有 __MigrationHistory 這個表。
然后新建控制台程序,並創建【來自數據庫的 Code First】,選擇 student 表,自動產生模型代碼和上下文類的代碼:
1 //這是自動產生的代碼 (我進行了刪減) 2 public partial class Model1 : DbContext 3 { 4 public Model1(): base("name=Model1") { } 5 public virtual DbSet<student> student { get; set; } 6 }
1 //這是自動產生的代碼 (我進行了刪減) 2 public partial class student 3 { 4 public int id { get; set; } 5 public string name { get; set; } 6 }
然后在 Main 方法里隨便調用一下,看看有沒有問題:
1 using (var db = new Model1()) 2 { 3 //隨便調用一下,看看有沒有問題 4 var x = db.student.Find(1); 5 }
因為此時的模型與實際數據庫結構是一樣的,運行結果當然不會有問題了。
現在,我們修改一下模型,在學生實體類中添加一個身份證的字段:
1 public partial class student 2 { 3 public int id { get; set; } 4 public string name { get; set; } 5 //添加身份證 idcard 字段 6 public string idcard { get; set; } 7 }
此時,我們再運行程序,程序報錯了,但報的錯誤並不是讓我們進行數據遷移:
報的錯誤是:列名 'idcard' 無效。
這就是數據庫中關閉了數據遷移的 Code First 的行為表現,不會提示你(要求你)進行數據遷移。
沒有了數據遷移,我們怎樣才能讓模型和實際數據庫表結構一樣呢?
很簡單,你自己手動在數據庫 student 表中添加一個 idcard 字段不就可以了嗎。
確實,當我們手動添加了 idcard 字段之后,再運行程序,程序就不報錯了。
小總結:
1) 使用【空 Code First 模型】創建出來的數據庫默認是開啟數據遷移的,我們只需要刪除 __MigrationHistory 表就可以關閉數據遷移。
2) 使用【來自數據庫的 Code First】這種方式,數據庫默認就是關閉了數據遷移的。
3) 在數據庫中關閉數據遷移的優點是:
你可能並不熟悉數據遷移是個什么鬼,那不如就讓它見鬼去吧。關閉它,會讓你進入你熟悉世界,通過手動改寫實體類,手動改數據庫表結構,來同步模型與實際表結構,挺好!
六、在關閉數據遷移的模式下,在 EF 實體模型中創建視圖
數據庫關閉了數據遷移的情況下,要在 EF 實體模型中創建視圖,實在是太簡單了。
你就編寫一個實體類來當成視圖,比如 v_student,你就把它當成是一張表一樣來編寫:
1 //我想寫一個視圖,先別急,先當它是一張表 2 public partial class v_student 3 { 4 public int id { get; set; } 5 public string name { get; set; } 6 //姓 7 public string first_name { get; set; } 8 }
1 public partial class Model1 : DbContext 2 { 3 public Model1(): base("name=Model1") { } 4 public virtual DbSet<student> student { get; set; } 5 //我想寫一個視圖,先別急,先當它是一張表 6 public virtual DbSet<v_student> v_student { get; set; } 7 }
然后,在 Main 方法中訪問這個視圖,看看有沒有問題:
1 using (var db = new Model1()) 2 { 3 //訪問一下這個視圖,看看有沒有問題 4 var x = db.v_student.Find(1); 5 }
運行。當然會有問題啦,會報這個錯誤:
注意到了嗎,它只是說 v_student 無效,並沒有說讓我們做什么數據遷移。
好吧,難道我們要傻傻的去創建一個 v_student 表嗎? 難道不可以創建一個 v_student 視圖?
當然可以,我們來創建一個視圖吧,在數據庫中執行以下SQL語句:
1 create view v_student 2 as 3 select id,name,substring(name,1,1) first_name from student 4 go
然后,測試運行程序,程序不報錯了。任務完成。
七、在開啟據遷移的模式下,在 EF 實體模型中創建視圖
也許你比較欣賞在數據庫中開啟了數據遷移的這種模式,在數據庫中開啟了數據遷移的模式下,能不能創建視圖呢?如果可以,又如何創建視圖呢?
網絡上你可以搜索到很多 Code First 中創建視圖的方法,這些方法都是指數據庫中開啟了數據遷移的 Code First 下的方法,但好像都挺麻煩的,反正我照着做並沒有做出來。
然后我摸索了一下,找出一種簡單又實用的方法,這里分享給大家。沒錯,這一段落才是本文最有價值的部分。
依然用例子來說明:
上接第四節【數據遷移】的例子,我們來添加一個 V_MyEntity 的視圖,代碼如下:
1 //我想寫一個視圖,先別急,先當它是一張表 2 public class V_MyEntity 3 { 4 public int Id { get; set; } 5 public string Name { get; set; } 6 //姓 7 public string First_Name { get; set; } 8 }
1 public class Model1 : DbContext 2 { 3 public Model1() : base("name=Model1") { } 4 public virtual DbSet<MyEntity> MyEntities { get; set; } 5 //我想寫一個視圖,先別急,先當它是一張表 6 public virtual DbSet<V_MyEntity> V_MyEntity { get; set; } 7 }
然后在 Main 方法隨便訪問一下這個視圖,看看可不可以:
1 using (var db = new Model1()) 2 { 3 //訪問一下 V_MyEntity 視圖,看看可不可以 4 var x = db.V_MyEntity.Find(1); 5 }
運行程序,自然是報錯了:
因為模型與實際數據庫表結構不一致,所以報錯了,由於數據庫中開啟了數據遷移的,所以會提示你(要求你)進行數據遷移。
此時,如果按照正常的步驟來進行數據遷移,我們將得到 V_MyEntity 這個表,這當然不是我們想要的結果,我們是要 V_MyEntity 視圖啊。
那就需要在數據遷移的過程中,做一點手腳,具體如下。
在程序包管理器控制台中輸入以下命令,以創建一個新的遷移版本:
Add-Migration v2
其中的 v2 表示版本號,其實可以隨便亂寫也行,只是個版本名字而已。
剛剛的操作使我們多出一個文件,內容如下(其中的中文注釋是我寫的,不是生成的):
1 //代碼遷移版本:v2 2 public partial class v2 : DbMigration 3 { 4 //版本升級 5 public override void Up() 6 { 7 //創建表 8 CreateTable( 9 "dbo.V_MyEntity", 10 c => new 11 { 12 Id = c.Int(nullable: false, identity: true), 13 Name = c.String(), 14 First_Name = c.String(), 15 }) 16 .PrimaryKey(t => t.Id); 17 18 } 19 //版本降級 20 public override void Down() 21 { 22 //刪除表 23 DropTable("dbo.V_MyEntity"); 24 } 25 }
我們當然不想要創建表啦,我們要創建視圖,那就把這個文件改一改,改成如下這個樣子(本文最最最重要的環節):
1 //代碼遷移版本:v2 2 public partial class v2 : DbMigration 3 { 4 //版本升級 5 public override void Up() 6 { 7 //創建視圖(本文最最最重要的環節) 8 Sql(@"create view V_MyEntity as select id,name,substring(name,1,1) first_name from MyEntities"); 9 } 10 //版本降級 11 public override void Down() 12 { 13 //刪除視圖 14 Sql("drop view V_MyEntity"); 15 } 16 }
改好之后,再執行數據遷移的最后一個命令,更新數據庫:
Update-Database
這個時候,我們發現數據庫沒有多出一個 V_MyEntity 的表,而是多出了一個 V_MyEntity 的視圖,目的達到。
最后我們再運行程序,程序也沒有再報錯了,目的妥妥的達到。
八、在【來自數據庫的 Code First】中開啟數據遷移
先要復習幾個問題:
1) 用【空 Code First 模型】創建的數據庫,默認就是開啟數據遷移的。
2) 用【來自數據庫的 Code First】這種方式,其數據庫默認是關閉數據遷移的。
3) 判斷一個數據庫有沒有啟用數據遷移,辦法是:檢查有沒有 __MigrationHistory 表。
也許雖然你比較欣賞數據庫中開啟了數據遷移的這種模式,但是無奈你已經有一個數據庫啦,數據庫里80多張表也已經創建好啦,那我們當然要用【來自數據庫的 Code First】,來幫助我們自動生成這80多個實體類啦。
可是,對於一個已經存在的數據庫,我們能開啟這個數據庫的數據遷移模式嗎?答案當然也是可以的,具體操作如下。
比如我們導入了一個Student表:
1 //我進行了刪減 2 public partial class Student 3 { 4 public int Id { get; set; } 5 public string Name { get; set; } 6 }
1 //我進行了刪減 2 public partial class Model1 : DbContext 3 { 4 public Model1() : base("name=Model1") { } 5 public virtual DbSet<Student> Student { get; set; } 6 }
然后二話不說,直接在項目中開啟代碼遷移(請注意這只是在項目中開啟代碼遷移,並沒有在數據庫中開啟代碼遷移):
Enable-Migrations
然后再二話不說,創建一個遷移版本:
Add-Migration v1
添加了遷移版本之后,會自動產生一個版本文件,內容如下(中文注釋是我寫的,不是自動生成的,略也是我刪減的):
1 //數據遷移版本名稱:v1 2 public partial class v1 : DbMigration 3 { 4 //版本升級 5 public override void Up() 6 { 7 //建表 8 CreateTable("dbo.Student",..略..); 9 } 10 //版本降級 11 public override void Down() 12 { 13 //刪表 14 DropTable("dbo.Student"); 15 } 16 }
然后再二話不說,執行更新數據庫的操作(這等於是一口氣把數據遷移的三個命令挨個打了個遍):
Update-Database
然后就報錯了:
錯誤提示數據庫已經有Student這個表了,這個問題怎么解決呢?很好解決,辦法就是找到之前那個版本文件,把版本升級Up()方法中的代碼全部注釋掉,如下:
1 //數據遷移版本名稱:v1 2 public partial class v1 : DbMigration 3 { 4 //版本升級 5 public override void Up() 6 { 7 //建表 8 //CreateTable("dbo.Student",..略..); //注釋掉這些代碼是重要環節 9 } 10 //版本降級 11 public override void Down() 12 { 13 //刪表 14 //DropTable("dbo.Student"); //注釋掉這些代碼是重要環節 15 } 16 }
注釋掉之后,再執行 Update-Database ,這次就不報錯了。
此時,數據庫中多了一個 __MigrationHistory 表,這表示數據庫的數據遷移功能已開啟,目的已達到:
最后,為了將來考慮,最好將剛剛注釋的代碼又取消注釋,這完全不影響你之后的使用。
總結:
1) EF 中只有 Code First 模式支持 new Model1(這里寫連接字符串),也就是說可以對其進行加密,這使得 Code First 成為大部分情況下的最佳選擇。
2) Code First 中,當模型與實際表結構不一致時,程序會報錯,我們有兩種方式來使他們一致,一是手動修改模型代碼和表結構,二是使用數據遷移功能。
這兩種方式,不是你想選哪一種就選哪一種的,取決於一個關鍵因素:數據庫是否開啟了數據遷移
3) 數據遷移的開啟,分兩個層面,一是項目的數據遷移開啟,二是數據庫的數據遷移開啟,前者並不怎么重要,后者決定了你需要手動還是自動來使模型與實際表結構一致。
項目中如果包含Migrations文件夾並且里面有相關的類,說明項目已開啟了數據遷移。
數據庫中如果有 __MigrationHistory 表,說明數據庫已開啟了數據遷移。
4) 刪除數據庫中的 __MigrationHistory 表,也就關閉了數據庫中數據遷移功能,關閉數據遷移的目的是:
你可能並不熟悉數據遷移是個什么鬼,那不如就讓它見鬼去吧。關閉它,會讓你進入你熟悉世界,通過手動改寫實體類,手動改數據庫表結構,來使模型與實際表結構一致,挺好!
5) 我的建議是:不要關閉數據遷移,還是可以嘗試學習一下數據遷移模式的。在程序包管理器控制台下,使用以下三個命令就可以使用數據遷移模式了:
*) 在項目中啟用數據遷移 Enable-Migrations
*) 創建遷移版本 Add-Migration 版本名稱
*) 更新數據庫 Update-Database
6) 在數據庫關閉數據遷移的情況下,創建視圖的方式與創建表一樣,寫好實體類 V_ABC 之后,再到數據庫里創建 V_ABC 的視圖就行了。
7) 在數據庫開啟數據遷移的情況下,創建視圖的方式是先寫好 V_ABC 實體類,再添加遷移版本,然后在遷移版本的代碼中注釋創建表 V_ABC 的代碼,改為創建視圖 V_ABC 的代碼,最后執行數據遷移中的更新數據庫命令即可。
8) 巨坑:當你在執行數據遷移的三個命令時,它們永遠是在 Web.config/App.config 中讀取連接字符串!別誤以為是 new Model1(這里的字符串) 啦。
在 Code First 中使用數據遷移,以及視圖創建,示例下載