Asp-Net-Core開發筆記:快速在已有專案中引入EFCore

程式設計實驗室發表於2023-10-08

前言

很多專案一開始選型的時候沒有選擇EFCore,不過EFCore確實好用,也許由於種種原因後面還是需要用到,這時候引入EFCore也很方便。

本文以 StarBlog 為例,StarBlog 目前使用的 ORM 是 FreeSQL ,引入 EFCore 對我來說最大的好處是支援多個資料庫,如果是 FreeSQL 的話,服務註冊的時候是單例模式,只能連線一個資料庫,如果需要使用 FreeSQL 同時連線多個資料庫,需要自行做一些額外的工作。

要實現的效果是:把訪問記錄單獨使用一個資料庫來儲存,並且使用 EFCore 管理。

安裝工具

首先安裝 EFCore 的 cli 工具

dotnet tool install --global dotnet-ef

專案架構

先來回顧一下專案架構:基於.NetCore開發部落格專案 StarBlog - (2) 環境準備和建立專案

StarBlog
├── StarBlog.Contrib
├── StarBlog.Data
├── StarBlog.Migrate
├── StarBlog.Web
└── StarBlog.sln

為瞭解耦,和資料有關的程式碼在 StarBlog.Data 專案下,因此引入 EFCore 只需要在 StarBlog.Data 這個專案中新增依賴即可。

新增依賴

StarBlog.Data 專案中新增以下三個依賴

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Sqlite
  • Microsoft.EntityFrameworkCore.Tools

EFCore 對 SQLite 的支援很弱(根本原因是微軟提供的 SQLite 驅動功能太少),所以只適合在本地開發玩玩,實際部署還是得切換成 C/S 架構的資料庫(PgSQL/MySQL/SQL Server)才行。

新增後專案的 .csproj 檔案新增的依賴類似這樣

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.18" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.18" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.18">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

目前 StarBlog 還在使用 .Net6 所以我新增的 EFCore 是 6.x 版本,等後續 .Net8 正式版釋出之後,我會把這個專案升級到 .Net8

建立 DbContext

DbContext 是 EFCore 與資料庫互動的入口,一般一個資料庫對應一個。

現在來 StarBlog.Data 專案下建立一個。

using Microsoft.EntityFrameworkCore;
using StarBlog.Data.Models;

namespace StarBlog.Data;

public class AppDbContext : DbContext {
  public DbSet<VisitRecord> VisitRecords { get; set; }
  
  public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

因為只需要讓 EFCore 管理訪問記錄,所以只需要一個 DbSet

實體類配置

然後來建立個配置,雖然也可以用 Data Annotation 來配置,但 EFCore 推薦使用 Fluent Config 方式來配置資料表和欄位。

建立 StarBlog.Data/Config/VisitRecordConfig.cs 檔案

public class VisitRecordConfig : IEntityTypeConfiguration<VisitRecord> {
  public void Configure(EntityTypeBuilder<VisitRecord> builder) {
    builder.ToTable("visit_record");
    builder.HasKey(e => e.Id);
    builder.Property(e => e.Ip).HasMaxLength(64);
    builder.Property(e => e.RequestPath).HasMaxLength(2048);
    builder.Property(e => e.RequestQueryString).HasMaxLength(2048);
    builder.Property(e => e.RequestMethod).HasMaxLength(10);
    builder.Property(e => e.UserAgent).HasMaxLength(1024);
  }
}

主要是配置了主鍵和各個欄位的長度。

資料型別這塊 EFCore 會自動對映,具體請參考官方檔案。

主鍵型別選擇

這裡插播一下題外話,關於主鍵型別應該如何選擇。

目前主要有幾種方式:

  • 自增
  • GUID
  • 自增+GUID
  • Hi/Lo

這幾種方式各有優劣。

  • 自增的好處是簡單,缺點是在資料庫遷移或者分散式系統中容易出問題,而且高併發時插入效能較差。
  • GUID好處也是簡單方便,而且也適用於分散式系統;MySQL的InnoDB引擎強制主鍵使用聚集索引,導致新插入的每條資料都要經歷查詢合適插入位置的過程,在資料量大的時候插入效能很差。
  • 自增+GUID是把自增欄位作為物理主鍵,GUID作為邏輯主鍵,可以在一定程度上解決上述兩種方式的問題。
  • Hi/Lo可以最佳化自增列的效能,但只有部分資料庫支援,比如SQL Server,其他的資料庫暫時還沒研究。

DesignTime 配置

因為我們的專案是把 AspNetCore 和資料分離的,所以需要一個 DesignTime 配置來讓 EFCore 知道如何執行遷移。

StarBlog.Data 中建立 AppDesignTimeDbContextFactory.cs 檔案

public class AppDesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext> {
  public AppDbContext CreateDbContext(string[] args) {
    var builder = new DbContextOptionsBuilder<AppDbContext>();

    var connStr = Environment.GetEnvironmentVariable("CONNECTION_STRING");
    if (connStr == null) {
      var dbpath = Path.Combine(Environment.CurrentDirectory, "app.log.db");
      connStr = $"Data Source={dbpath};";
    }

    builder.UseSqlite(connStr);
    return new AppDbContext(builder.Options);
  }
}

這裡從環境變數讀取資料庫連線字串,如果讀不到就使用預設的資料庫檔案。

資料庫遷移

這塊主要是使用兩組命令

  • migrations - 用於監控資料庫的修改
  • database - 將修改同步到資料庫裡

cd 到 StarBlog.Data 目錄下,執行

dotnet ef migrations add InitialCreate -o Migrations 

之後可以看到 Migrations 目錄下生成了遷移的程式碼

如果需要指定資料庫檔案,可以設定環境變數。

Windows的使用:

set CONNECTION_STRING = "Data Source=app.db;"

Linux的也差不多,把 set 換成 export

export CONNECTION_STRING = "Data Source=app.db;"

執行以下命令同步到資料庫

dotnet ef database update

執行之後就會在 StarBlog.Data 下生成 SQLite 資料庫檔案。

在AspNetCore專案裡整合EFCore

先把資料庫連線字串寫到配置檔案 appsettings.json

{
  "ConnectionStrings": {
    "SQLite": "Data Source=app.db;Synchronous=Off;Cache Size=5000;",
    "SQLite-Log": "Data Source=app.log.db;"
  }
}

Program.cs 裡註冊服務

builder.Services.AddDbContext<AppDbContext>(options => {
  options.UseSqlite(builder.Configuration.GetConnectionString("SQLite-Log"));
});

搞定~

db-first

從已有資料庫生成實體類,一般新專案不推薦這種開發方式,不過在舊專案上使用還是比較方便的,EFCore 的 cli tool 也提供很豐富的程式碼生成功能。

這裡提供一下例子:

  • 使用 PostgreSql 資料庫,要把其中 pe_shit_data 庫的所有表生成實體類
  • 生成的 DbContext 類名為 ShitDbContext
  • DbContext 類的名稱空間為 PE.Data
  • 實體類放在 ShitModels 目錄下,名稱空間為 PE.Data.ShitModels

命令如下

dotnet ef dbcontext scaffold `
    "Host=cuc.dou3.net;Database=pe_shit_data;Username=postgres;Password=passw0rd" `
    Npgsql.EntityFrameworkCore.PostgreSQL `
    -f `
    -c ShitDbContext `
    --context-dir . `
    --context-namespace PE.Data `
    -o ShitModels `
    --namespace PE.Data.ShitModels `

這個是 powershell 的命令,如果是 Linux 環境,把每一行命令末尾的反引號換成 \ 即可。

參考資料

相關文章