Asp-Net-Core開(kāi)發(fā)筆記:快速在已有項(xiàng)目中引入EFCore
1前言
很多項(xiàng)目一開(kāi)始選型的時(shí)候沒(méi)有選擇EFCore,不過(guò)EFCore確實(shí)好用,也許由于種種原因后面還是需要用到,這時(shí)候引入EFCore也很方便。
本文以 StarBlog 為例,StarBlog 目前使用的 ORM 是 FreeSQL ,引入 EFCore 對(duì)我來(lái)說(shuō)最大的好處是支持多個(gè)數(shù)據(jù)庫(kù),如果是 FreeSQL 的話,服務(wù)注冊(cè)的時(shí)候是單例模式,只能連接一個(gè)數(shù)據(jù)庫(kù),如果需要使用 FreeSQL 同時(shí)連接多個(gè)數(shù)據(jù)庫(kù),需要自行做一些額外的工作。
要實(shí)現(xiàn)的效果是:把訪問(wèn)記錄單獨(dú)使用一個(gè)數(shù)據(jù)庫(kù)來(lái)存儲(chǔ),并且使用 EFCore 管理。
2安裝工具
首先安裝 EFCore 的 cli 工具
dotnet tool install --global dotnet-ef
3項(xiàng)目架構(gòu)
先來(lái)回顧一下項(xiàng)目架構(gòu):基于.NetCore開(kāi)發(fā)博客項(xiàng)目 StarBlog - (2) 環(huán)境準(zhǔn)備和創(chuàng)建項(xiàng)目
StarBlog
├── StarBlog.Contrib
├── StarBlog.Data
├── StarBlog.Migrate
├── StarBlog.Web
└── StarBlog.sln
為了解耦,和數(shù)據(jù)有關(guān)的代碼在 StarBlog.Data 項(xiàng)目下,因此引入 EFCore 只需要在 StarBlog.Data 這個(gè)項(xiàng)目中添加依賴(lài)即可。
4添加依賴(lài)
在 StarBlog.Data 項(xiàng)目中添加以下三個(gè)依賴(lài)
-
Microsoft.EntityFrameworkCore -
Microsoft.EntityFrameworkCore.Sqlite -
Microsoft.EntityFrameworkCore.Tools
EFCore 對(duì) SQLite 的支持很弱(根本原因是微軟提供的 SQLite 驅(qū)動(dòng)功能太少),所以只適合在本地開(kāi)發(fā)玩玩,實(shí)際部署還是得切換成 C/S 架構(gòu)的數(shù)據(jù)庫(kù)(PgSQL/MySQL/SQL Server)才行。
添加后項(xiàng)目的 .csproj 文件新增的依賴(lài)類(lèi)似這樣
<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 版本,等后續(xù) .Net8 正式版發(fā)布之后,我會(huì)把這個(gè)項(xiàng)目升級(jí)到 .Net8
5創(chuàng)建 DbContext
DbContext 是 EFCore 與數(shù)據(jù)庫(kù)交互的入口,一般一個(gè)數(shù)據(jù)庫(kù)對(duì)應(yīng)一個(gè)。
現(xiàn)在來(lái) StarBlog.Data 項(xiàng)目下創(chuàng)建一個(gè)。
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) { }
}
因?yàn)橹恍枰?EFCore 管理訪問(wèn)記錄,所以只需要一個(gè) DbSet
6實(shí)體類(lèi)配置
然后來(lái)創(chuàng)建個(gè)配置,雖然也可以用 Data Annotation 來(lái)配置,但 EFCore 推薦使用 Fluent Config 方式來(lái)配置數(shù)據(jù)表和字段。
創(chuàng)建 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);
}
}
主要是配置了主鍵和各個(gè)字段的長(zhǎng)度。
數(shù)據(jù)類(lèi)型這塊 EFCore 會(huì)自動(dòng)映射,具體請(qǐng)參考官方文檔。
7主鍵類(lèi)型選擇
這里插播一下題外話,關(guān)于主鍵類(lèi)型應(yīng)該如何選擇。
目前主要有幾種方式:
-
自增 -
GUID -
自增+GUID -
Hi/Lo
這幾種方式各有優(yōu)劣。
-
自增的好處是簡(jiǎn)單,缺點(diǎn)是在數(shù)據(jù)庫(kù)遷移或者分布式系統(tǒng)中容易出問(wèn)題,而且高并發(fā)時(shí)插入性能較差。 -
GUID好處也是簡(jiǎn)單方便,而且也適用于分布式系統(tǒng);MySQL的InnoDB引擎強(qiáng)制主鍵使用聚集索引,導(dǎo)致新插入的每條數(shù)據(jù)都要經(jīng)歷查找合適插入位置的過(guò)程,在數(shù)據(jù)量大的時(shí)候插入性能很差。 -
自增+GUID是把自增字段作為物理主鍵,GUID作為邏輯主鍵,可以在一定程度上解決上述兩種方式的問(wèn)題。 -
Hi/Lo可以?xún)?yōu)化自增列的性能,但只有部分?jǐn)?shù)據(jù)庫(kù)支持,比如SQL Server,其他的數(shù)據(jù)庫(kù)暫時(shí)還沒(méi)研究。
8DesignTime 配置
因?yàn)槲覀兊捻?xiàng)目是把 AspNetCore 和數(shù)據(jù)分離的,所以需要一個(gè) DesignTime 配置來(lái)讓 EFCore 知道如何執(zhí)行遷移。
在 StarBlog.Data 中創(chuàng)建 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);
}
}
這里從環(huán)境變量讀取數(shù)據(jù)庫(kù)連接字符串,如果讀不到就使用默認(rèn)的數(shù)據(jù)庫(kù)文件。
9數(shù)據(jù)庫(kù)遷移
這塊主要是使用兩組命令
-
migrations- 用于監(jiān)控?cái)?shù)據(jù)庫(kù)的修改 -
database- 將修改同步到數(shù)據(jù)庫(kù)里
cd 到 StarBlog.Data 目錄下,執(zhí)行
dotnet ef migrations add InitialCreate -o Migrations
之后可以看到 Migrations 目錄下生成了遷移的代碼
如果需要指定數(shù)據(jù)庫(kù)文件,可以設(shè)置環(huán)境變量。
Windows的使用:
set CONNECTION_STRING = "Data Source=app.db;"
Linux的也差不多,把 set 換成 export
export CONNECTION_STRING = "Data Source=app.db;"
運(yùn)行以下命令同步到數(shù)據(jù)庫(kù)
dotnet ef database update
執(zhí)行之后就會(huì)在 StarBlog.Data 下生成 SQLite 數(shù)據(jù)庫(kù)文件。
10在AspNetCore項(xiàng)目里集成EFCore
先把數(shù)據(jù)庫(kù)連接字符串寫(xiě)到配置文件 appsettings.json 里
{
"ConnectionStrings": {
"SQLite": "Data Source=app.db;Synchronous=Off;Cache Size=5000;",
"SQLite-Log": "Data Source=app.log.db;"
}
}
在 Program.cs 里注冊(cè)服務(wù)
builder.Services.AddDbContext<AppDbContext>(options => {
options.UseSqlite(builder.Configuration.GetConnectionString("SQLite-Log"));
});
搞定~
11db-first
從已有數(shù)據(jù)庫(kù)生成實(shí)體類(lèi),一般新項(xiàng)目不推薦這種開(kāi)發(fā)方式,不過(guò)在舊項(xiàng)目上使用還是比較方便的,EFCore 的 cli tool 也提供很豐富的代碼生成功能。
這里提供一下例子:
-
使用 PostgreSql 數(shù)據(jù)庫(kù),要把其中 pe_shit_data庫(kù)的所有表生成實(shí)體類(lèi) -
生成的 DbContext類(lèi)名為ShitDbContext -
DbContext類(lèi)的命名空間為PE.Data -
實(shí)體類(lèi)放在 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 `
這個(gè)是 powershell 的命令,如果是 Linux 環(huán)境,把每一行命令末尾的反引號(hào)換成 \ 即可。
12參考資料
-
https://learn.microsoft.com/en-us/ef/core/cli/dotnet -
https://learn.microsoft.com/zh-cn/ef/core/managing-schemas/scaffolding
