在前面的幾篇文章中,簡單的介紹了如何使用Entity Framework的Code First模式建立資料庫,但是,在前面的幾篇文章中,我們都是通過使用資料庫初始化政策來做,也就是每次先删除資料庫然後在建立,這樣才能把新增加的字段資訊更新到資料庫,在測試的時候可以做,但是在正式的生産環境中就不能使用這種方式了,那麼我們如何做才能在原有的資料庫基礎上進行字段的增删,這就需要使用到EF的資料遷移技術,使用EF的資料遷移技術,我們不用每次都先删除資料庫然後在建立,并且資料庫遷移技術還可以為我們設定初始化的資料。在這篇文章中,将會示範“在實體類發生改變時如何自動更新資料庫中的表結構”
一、合并和遷移
1、合并
合并是指“新的實體模型映射到資料庫中,更新其結構”,例如:
新增了實體類,表現在資料庫中就是新增加實體類對應的資料表。
删除了實體類,表現在資料庫中就是删除了實體類對應的資料表。
在一個已經存在的實體類中增加屬性,表現在資料庫中就是在實體類對應的資料表中新增加字段。
在一個已經存在的實體類中删除屬性,表現在資料庫中就是在實體類對應的資料表中删除字段。
修改一個已經存在的實體類中屬性的名稱或類型,表現在資料庫中就是修改實體類對應的資料表中字段的名稱或類型。
2、遷移
遷移是指“在更新資料庫結構時,把老結構中的資料遷移到新結構中”。
二、遷移前的準備工作
搭建項目結構,整體的項目結構包括一個控制台應用程式和兩個類庫,項目結構如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIx0DciV2dmADM30zd-cmbw5CRzUCRzUydaVnQuxEMNRkT3lkeNBTQ65EewkmTxsGROl3ZU50dJR0T5VEVNNTRE1UewM0T6NmeNpXQU1kdFRVTzUERNlHOD9kejpXT6FEVNZ3YyI2cKJDT0ljMZVXTzold41WW15UbMRTRE1UeNhlWuZ0ViBXO5xkNNh0YwIFSh9CXt92YuM3YltWas5iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.png)
其中EF.Application是控制台程式,EF.FluentAPI和EF.Model是類型,EF.Model裡面存的是實體類,EF.FluentAPI是實體類的Map類,用來設定FluentAPI。
Student類結構如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace EF.Model
8 {
9 public class Student
10 {
11 public int StudentID { get; set; }
12
13 public string StudentName { get; set; }
14
15 public int Age { get; set; }
16
17 public string Sex { get; set; }
18 }
19 }
StudentMap類結構如下:
1 using EF.Model;
2 using System;
3 using System.Collections.Generic;
4 using System.ComponentModel.DataAnnotations.Schema;
5 using System.Data.Entity.ModelConfiguration;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9
10 namespace EF.FluentAPI
11 {
12 /// <summary>
13 /// 使用FluentAPI配置
14 /// </summary>
15 public class StudentMap :EntityTypeConfiguration<Student>
16 {
17 public StudentMap()
18 {
19 // 配置資料庫中生成的表的名稱
20 this.ToTable("Students");
21 // 設定StudentID列自動增長
22 this.Property(p => p.StudentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
23 // 設定StudentID列作為主鍵
24 this.HasKey(p => p.StudentID);
25 // 設定StudentName列的類型是nvarchar,最大長度是50,必須的
26 this.Property(p => p.StudentName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired();
27 // 設定Age列是必須的
28 this.Property(p => p.Age).IsRequired();
29 // 設定Sex的類型是nvarchar
30 this.Property(p => p.Sex).HasColumnType("nvarchar").IsRequired();
31 }
32 }
33 }
EF上下文類結構:
1 using System;
2 using System.Collections.Generic;
3 using System.Data.Entity;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7
8 namespace EF.FluentAPI
9 {
10 public class EFDbContext:DbContext
11 {
12 public EFDbContext()
13 : base("name=CodeFirstApplication")
14 { }
15
16 protected override void OnModelCreating(DbModelBuilder modelBuilder)
17 {
18 modelBuilder.Configurations.Add(new StudentMap());
19 }
20 }
21 }
資料庫連接配接字元串:【注意:在EF.Application和EF.FluentAPI的App.config裡面都要添加上該連接配接字元串】
1 <connectionStrings>
2 <add name="CodeFirstApplication" connectionString="Server=.;Database=MigrationsDB;User Id=sa;Password=1qaz@WSX" providerName="System.Data.SqlClient"/>
3 </connectionStrings>
控制台程式:
1 using EF.FluentAPI;
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7 using EF.Model;
8
9 namespace EF.Application
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 Console.WriteLine("請輸入學生姓名:");
16 string studentName = Console.ReadLine().Trim();
17 Console.WriteLine("請輸入學生年齡:");
18 int age = 0;
19 if (!int.TryParse(Console.ReadLine().Trim(), out age))
20 {
21 Console.WriteLine("年齡隻能輸入正整數,請重新輸入:");
22 return;
23 }
24 Console.WriteLine("請輸入學生性别(男/女):");
25 string sex = Console.ReadLine().Trim();
26
27 using (var context = new EFDbContext())
28 {
29 Student student = new Student()
30 {
31 StudentName=studentName,
32 Age=age,
33 Sex=sex
34 };
35
36 context.Entry(student).State = System.Data.Entity.EntityState.Added;
37 // 儲存
38 context.SaveChanges();
39 }
40
41 Console.Write("添加成功");
42 Console.ReadKey();
43 }
44 }
45 }
運作程式:
檢視資料庫:
其中生成的表__MigrationHistory用來記錄每次的遷移。
三、遷移
現在我們在Student實體類中增加Grade字段,整體項目做如下的改動:
Student類:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace EF.Model
8 {
9 public class Student
10 {
11 public int StudentID { get; set; }
12
13 public string StudentName { get; set; }
14
15 public int Age { get; set; }
16
17 public string Sex { get; set; }
18
19 // 新增加Grade字段,用來實作資料遷移
20 public string Grade { get; set; }
21 }
22 }
StudentMap類:
1 using EF.Model;
2 using System;
3 using System.Collections.Generic;
4 using System.ComponentModel.DataAnnotations.Schema;
5 using System.Data.Entity.ModelConfiguration;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9
10 namespace EF.FluentAPI
11 {
12 /// <summary>
13 /// 使用FluentAPI配置
14 /// </summary>
15 public class StudentMap :EntityTypeConfiguration<Student>
16 {
17 public StudentMap()
18 {
19 // 配置資料庫中生成的表的名稱
20 this.ToTable("Students");
21 // 設定StudentID列自動增長
22 this.Property(p => p.StudentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
23 // 設定StudentID列作為主鍵
24 this.HasKey(p => p.StudentID);
25 // 設定StudentName列的類型是nvarchar,最大長度是50,必須的
26 this.Property(p => p.StudentName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired();
27 // 設定Age列是必須的
28 this.Property(p => p.Age).IsRequired();
29 // 設定Sex的類型是nvarchar
30 this.Property(p => p.Sex).HasColumnType("nvarchar").IsRequired();
31 // 設定Grade字段是必須的
32 this.Property(p => p.Grade).HasColumnType("varchar").HasMaxLength(16).IsRequired();
33 }
34 }
35 }
控制台:
1 using EF.FluentAPI;
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Text;
6 using System.Threading.Tasks;
7 using EF.Model;
8
9 namespace EF.Application
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 Console.WriteLine("請輸入學生姓名:");
16 string studentName = Console.ReadLine().Trim();
17 Console.WriteLine("請輸入學生年齡:");
18 int age = 0;
19 if (!int.TryParse(Console.ReadLine().Trim(), out age))
20 {
21 Console.WriteLine("年齡隻能輸入正整數,請重新輸入:");
22 return;
23 }
24 Console.WriteLine("請輸入學生性别(男/女):");
25 string sex = Console.ReadLine().Trim();
26 Console.WriteLine("請輸入年級:");
27 string grade = Console.ReadLine().Trim();
28
29 using (var context = new EFDbContext())
30 {
31 Student student = new Student()
32 {
33 StudentName=studentName,
34 Age=age,
35 Sex=sex,
36 Grade=grade
37 };
38
39 context.Entry(student).State = System.Data.Entity.EntityState.Added;
40 // 儲存
41 context.SaveChanges();
42 }
43
44 Console.Write("添加成功");
45 Console.ReadKey();
46 }
47 }
48 }
啟用資料遷移:
1、打開遷移
在程式包管理器控制台中輸入:Enable-Migrations
按Enter鍵後,會生成Migrations檔案夾,以及Migrations檔案夾下面的Configuration類和201711281316287_InitialCreate類:
Configuration:這個類允許你去配置如何遷移,對于本文将使用預設的配置(在本文中因為隻有一個Context,Enable-Migrations将自動對context type作出适配);
201711281316287_InitialCreate:這個遷移之是以存在是因為我們之前用Code First建立了資料庫,在啟用遷移之前,scaffolded migration裡面的代碼表示在資料庫中已經建立的對象,本文中即為表Students。
Code First Migrations有兩個需要熟悉的指令:
Add-Migration 将scaffold建立下一次基于上一次遷移以來的更改的遷移;
Update-Database 将任何挂起的遷移應用到資料庫;
以上面新增加的字段Grade屬性為例,指令Add-Migration允許我們對遷移進行命名,我們把遷移命名為AddGrade。
2、增加遷移節點
在程式包管理器控制台中輸入指令:Add-Migration AddGrade
一個新的遷移(201711281402492_AddGrade)在目錄Migrations中建立成功:
201711281402492_AddGrade類結構如下:
1 namespace EF.FluentAPI.Migrations
2 {
3 using System;
4 using System.Data.Entity.Migrations;
5
6 public partial class AddGrade : DbMigration
7 {
8 public override void Up()
9 {
10 AddColumn("dbo.Students", "Grade", c => c.String(nullable: false, maxLength: 16, unicode: false));
11 }
12
13 public override void Down()
14 {
15 DropColumn("dbo.Students", "Grade");
16 }
17 }
18 }
201711281402492_AddGrade的名稱是上面Add後面定義的遷移名稱,而類下面有兩個方法:一個是Up,一個是Down,記錄了需要更新的修改,這裡也就是Students表增加了Grade列。隻要我們在後面執行Update-Database,就會執行此類下面的Up函數。
這裡的Down函數簡單介紹就是:為了復原修改而設計的。如果使用者希望恢複到某一個遷移節點,程式會自動根據已經執行的遷移,判斷復原哪些遷移,執行他們的Down函數。
3、更新資料庫
在程式包管理器控制台中輸入指令:Update-Database -Verbose
檢視資料庫,Students表已經增加Grade字段:
到此為止,遷移已經完成,再次運作項目:
檢視資料庫:
輸入的資料儲存到資料庫中。
四、修改屬性
1、将Student實體類中的Grade屬性的名稱修改為GradeTest:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace EF.Model
8 {
9 public class Student
10 {
11 public int StudentID { get; set; }
12
13 public string StudentName { get; set; }
14
15 public int Age { get; set; }
16
17 public string Sex { get; set; }
18
19 // 将Grade屬性名修改為GradeTest
20 public string GradeTest { get; set; }
21 }
22 }
2、增加遷移節點
在程式包管理器控制台中輸入指令:Add-Migration ModifyGrade
在Migrations檔案夾下面會生成本次的遷移記錄:201711290052153_ModifyGrade
檢視201711290052153_ModifyGrade類結構:
1 namespace EF.FluentAPI.Migrations
2 {
3 using System;
4 using System.Data.Entity.Migrations;
5
6 public partial class ModifyGrade : DbMigration
7 {
8 public override void Up()
9 {
10 AddColumn("dbo.Students", "GradeTest", c => c.String(nullable: false, maxLength: 16, unicode: false));
11 DropColumn("dbo.Students", "Grade");
12 }
13
14 public override void Down()
15 {
16 AddColumn("dbo.Students", "Grade", c => c.String(nullable: false, maxLength: 16, unicode: false));
17 DropColumn("dbo.Students", "GradeTest");
18 }
19 }
20 }
可以看到在Up方法裡面,它不是直接修改了列的名稱,而是先增加了一個新列GradeTest,然後删除舊列Grade。這樣執行會有一個後果:如果Grade列裡面有資料,資料會全部丢失。
3、更新到資料庫
在程式包管理器控制台中輸入指令:Update-Database -Verbose
檢視資料庫表:
通過檢視資料庫表,會發現新增加了GradeTest列,原先的Grade列被删掉,資料也全部丢失。
五、删除屬性
删除屬性和增加屬性的操作差不多
1、修改Student實體類,注釋掉GradeTest屬性:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace EF.Model
8 {
9 public class Student
10 {
11 public int StudentID { get; set; }
12
13 public string StudentName { get; set; }
14
15 public int Age { get; set; }
16
17 public string Sex { get; set; }
18
19 // 将GradeTest屬性删除
20 //public string GradeTest { get; set; }
21 }
22 }
2、增加遷移節點
在程式包管理器控制台中輸入指令:Add-Migration DeleteGradeTest
檢視生成的遷移記錄類:
201711290130110_DeleteGradeTest類裡面的Up方法裡面删除了GradeTest列。
3、更新到資料庫
在程式包管理器控制台中輸入指令:Update-Database -Verbose
檢視資料庫表,可以發現GradeTest列被删除掉:
六、遷移至指定的版本(包括後退)
到目前為止,我們進行遷移都是進行更新,但是有些時候我們需要更新或降級至指定版本,例如我們想遷移資料庫至運作ModifyGrade遷移之後的狀态,此時我們就可以使用-TargetMigration來降級到這個版本。
在程式包管理器控制台中輸入指令:Update-Database -TargetMigration:ModifyGrade
這個指令将會運作201711290130110_DeleteGradeTest類裡面的Down指令。Reverting migrations表示回複遷移。
這時候在檢視資料庫表,會發現Students表中又有了GradeTest列。
如果你想復原一切至空資料庫,可以使用指令:Update-Database -TargetMigration:$InitialDatabase
這時候在檢視資料庫,發現Students表中所有列都已經被删除:
七、如何在保留現有資料的基礎上修改列名
檢視DbMigration類,會發現該類下面有一個RenameColumn()的方法,使用該方法可以在不丢失資料的基礎上修改列的名稱:
1、修改Student實體類,将StudentName修改為Name。
2、在程式包管理器控制台中輸入指令:Add-Migration RenameStudentName,生成遷移檔案,手動修改遷移類檔案,修改内容如下:
1 namespace EF.FluentAPI.Migrations
2 {
3 using System;
4 using System.Data.Entity.Migrations;
5
6 public partial class RenameStudentName : DbMigration
7 {
8 public override void Up()
9 {
10 //AddColumn("dbo.Students", "Name", c => c.String(nullable: false, maxLength: 50));
11 AddColumn("dbo.Students", "GradeTest", c => c.String(nullable: false, maxLength: 16, unicode: false));
12 //DropColumn("dbo.Students", "StudentName");
13 RenameColumn("dbo.Students", "StudentName", "Name");
14 }
15
16 public override void Down()
17 {
18 //AddColumn("dbo.Students", "StudentName", c => c.String(nullable: false, maxLength: 50));
19 DropColumn("dbo.Students", "GradeTest");
20 //DropColumn("dbo.Students", "Name");
21 RenameColumn("dbo.Students", "Name", "StudentName");
22 }
23 }
24 }
3、執行Update-Database指令,資料庫列名被自動修改。
這裡值得注意的是:在執行Update指令時,程式會提醒操作者: Changing any part of an object name could break scripts and stored procedures。翻譯為中文:更改對象名的任一部分都可能會破壞腳本和存儲過程。及修改列名可能會導緻存儲過程及其他調用列的sql腳本失效。
檢視資料庫表發現列名已經修改:
注意:在實際開發中,不建議随便修改列名:可能會導緻其他用的該列的地方調用失敗。
總結:
1、遷移的關聯在資料庫的遷移曆史表__MigrationHistory和項目的Migrations檔案夾下的繼承了DbMigration的cs檔案。
2、Migrations檔案夾下的繼承了DbMigration的cs檔案可以手動修改,這裡的修改可以非常靈活,表格和表格字段的增删改,在這裡都有。
代碼下載下傳位址:https://pan.baidu.com/s/1eR9RJ0Y