准備工作
1.新建一個控制台項目, 在"程序包管理控制台"執行 Install-package EntityFramework //安裝EF環境
2.在項目下新建類(Paper),也就是code first中的code。建好之后,Ctrl+Shift+B生成項目。(不生成的話,會出現控制器找不到類型或者其他報錯)
3.在app.config或web.config的configuration下添加節點connectionStrings:
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=MySite;Persist Security Info=True;User ID=a;Password=b" providerName="System.Data.SqlClient" /> </connectionStrings>
4.項目下新建類SiteDbContext,作為項目的數據上下文:
public class SiteDbContext : DbContext { public SiteDbContext() : base("DefaultConnection") { } public DbSet<Paper> Papers { get; set; } }
在類下定義構造函數,傳遞步驟3的connectionString的name給基類構造函數。這樣,數據的獲取就會通過步驟3的數據庫鏈接了。同時在數據上下文中定義已經定義好的類的DbSet類型的屬性。為后面的遷移做准備。
增表
1.此時,如果直接在program的Main函數中調用SiteDbContext,然后操作下面的Papers數據,會在數據庫自動生成Papers表。調試程序。
2.打開遷移:Enable-Migrations
Checking if the context targets an existing database... Detected database created with a database initializer. Scaffolded migration '201502150256371_InitialCreate' corresponding to existing database. To use an automatic migration instead, delete the Migrations folder and re-run Enable-Migrations specifying the -EnableAutomaticMigrations parameter. Code First Migrations enabled for project MigrationTest.
執行完命令后,在項目中自動生成文件夾Migrations,以及文件夾下面的Configuration.cs和201502150256371_InitialCreate.cs
增屬性
1.修改類Paper的結構,在這里,我們新增屬性public string Test{get;set;},生成程序。
2.為了驗證數據庫會不會全部重新生成,我們在數據庫里手動插入幾條數據:
3.增加遷移的節點:Add-Migration PaperTest(在前面操作的基礎上,執行這條命令時,程序會自動判斷在上次遷移基礎上的修改)
執行完上面的命令后,會在文件夾Migrations下自動生成一個類:
public partial class PaperTest : DbMigration { public override void Up() { AddColumn("dbo.Papers", "Test", c => c.String()); } public override void Down() { DropColumn("dbo.Papers", "Test"); } }
class的名稱是上面Add后面定義的。而類下面有兩個方法,一個是Up,一個是Down。在Up里面,記錄了需要升級的修改,這里也就是Papers表格增加了列Test。只要我們在后面執行Update-Database,就會執行此類下面的Up函數。
這里的Down函數簡單介紹就是,為了回滾修改而設計的。如果用戶希望恢復到某一個遷移節點,程序會自動根據已經執行的遷移,判斷回滾哪些遷移,執行他們的Down函數。
4.執行Update-Database,將這里的修改升級到數據庫。
查看提示,在Update命令后面輸入參數-Verbose可以查看升級的明細。
第二行,運行遷移對象201502260053423_PaperTest。
第三行,運行Seed函數。這里說明一下,Seed函數在Configuration.cs里面:
protected override void Seed(MigrationTest.SiteDbContext 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. E.g. // // context.People.AddOrUpdate( // p => p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // }
仔細查看注釋的語句,可以看到,這個函數在每次遷移到最新版本時調用。用AddOrUpdate函數可以避免生成重復的數據。最后面給了一個AddOrUpdate的調用示例。
這個函數的用處,我暫時理解為,生成初始化數據。不管怎么遷移,都會存在這些數據。
5.執行完上面的操作后,再回到數據庫,查看數據結構和數據內容:
到此,在現有的類上面增加屬性的操作完成了。
刪屬性
刪除屬性的操作和增加屬性的操作差不多。首先修改類結構,屏蔽上面的//public string Test { get; set; }。然后Add一個遷移節點,命名為DelPaperTest。執行此命令,生成相應的遷移類文件。最后執行Update-Database,在這里我們用Update-Database -Verbose命令,查看一下執行的詳細內容:
PM> Update-Database -Verbose
Using StartUp project 'MigrationTest'.
Using NuGet project 'MigrationTest'.
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Target database is: 'MySite' (DataSource: ., Provider: System.Data.SqlClient, Origin: Configuration).
Applying explicit migrations: [201502260118534_DelPaperTest].
Applying explicit migration: 201502260118534_DelPaperTest.
DECLARE @var0 nvarchar(128)
SELECT @var0 = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'dbo.Papers')
AND col_name(parent_object_id, parent_column_id) = 'Test';
IF @var0 IS NOT NULL
EXECUTE('ALTER TABLE [dbo].[Papers] DROP CONSTRAINT [' + @var0 + ']')
ALTER TABLE [dbo].[Papers] DROP COLUMN [Test]
INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
VALUES (N'201502260118534_DelPaperTest', N'MigrationTest.SiteDbContext', 0x1F8...00 , N'6.1.2-31219')
Running Seed method.
查看上面內容里面的sql語句,首先尋找列Test相關的約束,如果有,先刪除約束。然后刪除列,最后在遷移歷史表里面寫入一行遷移記錄。
改屬性
1.在Paper類下修改屬性名稱,生成項目:
public string Desc { get; set; } --> public string DescAA { get; set; }
2.按照常規的方法,接下來我們應該添加一個遷移的節點。首先我們看看這樣執行有什么樣的結果:
Add-Migration ModifyPaper
打開自動生成的遷移類文件201502260128314_ModifyPaper.cs:
public partial class ModifyPaper : DbMigration { public override void Up() { AddColumn("dbo.Papers", "DescAA", c => c.String()); DropColumn("dbo.Papers", "Desc"); } public override void Down() { AddColumn("dbo.Papers", "Desc", c => c.String()); DropColumn("dbo.Papers", "DescAA"); } }
可以看到在Up里面,它不是直接修改了列的名稱,而是先增加新列DescAA,然后刪除舊列Desc。這樣執行有一個后果,如果列里面有數據,則數據全部丟失。
3.執行Update-Database的結果如下:
如何才能在保留現有數據的基礎上修改列名呢?
(常規操作中,不建議類似這樣修改列名的操作,因為數據庫的調用可能不止這一處,容易產生漏改而出現bug)
注意:修改列名見下面的方法三。下面的內容已過時,留文僅作記錄。
1.直接修改類屬性Desc為DescAA,同時,手動修改數據庫列名為DescAA。運行程序,會報錯:
2.在運行程序前,刪除遷移文件夾Migrations。然后運行程序,同樣報上面的錯誤。
3.項目中執行命令Enable-Migrations,重新生成遷移文件夾。然后運行程序,同樣報錯。
4.刪除數據庫中遷移歷史表中的項目相關的遷移記錄,同時刪除項目中的Migrations文件夾,然后再執行Enable-Migrations,會看到:
同時Migrations文件夾下除了Configuration.cs,沒有任何其他的類文件。
到此,修改類結構的屬性名稱完成,同時成功保住了數據庫的現有數據。
但是此方法,有一個陷阱。
如果在修改屬性之后,又有增屬性或者刪屬性的操作,在Add-Migration之后,自動生成的類里面,會出現這樣的代碼:
public partial class DelTestAfterChangeDescAA : DbMigration { public override void Up() { CreateTable( "dbo.Papers", c => new { PaperID = c.Int(nullable: false, identity: true), PaperName = c.String(), DescAA = c.String(), }) .PrimaryKey(t => t.PaperID); } public override void Down() { DropTable("dbo.Papers"); } }
上面的代碼直接CreateTable和DropTable。這是執行Update-Database會提示失敗,因為數據庫已有表Papers
這時,我們可以選擇手動的修改Up和Down的內容:
public partial class DelTestAfterChangeDescAA : DbMigration { public override void Up() { DropColumn("dbo.Papers", "Test"); } public override void Down() { AddColumn("dbo.Papers", "Test", c => c.String()); } }
執行Update-Database,命令完成。
方法二(注意:修改列名見下面的方法三。下面的內容已過時,留文僅作記錄。):
1.在上面的基礎上,修改Paper類的DescAA為Desc。
2.運行Add-Migration ModifyDescAA,得到遷移cs文件,手動注釋其中Up的DropColumn:
public partial class ModifyDesc : DbMigration { public override void Up() { AddColumn("dbo.Papers", "Desc", c => c.String()); //DropColumn("dbo.Papers", "DescAA"); } public override void Down() { //AddColumn("dbo.Papers", "DescAA", c => c.String()); DropColumn("dbo.Papers", "Desc"); } }
3.執行Update-Database。這時,數據庫的Papers表新增列Desc。
4.進入數據庫,手動刪除剛才新增的Desc列,同時,將DescAA列名修改成Desc。
5.運行程序,可以調試,說明上面的修改屬性名操作成功。
6.為了防止以后遷移到某個特定的版本時,回滾出現問題。手動屏蔽上面2步驟中Up和Down中的代碼.
這里留意:不能手動刪除遷移歷史表中的剛才一行遷移歷史,同時手動刪除項目中上面2步驟生成的cs文件。這樣會直接報上面的"數據庫上下文已改"錯。
上面這兩種方法,操作完了之后都需要做一定工作量的修補,否則下面的Code first就會有些隱患。
在調試完上面兩種方法后,發現DbMigration有RenameColumn的函數。
呵呵,在這個函數的基礎上,修改屬性名稱就變得超級簡單了。
1.修改Paper的DescAA,改成Desc
2.執行Add-Migration RenameDesc命令,生成遷移類文件,手動修改類文件內容如下:
public partial class RenameDesc : DbMigration { public override void Up() { //AddColumn("dbo.Papers", "Desc", c => c.String()); //DropColumn("dbo.Papers", "DescAA"); RenameColumn("dbo.Papers", "DescAA", "Desc"); } public override void Down() { //AddColumn("dbo.Papers", "DescAA", c => c.String()); //DropColumn("dbo.Papers", "Desc"); RenameColumn("dbo.Papers", "Desc", "DescAA"); } }
3.執行Update-Database命令,數據庫列名被自動修改。
這里值得注意的是,在執行Update命令時,程序會提醒操作着,修改列名可能會導致存儲過程及其他調用列的sql腳本失效:
這是在實際操作中要注意的問題,也正是筆者不建議隨便修改列名的主要原因——可能導致其他調用失效。
筆者小記:
邊摸索邊操作,到這里,對EF的code first架構算是有了一定的了解
1.遷移的關聯在數據庫的遷移歷史表和項目的Migrations文件夾下的繼承了DbMigration的cs文件
2.在數據庫的遷移歷史表中,字段Model有一些加了密的遷移映射,隨意刪除修改會導致"數據庫上下文被改"的錯誤。
3.Migrations文件夾下的繼承了DbMigration的cs文件可以手動修改,這里的修改可以非常靈活,表格和表格字段的增刪改,在這里都有。
理解了上面的這些點,簡單使用以code first為基礎的EF,應該沒什么問題了。