Par*_*a99 2 c# dependency-injection .net-core asp.net-core .net-7.0
我的应用程序中有一些后台作业,它们会定期(从几分钟到几个小时)修改数据库。它们都有一个while
使用stoppingToken.IsCancellationRequested
条件的循环。
目前,我正在循环内创建和处置一个范围,这意味着在每次迭代中,都需要创建和处置一个范围。我的问题是,从性能和安全的角度来看,我应该在哪里创建我的范围?在应用程序生命周期中每次迭代都在循环内部还是在循环外部?
public class MyBackgroundJob : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public MyBackgroundJob(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Here?
//using var scope = _serviceProvider.CreateScope();
//var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
while (!stoppingToken.IsCancellationRequested)
{
// Or here?
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
// Do some work
try
{
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
catch (TaskCanceledException)
{
// application is shutting down
// ignore this
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
最有可能的是,作用域应该在循环内创建。这取决于Do Some Work
以及如何DbContext
使用。更好的想法可能是使用 DbContext Factory 而不是范围。
DbContext 不是连接。在必须读取或保存数据之前,它甚至不会保持连接打开。完成后,它会关闭连接。DbContext 是一个工作单元,它跟踪所有加载的对象、对它们所做的任何更改,并在调用时将所有更改保留在单个事务中SaveChanges
。处理它也会放弃更改,从而有效地回滚它们。
如果循环执行单独的“事务”,则必须在循环内创建作用域和 DbContext。这里很可能就是这种情况,因为循环每 5 分钟重复一次。
长期存在的 DbContext 没有真正的好处。要保持长期存在的 DbContext,您必须确保禁用且不Find()
使用跟踪,以防止缓存加载的实体。本质上,EF Core 将像 Dapper 一样工作,执行查询并将结果映射到对象。
使用 DbContext 工厂
如果范围仅用于创建 DbContext 实例,则替代方法是使用DbContextFactory。工厂将用于在循环内创建 DbContext,而无需显式创建范围。
该工厂使用AddDbContextFactory注册为单例:
builder.Services.AddDbContextFactory<ApplicationDbContext>(
options =>
options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));
Run Code Online (Sandbox Code Playgroud)
然后后台服务使用它来创建 DbContext:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyBackgroundJob(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var context = _contextFactory.CreateDbContext())
{
// Do Some work
}
}
...
Run Code Online (Sandbox Code Playgroud)
使用池化 DbContext Factory
一般来说,DbContext 对象是轻量级的,但它们确实有一些开销。然而,长期存在的服务将在其生命周期内分配(和处置)其中的许多。为了消除这种开销,可以使用池化 DbContext Factory 来创建可重用的 DbContext 实例。调用时Dispose()
,DbContext 实例将被清除并放入上下文池中,以便可以重用。
只需要将工厂注册更改为AddDbContextPool:
builder.Services.AddDbContextPool<ApplicationDbContext>(
options =>
options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));
Run Code Online (Sandbox Code Playgroud)
服务代码保持不变。
归档时间: |
|
查看次数: |
967 次 |
最近记录: |