前言
很多專案一開始選型的時候沒有選擇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 環境,把每一行命令末尾的反引號換成 \
即可。