天天看點

Entity Framework(六):資料遷移

       在前面的幾篇文章中,簡單的介紹了如何使用Entity Framework的Code First模式建立資料庫,但是,在前面的幾篇文章中,我們都是通過使用資料庫初始化政策來做,也就是每次先删除資料庫然後在建立,這樣才能把新增加的字段資訊更新到資料庫,在測試的時候可以做,但是在正式的生産環境中就不能使用這種方式了,那麼我們如何做才能在原有的資料庫基礎上進行字段的增删,這就需要使用到EF的資料遷移技術,使用EF的資料遷移技術,我們不用每次都先删除資料庫然後在建立,并且資料庫遷移技術還可以為我們設定初始化的資料。在這篇文章中,将會示範“在實體類發生改變時如何自動更新資料庫中的表結構”

一、合并和遷移

1、合并

合并是指“新的實體模型映射到資料庫中,更新其結構”,例如:

新增了實體類,表現在資料庫中就是新增加實體類對應的資料表。

删除了實體類,表現在資料庫中就是删除了實體類對應的資料表。

在一個已經存在的實體類中增加屬性,表現在資料庫中就是在實體類對應的資料表中新增加字段。

在一個已經存在的實體類中删除屬性,表現在資料庫中就是在實體類對應的資料表中删除字段。

修改一個已經存在的實體類中屬性的名稱或類型,表現在資料庫中就是修改實體類對應的資料表中字段的名稱或類型。

2、遷移

遷移是指“在更新資料庫結構時,把老結構中的資料遷移到新結構中”。

二、遷移前的準備工作

搭建項目結構,整體的項目結構包括一個控制台應用程式和兩個類庫,項目結構如下:

Entity Framework(六):資料遷移

其中EF.Application是控制台程式,EF.FluentAPI和EF.Model是類型,EF.Model裡面存的是實體類,EF.FluentAPI是實體類的Map類,用來設定FluentAPI。

Student類結構如下:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 StudentMap類結構如下:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 EF上下文類結構:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 資料庫連接配接字元串:【注意:在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>      

 控制台程式:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 運作程式:

Entity Framework(六):資料遷移

檢視資料庫:

Entity Framework(六):資料遷移

其中生成的表__MigrationHistory用來記錄每次的遷移。

三、遷移

現在我們在Student實體類中增加Grade字段,整體項目做如下的改動:

Student類:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 StudentMap類:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 控制台:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 啟用資料遷移:

1、打開遷移

在程式包管理器控制台中輸入:Enable-Migrations

Entity Framework(六):資料遷移

按Enter鍵後,會生成Migrations檔案夾,以及Migrations檔案夾下面的Configuration類和201711281316287_InitialCreate類:

Entity Framework(六):資料遷移

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

Entity Framework(六):資料遷移

一個新的遷移(201711281402492_AddGrade)在目錄Migrations中建立成功:

Entity Framework(六):資料遷移

201711281402492_AddGrade類結構如下:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 201711281402492_AddGrade的名稱是上面Add後面定義的遷移名稱,而類下面有兩個方法:一個是Up,一個是Down,記錄了需要更新的修改,這裡也就是Students表增加了Grade列。隻要我們在後面執行Update-Database,就會執行此類下面的Up函數。

這裡的Down函數簡單介紹就是:為了復原修改而設計的。如果使用者希望恢複到某一個遷移節點,程式會自動根據已經執行的遷移,判斷復原哪些遷移,執行他們的Down函數。

3、更新資料庫

在程式包管理器控制台中輸入指令:Update-Database -Verbose

Entity Framework(六):資料遷移

檢視資料庫,Students表已經增加Grade字段:

Entity Framework(六):資料遷移

到此為止,遷移已經完成,再次運作項目:

Entity Framework(六):資料遷移

檢視資料庫:

Entity Framework(六):資料遷移

輸入的資料儲存到資料庫中。

四、修改屬性

1、将Student實體類中的Grade屬性的名稱修改為GradeTest:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 2、增加遷移節點

在程式包管理器控制台中輸入指令:Add-Migration ModifyGrade

Entity Framework(六):資料遷移

在Migrations檔案夾下面會生成本次的遷移記錄:201711290052153_ModifyGrade

Entity Framework(六):資料遷移

檢視201711290052153_ModifyGrade類結構:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 可以看到在Up方法裡面,它不是直接修改了列的名稱,而是先增加了一個新列GradeTest,然後删除舊列Grade。這樣執行會有一個後果:如果Grade列裡面有資料,資料會全部丢失。

3、更新到資料庫

在程式包管理器控制台中輸入指令:Update-Database -Verbose

Entity Framework(六):資料遷移

檢視資料庫表:

Entity Framework(六):資料遷移

通過檢視資料庫表,會發現新增加了GradeTest列,原先的Grade列被删掉,資料也全部丢失。

五、删除屬性

删除屬性和增加屬性的操作差不多

1、修改Student實體類,注釋掉GradeTest屬性:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 2、增加遷移節點

在程式包管理器控制台中輸入指令:Add-Migration DeleteGradeTest

Entity Framework(六):資料遷移

檢視生成的遷移記錄類:

Entity Framework(六):資料遷移

201711290130110_DeleteGradeTest類裡面的Up方法裡面删除了GradeTest列。

3、更新到資料庫

在程式包管理器控制台中輸入指令:Update-Database -Verbose

Entity Framework(六):資料遷移

檢視資料庫表,可以發現GradeTest列被删除掉:

Entity Framework(六):資料遷移

六、遷移至指定的版本(包括後退)

到目前為止,我們進行遷移都是進行更新,但是有些時候我們需要更新或降級至指定版本,例如我們想遷移資料庫至運作ModifyGrade遷移之後的狀态,此時我們就可以使用-TargetMigration來降級到這個版本。

在程式包管理器控制台中輸入指令:Update-Database -TargetMigration:ModifyGrade

Entity Framework(六):資料遷移

這個指令将會運作201711290130110_DeleteGradeTest類裡面的Down指令。Reverting migrations表示回複遷移。

這時候在檢視資料庫表,會發現Students表中又有了GradeTest列。

如果你想復原一切至空資料庫,可以使用指令:Update-Database -TargetMigration:$InitialDatabase

Entity Framework(六):資料遷移

這時候在檢視資料庫,發現Students表中所有列都已經被删除:

Entity Framework(六):資料遷移

七、如何在保留現有資料的基礎上修改列名

檢視DbMigration類,會發現該類下面有一個RenameColumn()的方法,使用該方法可以在不丢失資料的基礎上修改列的名稱:

Entity Framework(六):資料遷移

1、修改Student實體類,将StudentName修改為Name。

2、在程式包管理器控制台中輸入指令:Add-Migration RenameStudentName,生成遷移檔案,手動修改遷移類檔案,修改内容如下:

Entity Framework(六):資料遷移
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 }      
Entity Framework(六):資料遷移

 3、執行Update-Database指令,資料庫列名被自動修改。

Entity Framework(六):資料遷移

這裡值得注意的是:在執行Update指令時,程式會提醒操作者: Changing any part of an object name could break scripts and stored procedures。翻譯為中文:更改對象名的任一部分都可能會破壞腳本和存儲過程。及修改列名可能會導緻存儲過程及其他調用列的sql腳本失效。

檢視資料庫表發現列名已經修改:

Entity Framework(六):資料遷移

注意:在實際開發中,不建議随便修改列名:可能會導緻其他用的該列的地方調用失敗。

總結:

1、遷移的關聯在資料庫的遷移曆史表__MigrationHistory和項目的Migrations檔案夾下的繼承了DbMigration的cs檔案。

2、Migrations檔案夾下的繼承了DbMigration的cs檔案可以手動修改,這裡的修改可以非常靈活,表格和表格字段的增删改,在這裡都有。

代碼下載下傳位址:https://pan.baidu.com/s/1eR9RJ0Y