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