ASP.NET Core分层结构项目里使用EF Core
创建一个类库项目,引入需要的包,注意选择与自己项目版本匹配的
1
2
3Install-Package Microsoft.EntityFrameworkCore
Install-Package Npgsql.EntityFrameworkCore.PostgreSQL //使用PostgreSQL
Install-Package Microsoft.EntityFrameworkCore.Tools注意安装的包版本需要匹配你的框架版本
用的数据库需要什么包,可以看微软官方文档这篇:数据库提供程序
建立一个 DbContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class HolyGrailWarDbContext:DbContext
{
public HolyGrailWarDbContext(DbContextOptions<HolyGrailWarDbContext> options) :base(options)
{
}
public DbSet<Master> Masters { get; set; }
}
public class Master
{
public int MasterId { get; set; }
public string Name { get; set; }
}注意构造函数的写法,以及不重写 OnConfiguring 方法
创建一个ASP.NET Core WebApi项目,引入需要的包
1
2
3Install-Package Microsoft.EntityFrameworkCore
Install-Package Npgsql.EntityFrameworkCore.PostgreSQL
Install-Package Microsoft.EntityFrameworkCore.Tools注册
1
2
3string strConn = @"Host=localhost;Database=HolyGrailWar;Username=postgres;Password=password";
services.AddDbContext<HolyGrailWarDbContext>(options =>
options.UseNpgsql(strConn));注入使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34[ ]
[ ]
public class MasterController : ControllerBase
{
private readonly HolyGrailWarDbContext holyGrailWarDbContext;
public MasterController(HolyGrailWarDbContext holyGrailWarDbContext)
{
this.holyGrailWarDbContext = holyGrailWarDbContext;
}
[ ]
public IEnumerable<Master> GetMasters()
{
return holyGrailWarDbContext.Masters;
}
[ ]
public IActionResult AddMasters()
{
var master1 = holyGrailWarDbContext.Masters.Add(new Master { Name = $"Shirou Emiya" });
var master2 = holyGrailWarDbContext.Masters.Add(new Master { Name = $"Osaka Rin" });
var master3 = holyGrailWarDbContext.Masters.Add(new Master { Name = $"Illyasviel von Einzbern" });
holyGrailWarDbContext.SaveChanges();
string str = string.Empty;
var masters = holyGrailWarDbContext.Masters;
foreach (var master in masters)
{
str += $"ID:{master.MasterId,4}, Name:{master.Name} \n";
}
return Ok($"Add masters OK, Now has below masters: \n{str}");
}
}
迁移
上面还没有建立相应的数据库,我们需要使用 EF Core 的迁移命令进行
把 ASP.NET Core WebApi 项目设为启动项目,将 “程序包管理控制台” 的默认项目设为 DbContext 所在类库
在“程序包管理控制台”,运行命令
Add-Migration
,获得迁移脚本1
Add-Migration InitialCreate //InitialCreate是迁移版本名称,可以自定义
在“程序包管理控制台”,运行命令
Update-Database
,更新数据库1
Update-Database
查看数据库,可以看到相应的数据库与表都建立的,使用前面添加的控制器测试使用
在运行Add-Migration
与Update-Database
命令时候,可能有一些问题,按照错误查找解决办法即可
下面列举我遇到过的问题:
问题:
The EF Core tools version '3.1.21' is older than that of the runtime '3.1.22'. Update the tools for the latest features and bug fixes.
解决:更新
Microsoft.EntityFrameworkCore.Tools
到相应版本问题:
Update-Database
,Add-Migration
生成的脚本报错解决:没有
Microsoft.EntityFrameworkCore.Relational
包,一般引入相应的SQL包就会包含了,如Postgresql的包Npgsql.EntityFrameworkCore.PostgreSQL
就包含,实在没有就自己安装问题:
Add-Migration
时错误,More than one DbContext was found. Specify which one to use. Use the '-Context' parameter for PowerShell commands and the '--context' parameter for dotnet commands.
解决:先在 ASP.NET Core WebApi 项目注册需要用的 DbContext,然后使用参数 ‘-Context’ 指定DbContext,如现在有两个 DbContext(
FirstholyGrailWarDbContext
与SecondholyGrailWarDbContext
)都要用,则依下步骤注册
1
2
3
4
5string strConn = @"Host=localhost;Database=HolyGrailWar;Username=postgres;Password=password";
services.AddDbContext<FirstholyGrailWarDbContext>(options =>
options.UseNpgsql(strConn));
services.AddDbContext<SecondholyGrailWarDbContext>(options =>
options.UseNpgsql(strConn));迁移,有多个 DbContext 时两个命令都需要指定要用的 DbContext,其他相关命令也是
1
2
3
4
5
6
7//FirstholyGrailWarDbContext
Add-Migration InitialCreate -Context FirstholyGrailWarDbContext
Update-Database -Context FirstholyGrailWarDbContext
//SecondholyGrailWarDbContext
Add-Migration InitialCreate -Context SecondholyGrailWarDbContext
Update-Database -Context SecondholyGrailWarDbContext
慎用AddDbContextPool
AddDbContext与AddDbContextPool
DbContext 是非线程安全的,我们不能在同一DbContext
实例上同时运行多个操作(Add、Update、Delete等),因此一般的做法就是在需要使用的时候就创建一个DbContext
,待用完后就销毁掉,这就是AddDbContext
的做法。
但是实际上我们是可以重用已创建的DbContext
实例的,只是需要注意该DbContext
前面的操作都需要已经完成了,这就是AddDbContextPool
的做法。AddDbContextPool
会保留多个已创建的DbContext
且现在已经没被使用的实例到池,当请求需要DbContext
时也会倾向于返回池里已有的DbContext
多过创建一个新的。
使用AddDbContextPool问题
AddDbContextPool
的做法看似很美好,能够避免DbContext
实例创建时的性能消耗,但是实际使用中还是有些坑的:
用了
AddDbContextPool
那么你的DbContext
就难以注入其他服务,因为使用AddDbContextPool
创建的DbContext
类似于 singleton 服务,导致只有同为 singleton 的服务才能注入。有很多 ADO.NET 的提供者也实现了数据库连接池的机制,有可能于其冲突。
有其他坑会导致一些奇怪的问题,如
使用带“小上下文”策略的AddDbContext方法
鉴于上面AddDbContextPool
的问题,推荐假如自认不能 hold 住的还是使用AddDbContext
的方法,虽然性能差点,但是对于非高并发请求(1000req/1s以上)的应用,使用AddDbContext
就足够了。
假如觉得不满足的话,可以使用“小DbContext”的策略,即把一个DbContext分拆成多个DbContext,如本身一个大 DbContext 里有100个 DbSet,但是拆分成20个 DbContext,这样平均一个 DbContext 就4个 DbSet,创建的消耗自然减少,使用多个 DbContext 步骤如下:
首先需要自己分割 DbContext,假设已分割完了有两个
FirstDbContext
、SecondDbContext
注册
1
2services.AddDbContext<FirstDbContext>(options => options.UseSqlServer(strConnString));
services.AddDbContext<SecondDbContext>(options => options.UseSqlServer(strConnString));使用时注入,假设只注入FirstDbContext
1
2
3
4
5private readonly FirstDbContext firstDbContext;
public BlogController(FirstDbContext firstDbContext)
{
this.firstDbContext = firstDbContext;
}
这种做法是 DDD 设计原则,把一个大的根据业务分拆成小的,也很符合微服务的概念。不过难点就在于如何拆分,分得不好,就会导致明明以前只需要一个 DbContex 就能干的,结果现在需要多个 DbContex,不但麻烦了,性能可能还降低的。