[EF Core 3] 如何使用 Code First 的 Migration

Code First 的 Migration 指令很簡單,在使用那些命令時,有時候成功,有時候失敗,不知道為什麼,有這種困擾的人,繼續往下閱讀

開發環境

  • VS 2019
  • EF Core 3

分層架構

專案按照架構分層

Lab.DAL:專案範本 Class Libaray(.NET Core),DbContext 放在這裡,連線字串放在 appsettings.json

Lab.DAL.UnitTest:這裡是應用程式的進入點,可以換成你的 Web、Desktop,連線字串放在 appsettings.json

 

實作

安裝套件

Visual Studio PowerShell:

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.Extensions.Configuration.Json
Install-Package Microsoft.Extensions.Logging.Console

.NET Core CLI:

dotnet add package  Microsoft.EntityFrameworkCore.SqlServer
dotnet add package  Microsoft.EntityFrameworkCore.Tools
dotnet add package  Microsoft.Extensions.Configuration.Json
dotnet add package  Microsoft.Extensions.Logging.Console

建立POCO

https://github.com/yaochangyu/sample.dotblog/tree/master/ORM/EFCore/EF%20Core%20Migration/Lab.DAL/EntityModel

    public class LabEmployeeContext : DbContext
    {
        private static readonly bool[] s_migrated = {false};
 
        public virtual DbSet<Employee> Employee { get; set; }
 
        public virtual DbSet<Identity> Identity { get; set; }
 
        public virtual DbSet<Order> Order { get; set; }
 
        public LabEmployeeContext()
        {
            
        }
 
        public LabEmployeeContext(DbContextOptions<LabEmployeeContext> options)
            : base(options)
        {
            if (options == null)
            {
                options = DbOptionsFactory.DbContextOptions;
            }
 
            if (!s_migrated[0])
            {
                lock (s_migrated)
                {
                    if (!s_migrated[0])
                    {
                        this.Database.Migrate();
                        s_migrated[0] = true;
                    }
                }
            }
        }
 
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //var connectionString = "Server=(localdb)\\mssqllocaldb;Database=LabEmployee.DAL;Trusted_Connection=True;";
            var connectionString = DbOptionsFactory.ConnectionString;
            if (!optionsBuilder.IsConfigured)
            {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
                optionsBuilder
                    .UseSqlServer(connectionString);
            }
        }
    }

 

[Table("Identity")]
public class Identity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Guid EmployeeId { get; set; }
 
    public string Account { get; set; }
 
    public string Password { get; set; }
 
    public long SequenceId { get; set; }
 
    public string Remark { get; set; }
 
    public virtual Employee Employee { get; set; }
}

 

[Table("Order")]
public class Order
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Guid Id { get; set; }
 
    public Guid? EmployeeId { get; set; }
 
    public DateTime? OrderTime { get; set; }
 
    public string Remark { get; set; }
 
    public long SequenceId { get; set; }
 
    public virtual Employee Employee { get; set; }
}

 

public class Employee
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Guid Id { get; set; }
 
    public string Name { get; set; }
 
    public int? Age { get; set; }
 
    public long SequenceId { get; set; }
 
    public string Remark { get; set; }
 
    public virtual Identity Identity { get; set; }
 
    public virtual ICollection<Order> Order { get; set; }
 
    public Employee()
    {
        this.Order = new HashSet<Order>();
    }
}


 

專案位置

如果懶得自己重建專案,可以直接拉這裡 code

https://github.com/yaochangyu/sample.dotblog/tree/master/ORM/EFCore/EF%20Core%20Migration

使用情境

自動更新資料結構且保留資料

當準備要上線或是要給 QA 使用,DbContext 相關的設定應該都已經設定完成,我需要一個開發資料庫和一個遠端的線上資料庫,Lab.DAL 放開發資料庫的連線,Lab.DAL.UnitTest 放遠端資料庫的連線,此時,資料庫都不存在

建立第一個 Migration,調用 Add-Migration,後面接一個有意義的名詞,有空白要加雙引號

Visual Studio PowerShell:

Add-Migration InitialCreate

.NET Core CLI:

dotnet ef migrations add InitialCreate


 

Default project 停在 Lab.DAL

 

這時候 Lab.DAL 的資料庫不存在,他長出來的 Migration 都是 CreateTable

 

接著,我要把開發資料庫長出來,先把 Lab.DAL 設為起始專案,Package Manager Console 的 default Project 也是 Lab.DAL,然後再執行以下命令

Visual Studio PowerShell:

Update-Database

.NET Core CLI:

dotnet ef database update
  • 在 EF6 假使不設定起始專案,那就要把連線字串給 Update-Database,EF Core 似乎找不到這個參數
  • 這個開發用的資料庫很重要,Add-Migration 會跟這個資料庫比對產生差異語法

 

開發用的 DB 建立完成之後,接著執行測試方法,這個時候跑 Lab.DAL.UnitTest,資料庫就會自行長出來,__EFMigrationsHistory 資料表會有我們剛剛建立的 InitialCreate Migration;若把測試專案換成 Web,資料庫也會自己長出來。

測試程式僅是很單純的查詢 Employee Table

[TestMethod]
public void TestMethod1()
{
    using (var dbContext = new LabEmployeeContext(DbOptionsFactory.DbContextOptions))
    {
        var employees = dbContext.Employee.AsNoTracking().ToList();
    }
}

 

專案在維護的過程當中,會碰到需求異動,可能也會異動資料結構,假設我修改某一個欄位的字串長度

 

然後執行

Visual Studio PowerShell:

Add-Migration "Add StringLength at Account"

.NET Core CLI:

dotnet ef migrations add "Add StringLength at Account"

如下圖,Mirgation 會就多出異動的欄位資訊

 

確定需求完成了(程式碼改好了)

再執行 Update-Database,更新開發資料庫的資料結構,這樣下次才能執行 Add-Migration,可以看到 __EFMigrationsHistory 的異動多了  "Add StringLength at Account" 資料列


 

 

整個開發維護的週期,VS IDE 預設專案設定開發用的連線字串 → 變更 POCO → 調用 Add-Migration → 調用 Update-Database → 執行測試站台(資料結構自動更新)

不想設定 VS IDE 預設專案,可以下達命令

Add-Migration [-Name] <String> [-OutputDir <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]
Update-Database [[-Migration] <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

 

Visual Studio PowerShell::EF Core 工具參考 (封裝管理員主控台) -EF Core | Microsoft Docs

.NET Core CLI:EF Core 工具參考 ( .NET CLI) -EF Core | Microsoft Docs

dotnet ef database update -- --environment Production

假使想要反悔:

還沒有調用  Update-Database 前,調用 Remove-Migration,他就會自動幫我們刪除最後一次的 Migration *.cs 檔案,或者你手動刪除檔案也可以

已經調用 Update-Database 了,那就要調用 Update-Database -Migration "20191129022845_InitialCreate",他就會把 __EFMigrationsHistory 的 "Add StringLength at Account" 刪掉並復原資料結構

 

自動更新資料結構

EF 6 也能寫 Code 自動更新資料結構,如果你有跟著我一塊做的話你會發現 Lab.DAL.UnitTest 可以自動更新資料結構,因為調用 this.Database.Migrate(),為了確保 this.Database.Migrate() 在多執行緒的環境只被調用一次,加上鎖的功能

public LabEmployeeContext(DbContextOptions<LabEmployeeContext> options)
    : base(options)
{
    if (options == null)
    {
        options = DbOptionsFactory.DbContextOptions;
    }
 
    if (!s_migrated[0])
    {
        lock (s_migrated)
        {
            if (!s_migrated[0])
            {
                this.Database.Migrate();
                s_migrated[0] = true;
            }
        }
    }
}

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo