EF Core 的 Code First 模式

蘆薈柚子茶發表於2022-04-26

0 前言

本文正文第一節,會對 Code First 進行基本的介紹,以及對相關名詞進行說明,讀者一開始可以不用在這裡消耗過多時間,可以先操作一遍例子,再回過頭理解。

第二節,以一個簡單的例子,展示 EF Core 的 Code First 模式的操作流程。

第三節,將 Code First 的其他指令例舉出來,以便於日後翻查。

第四節(未完成),將 Code First 其他一些操作,如:在遷移程式碼中新增 SQL 語句等。

第五節,將 Code First 模式常見的問題列舉出來,防止踩坑。


1 相關介紹

1.1 Code First 模式

以 EF Core 模型為準,使用遷移的方式,將 EF Core 模型的變化以增量的方式更新到資料庫。

簡單理解:以C#程式碼定義的資料實體,生成資料庫的表結構。

1.2 相關名詞

資料庫上下文(DbContext):繼承自 DbContext,主要作用是連線資料庫,跟蹤資料實體狀態(實體狀態包括:added、modified、deleted 等),將資料庫實體的狀態寫入資料庫(持久化至資料庫中)。

資料實體(Entity):C#的實體類,與資料庫的表對應

資料模型(Model):暫且認為是資料庫的表吧(因為官方文件的描述,感覺就像是)

約定(conventions):主要是資料實體的類名、屬性。

資料註釋(data annotations):應用於類上、屬性的特性(如:[Table("SysUser")]),會被 Fluent API 的配置覆蓋。

Fluent API:於自定義的 DbContext 中重寫 OnModelCreating 方法中,對資料模型描述的配置,如:

 protected override void OnModelCreating(ModelBuilder modelBuilder)
 {
     builder.Entity<User>().ToTable("SysUser");
 }

資料實體(Entity)、資料模型(Model)、約定(conventions)、資料註釋(data annotations)、Fluent API 說明:

資料實體(Entity)的類名、屬性等,稱之為約定(conventions),約定主要是為了定義資料模型(Model)的形狀。

但是光靠約定可能不足以完整描述資料模型,有時我們的資料模型與我們的資料實體可能也有差異,這時,就可以通過資料註釋(data annotations)和 Fluent API 補充。


2 EF Core 的基礎使用

2.1 新建 WebApi 工程

這裡基於 VS Code 工具,使用命令列建立一個 WebApi 程式:

mkdir CodeFirstTest & cd CodeFirstTest #新建資料夾DbFirstTest並切換至該目錄下
dotnet new webapi --framework net6.0   #新建ASP.NET6.0 WebAPI程式

2.2 引入 EF Core 相關 Nuget 包

EF Core 部分 Nuget 包如下:

Microsoft.EntityFrameworkCore -->> 核心包
Microsoft.EntityFrameworkCore.Design -->> Design包:Code First 或 Db First 需要
Microsoft.EntityFrameworkCore.SqlServer -->> 微軟官方 SQL Server 驅動
Pomelo.EntityFrameworkCore.MySql -->> 社群 MySql 驅動
MySql.EntityFrameworkCore -->> Oracle官方 MySql 驅動

其中核心包是必須的,另外還需配備對應資料庫的驅動包,而 Design 包主要是在使用 Code First 或 Db First 需要的包。

這裡,我們向工程引入必須的 Nuget 包,SQL 驅動程式選擇 SQL Server 的:

dotnet add package Microsoft.EntityFrameworkCore --version 6.0.4
dotnet add package Microsoft.EntityFrameworkCore.Design --version 6.0.4
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.4
# dotnet add package Pomelo.EntityFrameworkCore.MySql --version 6.0.1

2.3 準備配置資訊

在 appsettings.json 中增加一個節點,用於連線資料庫時使用。

"ConnectionStrings": {
    "SqlServer": "server=localhost;database=efcore;uid=sa;pwd=Qwe123456;",
    "MySql": "server=localhost;port=3306;database=efcore;user=root;password=123456;charset=utf8mb4;"
}

2.4 新建資料庫上下文 DbContext

新建一個自定義的資料庫上下文 TestDbContext:

using Microsoft.EntityFrameworkCore;

namespace CodeFirstTest;

public class TestContext : DbContext
{
    public TestContext(DbContextOptions<TestContext> options) : base(options) { }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        base.OnConfiguring(options);
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>();
        base.OnModelCreating(builder);
    }

    public virtual DbSet<User> User { get; set; }
}

2.5 建立資料實體

建立一個資料實體 User 如下:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace CodeFirstTest;

[Table("SysUser")]
public class User
{
    [Key]
    public Guid Id { get; set; } = new Guid();

    [StringLength(128)]
    [Comment("姓名")]
    public string? Name { get; set; }

    [StringLength(11)]
    [Comment("手機號碼")]
    public string? Phone { get; set; }
}

[Table("SysUser")] 註釋資料庫中的表名為 SysUser。

[StringLength(128)] 註釋字串長度,[Comment("姓名")] 註釋資料庫中該欄位含義。

這些註釋,稱為“資料註釋”,主要是對資料模型的補充描述(可以簡單認為:對資料庫的表結構的補充描述)。

關於配置資料模型的詳細內容,可以翻查EF Core官方文件:建立並配置模型

2.6 註冊服務

在 Program.cs 中註冊服務,並配置資料庫連線串。

var configuration = builder.Configuration;
builder.Services.AddDbContext<TestContext>(options => {
    options.UseSqlServer(configuration["ConnectionStrings:SqlServer"]);
});

2.7 編譯專案

在生成遷移之前,需要先對工程進行編譯,否則會報錯。

dotnet build

2.8 資料庫遷移(Code First 模式)

如果沒有安裝 EF 工具,需要先安裝

# 安裝全域性工具
dotnet tool install --global dotnet-ef
# 更新工具
dotnet tool update --global dotnet-ef

建立一個名為 Initial 的遷移,將會在專案根目錄下生成一個 Migrations 的目錄:

dotnet ef migrations add Initial

更新到資料庫

dotnet ef database update

2.9 增加測試控制器

增加一個 UserController 用於測試。

using Microsoft.AspNetCore.Mvc;

namespace CodeFirstTest.Controllers;

[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
    private readonly TestContext _db;

    public UserController(TestContext db)
    {
        _db = db;
    }

    [HttpGet]
    public User? Get(Guid id)
    {
        return _db.User.Find(id);
    }

    [HttpPost]
    public void Post(User user)
    {
        _db.User.Add(user);
        _db.SaveChanges();
    }

    [HttpDelete]
    public bool Delete(Guid id)
    {
        User? user = _db.User.Find(id) ?? null;
        if (user == null) return false;
        _db.User.Remove(user);
        _db.SaveChanges();
        return true;
    }
}

2.10 執行專案

dotnet build
dotnet run

訪問:https://localhost:7232/swagger/index.html

對介面進行操作,可以實現對 User 的增刪查。

2.11 原始碼

Gitee:https://gitee.com/lisheng741/testnetcore/tree/master/EFCore/CodeFirstTest

Github:https://github.com/lisheng741/testnetcore/tree/master/EFCore/CodeFirstTest


3 資料庫遷移

具體參考EF Core官方文件:管理資料庫架構:遷移

3.1 安裝工具

# 安裝全域性工具
dotnet tool install --global dotnet-ef

# 更新工具
dotnet tool update --global dotnet-ef

# 驗證安裝
dotnet ef

3.2 遷移

3.2.1 管理遷移

建立一個名為 Migrations 的目錄,並生成一些檔案

dotnet ef migrations add InitialCreate

建立遷移時指定遷移目錄

dotnet ef migrations add InitialCreate --output-dir [directory]

刪除遷移

dotnet ef migrations remove

列出所有遷移

dotnet ef migrations list

3.2.2 應用遷移

應用遷移主要有2種方式,一種是生成 SQL 指令碼,一種是通過命令列工具執行命令進行遷移,具體請參考EF Core 官方文件:應用遷移

除了這兩種遷移方式外,還有一種是在程式種遷移,即將遷移的程式碼寫入程式中,由程式執行時觸發。

1) 命令列工具

將遷移應用到資料庫

dotnet ef database update

將遷移應用到資料庫:指定遷移

dotnet ef database update AddNewTables

注意:使用該命令,也可以進行遷移回滾(回滾到之前的某個遷移)。

2) 生成 SQL 指令碼
dotnet ef migrations script

指定遷移起點(From)

dotnet ef migrations script AddNewTables

指定遷移起點(From)和結束點(To)

dotnet ef migrations script AddNewTables AddAuditTable

冪等 SQL 指令碼(idempotent):指令碼將在內部檢查已經應用哪些遷移(通過遷移歷史記錄表),並且只應用缺少的遷移。

dotnet ef migrations script --idempotent
3)在程式執行時進行遷移

請參考EF Core 文件:應用遷移:在執行時應用遷移


4 其他操作

4.1 遷移程式碼新增 SQL

請參考:EF Core 官方文件:管理遷移:新增原始 SQL

下面的例子,用一個新的 FullName 屬性替換現有的 FirstNameLastName 屬性,並將現存的資料轉移到新的列上。

migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customer",
    nullable: true);

migrationBuilder.Sql("UPDATE Customer SET FullName = FirstName + ' ' + LastName;");

migrationBuilder.DropColumn(
    name: "FirstName",
    table: "Customer");

migrationBuilder.DropColumn(
    name: "LastName",
    table: "Customer");

4.2 自定義遷移操作

MigrationBuilder.Sql() 或 自定義 MigrationOperation 物件,可以對 MigrationBuilder 進行擴充套件。

如:想要在遷移程式碼中使用如下程式碼(CreateUser 方法為自定義方法)

migrationBuilder.CreateUser("SQLUser1", "Password");

4.2.1 使用 MigrationBuilder.Sql()

CreateUser 自定義程式碼如下:

public static OperationBuilder<SqlOperation> CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
    => migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';");

4.2.2 自定義 MigrationOperation 物件

請參考EF Core 官方文件:自定義操作

建立和刪除 API(EnsureCreated 和 EnsureDeleted)

在程式執行中可以呼叫的 API,用於管理資料庫的建立和刪除。


5 Code First 模式常見問題

5.1 列重新命名

具體請檢視EF Core 官方文件:管理遷移:列重新命名

官方舉的例子:如果你將屬性從 Name 重新命名為 FullName,EF Core 將生成以下遷移:

migrationBuilder.DropColumn(
    name: "Name",
    table: "Customers");

migrationBuilder.AddColumn<string>(
    name: "FullName",
    table: "Customers",
    nullable: true);

該遷移程式碼將 Name 列刪除,然後新增新的列 FullName,這樣做,會導致 Name 列原有的資料丟失。

所以需要自行將該遷移程式碼修改如下:

migrationBuilder.RenameColumn(
    name: "Name",
    table: "Customers",
    newName: "FullName");

參考來源

EF Core 官方文件

EF Core / 基礎_從建庫到增刪改查

EF Core的基本使用

相關文章