最近悟出來一個道理,在這兒分享給大家:學歷代表你的過去,能力代表你的現在,學習代表你的將來。
十年河東十年河西,莫欺少年窮
學無止境,精益求精
標題叫EF CodeFirs 代碼遷移、數據遷移。
那麼:到底叫代碼遷移還是數據遷移?我在網上看了大半天,怎麼叫的都有,後來查了MSDN,MSDN上叫代碼遷移。在此,我們也稱之為代碼遷移。
為什麼有人將其稱為數據遷移呢?可能是因為本節內容和操作數據庫有關<增加一張表,刪除一張表,增加一個表字段,刪除一個表字段,修改一個表字段等>,所以網友稱之為數據遷移
MSDN等權威結構為什麼稱之為代碼遷移呢?可能是因為本節的內容通過我們熟知的C#代碼就可以搞定,所以稱之為代碼遷移
嘻嘻,上邊是我的猜測,人家可跳過
正文:
背景:我們為什麼要使用代碼遷移?
我們使用CodeFirst初始化數據庫無非以下三種方式:
一、 每次運行都會創建新的數據庫
Database.SetInitializer<XXXXXContext>(new DropCreateDatabaseAlways<XXXXXContext>());
二、只有第一次運行~才會創建新的數據庫~默認的方式
Database.SetInitializer<XXXXXContext>(new CreateDatabaseIfNotExists<XXXXXContext>());
三、 修改模型後~運行~會創建新的數據庫
Database.SetInitializer<XXXXXContext>(new DropCreateDatabaseIfModelChanges<XXXXXContext>());
不論我們使用哪種方式,當條件成立時,都會創建新的數據庫,而創建新的數據庫就意味著‘從頭再來’
舉個例子:如果您的團隊開發的項目已經被客戶使用,客戶在實際的生產操作中已經創建了很多數據,如果,某一天客戶更改需求,我們難不成要采用上述方式將數據庫重建?一旦重建,客戶之前的數據就會丟失,這是任何一個客戶不能容忍的
為了解決這個問題,我們要引入代碼遷移
在進行代碼遷移之前,我們必須做好數據庫備份工作,確保客戶數據的安全性及完整性
現在我們引入數據遷移示例:
我們有以下模型類:
Student模型類
public class Student { [Key] public int Id { get; set; } [Required] [StringLength(10)] public string Name { get; set; }//姓名 [StringLength(2)] public string Sex { get; set; }//性別 [Unique(ErrorMessage="學號不允許重復")] [StringLength(18)] public string StudentNum { get; set; }//學號 [StringLength(100)] public string StudentAddress { get; set; }//地址 }
Course模型類
public class Course { [Key] public int Id { get; set; } [Required] [StringLength(20)] public string Name { get; set; }//課程名稱
}
Score模型類
[Key] public int Id { get; set; } public int StudentScore { get; set; }//學生分數 public int StudentID { get; set; }//學生ID public int CourseID { get; set; }//課程ID public virtual Student Student { get; set; }//virtual關鍵字修飾,用於延遲加載 提高性能 只有顯式調用時 才會加載 並可以代表一個Student對象 也就是 屬性==對象 public virtual Course Course { get; set; }//virtual關鍵字修飾,用於延遲加載 提高性能 只有顯式調用時 才會加載 並可以代表一個Course對象 也就是 屬性==對象
StudentContext上下文
public class StudentContext : DbContext { public StudentContext() : base("StudentContext")//指定連接字符串 { } public DbSet<Student> Students { get; set; } public DbSet<Course> Courses { get; set; } public DbSet<Score> Scores { get; set; } /// <summary> /// OnModelCreating方法中的modelBuilder.Conventions.Remove語句禁止表名稱正在多元化。如果你不這樣做,所生成的表將命名為Students、Courses和Enrollments。相反,表名稱將是Student、Course和Enrollment。開發商不同意關於表名稱應該多數。本教程使用的是單數形式,但重要的一點是,您可以選擇哪個你更喜歡通過包括或省略這行代碼的形式。 /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }
現在客戶提出如下需求:在確保數據完整性的情況下,添加一張學校表<字段:學校名稱、學校地址,學校熱線>及增加學生聯系方式字段
首先,我們添加學校模型類:
public class School//新建一張學校表-----------------------------新建的表 { [Key] public int schoolId { get; set; } [Required] [StringLength(10)] public string schoolName { get; set; }//學校名稱 [StringLength(100)] public string StudentAddress { get; set; }//學校地址 [StringLength(20)] public string StudentTel { get; set; }//學校熱線 }
修改學生模型類如下:
public class Student { [Key] public int Id { get; set; } [Required] [StringLength(10)] public string Name { get; set; }//姓名 [StringLength(2)] public string Sex { get; set; }//性別 [Unique(ErrorMessage="學號不允許重復")] [StringLength(18)] public string StudentNum { get; set; }//學號 [StringLength(100)] public string StudentAddress { get; set; }//地址 [StringLength(11)] public string StudentTel { get; set; }//聯系電話----------------------------新加的字段 }
上下文類加上DBset<School>如下:
public class StudentContext : DbContext { public StudentContext() : base("StudentContext")//指定連接字符串 { } public DbSet<Student> Students { get; set; } public DbSet<Course> Courses { get; set; } public DbSet<Score> Scores { get; set; } public DbSet<School> Schools { get; set; }//新加一張表 /// <summary> /// OnModelCreating方法中的modelBuilder.Conventions.Remove語句禁止表名稱正在多元化。如果你不這樣做,所生成的表將命名為Students、Courses和Enrollments。相反,表名稱將是Student、Course和Enrollment。開發商不同意關於表名稱應該多數。本教程使用的是單數形式,但重要的一點是,您可以選擇哪個你更喜歡通過包括或省略這行代碼的形式。 /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }
下面,我們進行代碼遷移<在進行代碼遷移之前,您必須重新生成您的項目>:
首先,打開程序包管理器控制台
在PM>
提示符下輸入以下命令︰
enable-migrations
add-migration InitialCreate
解釋下這兩個命令的含義:enable-migrations 是指啟動遷移,此命令輸入後,系統會檢查上下文目標 add-migration InitialCreate
創建名稱為:時間戳_InitialCreate.CS的文件,其中時間戳是根據當前時間點生成的。
我們得到如下信息
至此,我們在項目中會發現多出一個名為:Migrations的文件夾,文件夾內有兩個文件:201612080727320_InitialCreate.cs 和 Configuration.cs
下面介紹下這兩個文件的作用:
Configuration.cs 中有個Seed方法,您每次代碼遷移執行時,都會執行seed()方法,您可以通過Seed方法增加一些必要的數據。例如,上述我們增加了一張學校模型類,在代碼遷移時,我們希望插入一條學校數據,我們可以這樣修改seed()方法:
下面是我修改後的Seed方法:
namespace EF_Test.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; using EF_Test.DAL; internal sealed class Configuration : DbMigrationsConfiguration<EF_Test.DAL.StudentContext> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(EF_Test.DAL.StudentContext context) { context.Schools.AddOrUpdate( p => p.schoolId, new School { schoolName = "河南城建學院", StudentAddress = "平頂山市新華區" , StudentTel="0375-88888888" } ); context.SaveChanges(); } } }
201612080727320_InitialCreate.cs 或者稱為:時間戳_InitialCreate.cs 是干嘛用的呢?我們來看看生成的代碼,如下:
namespace EF_Test.Migrations { using System; using System.Data.Entity.Migrations; public partial class InitialCreate : DbMigration { public override void Up() { CreateTable( "dbo.Course", c => new { Id = c.Int(nullable: false, identity: true), Name = c.String(nullable: false, maxLength: 20), }) .PrimaryKey(t => t.Id); CreateTable( "dbo.Score", c => new { Id = c.Int(nullable: false, identity: true), StudentScore = c.Int(nullable: false), StudentID = c.Int(nullable: false), CourseID = c.Int(nullable: false), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true) .ForeignKey("dbo.Student", t => t.StudentID, cascadeDelete: true) .Index(t => t.StudentID) .Index(t => t.CourseID); CreateTable( "dbo.Student", c => new { Id = c.Int(nullable: false, identity: true), Name = c.String(nullable: false, maxLength: 10), Sex = c.String(maxLength: 2), StudentNum = c.String(maxLength: 18), StudentAddress = c.String(maxLength: 100), StudentTel = c.String(maxLength: 11), }) .PrimaryKey(t => t.Id); } public override void Down() { DropForeignKey("dbo.Score", "StudentID", "dbo.Student"); DropForeignKey("dbo.Score", "CourseID", "dbo.Course"); DropIndex("dbo.Score", new[] { "CourseID" }); DropIndex("dbo.Score", new[] { "StudentID" }); DropTable("dbo.Student"); DropTable("dbo.Score"); DropTable("dbo.Course"); } } }
有兩個方法,Up()和Down() 都是些創建表,刪除主鍵,刪除表的一些動作。我們先不管這些,繼續我們的代碼遷移:
在PM>
提示符下輸入以下命令︰
Update-Database 或者 Update-Database -Verbose 前者僅僅是更新數據庫,不會在程序包管理器控制台中輸出執行的SQL語句,而後者則可以輸出執行的SQL語句,我們姑且使用後者吧
執行失敗了,原因也很明確,是因為我們現有的數據庫中已經存在Course等表,再次創建會發生異常,怎麼辦?
我們翻開 201612080727320_InitialCreate.cs 這個文件,將此文件作如下修改:
public override void Up() { CreateTable( "dbo.School", c => new { schoolId = c.Int(nullable: false, identity: true), schoolName = c.String(nullable: false, maxLength: 10), StudentAddress = c.String(maxLength: 100), StudentTel = c.String(maxLength: 20), }) .PrimaryKey(t => t.schoolId); } public override void Down() { DropTable("dbo.School"); }
項目生成成功後,再次執行:Update-Database -Verbose
萬事大吉,執行成功,而且執行了Seed方法,那麼我們的數據庫中應該有一張名為 School 的表,並且裡面有一條數據,如下:
學校模型創建成功了,但是,學生表中添加的字段加上去了麼?
根據上圖,學生表結構並沒有加上StudentTel一列,為何?我們該怎麼辦?
在此,我們執行:add-migration 命令 add-migration 命令和 add-migration InitialCreate 命令的不同是一個指定了後綴名稱,一個沒有指定,需要我們自定義!
根據上圖,我們創建了一個後綴為:SshoolCreate的文件,如下:
雙擊文件,我們發現裡面沒什麼內容,我們添加如下代碼:
public partial class SshoolCreate : DbMigration { public override void Up() { AddColumn("dbo.Student", "StudentTel", c => c.String(maxLength: 11)); } public override void Down() { DropColumn("dbo.Student", "StudentTel"); } }
繼續執行:update-database 命令
執行成功,我們到數據庫看看
字段StudentTel已經加入了表Student
至此,EF 代碼遷移也就講完了,上文比較亂,最後做個總結:
1、 數據庫中有張名為:__MigrationHistory的表,主要用於代碼遷移記錄
每當你成功遷移一次,都會生成一條記錄。
2、上文中提到:時間戳_InitialCreate.CS 文件,如果在項目中有多個同類文件,項目會執行距離當前時間最近的 時間戳_InitialCreate.CS,如果距離當前時間最近的 時間戳_InitialCreate.CS 在表__MigrationHistory中有記錄,則不會執行!
3、其實代碼遷移用到的命令也就那幾個,在此總結下:
enable-migrations : 啟動代碼遷移並生成Configuration.CS文件
add-migration : 啟動生成 時間戳_Name.CS文件,類似於:201612080802330_Name.CS這種文件,需要指定Name,用於多次遷移
add-migration InitialCreate : 啟動生成類似於:201612080802330_InitialCreate.CS這種文件
Update-Database -Verbose 和 Update-Database 執行代碼遷移,修改數據庫表結構。 前者會在執行過程中生成相應的SQL語句
4、常用的C#代碼:
創建一張表:
public partial class InitialCreate : DbMigration { public override void Up() { CreateTable( "dbo.School", c => new { schoolId = c.Int(nullable: false, identity: true), schoolName = c.String(nullable: false, maxLength: 10), StudentAddress = c.String(maxLength: 100), StudentTel = c.String(maxLength: 20), }) .PrimaryKey(t => t.schoolId); } public override void Down() { DropTable("dbo.School"); }
刪除一張表:
public partial class DropTable : DbMigration { public override void Up() { DropTable("dbo.School"); } public override void Down() { CreateTable( "dbo.School", c => new { schoolId = c.Int(nullable: false, identity: true), schoolName = c.String(nullable: false, maxLength: 10), StudentAddress = c.String(maxLength: 100), StudentTel = c.String(maxLength: 20), }) .PrimaryKey(t => t.schoolId); } }
修改字段長度或字段名稱
public partial class EEE : DbMigration { public override void Up() { AddColumn("dbo.Student", "StudentPhone", c => c.String(maxLength: 11)); DropColumn("dbo.Student", "StudentTel"); } public override void Down() { AddColumn("dbo.Student", "StudentTel", c => c.String(maxLength: 11)); DropColumn("dbo.Student", "StudentPhone"); } }
增加表字段:
public partial class FFF : DbMigration { public override void Up() { AddColumn("dbo.Student", "StudentPhowwwwne", c => c.String(maxLength: 11)); } public override void Down() { DropColumn("dbo.Student", "StudentPhowwwwne"); } }
刪除表字段:
public partial class GGG : DbMigration { public override void Up() { DropColumn("dbo.Student", "StudentPhowwwwne"); } public override void Down() { AddColumn("dbo.Student", "StudentPhowwwwne", c => c.String(maxLength: 11)); } }
等等吧,上述代碼都能生成。
5、如果您生成了一個新的 時間戳_XXXX.CS文件,請務必使用Update-DataBase命令執行,否則,您將永遠不會生成下一個 時間戳_XXXX.CS文件,究其原因,是因為在數據表[__MigrationHistory]中沒有記錄。
最新的[__MigrationHistory]數據
根據上述數據,就證明我做了五次代碼遷移了!
好了,本節的內容也就講完了,謝謝大家的耐心查閱!
@陳臥龍的博客