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
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