首页 > 解决方案 > 数据上下文注册为瞬态,但内存使用量不断增长。我的 DI 配置有问题吗?

问题描述

我在.NET Core 3.1 控制台应用程序中使用EF CorePostgres(可能没关系)。

该程序使用一个共享项目(在解决方案的其他组件中),所有业务逻辑都使用带有 Mediator 的简单 CQRS 类型模式实现。

在一个地方,我从数据库中检索大对象(10 - 100MB)。这不是很频繁,因此本身不是问题。在现代硬件上只需要几分之一秒。问题是由于某种原因,这些对象在命令执行之间被缓存在数据上下文中,就像数据上下文没有被释放一样。

我不明白为什么,因为我将 DI 容器(内置标准)中的 DbContext 注册为瞬态。我如何理解它应该在每次请求时创建一个新实例,而垃圾收集器应该负责其余的工作。

注册码是这样的:

static IServiceProvider ConfigureServiceProvider()
{
    IServiceCollection services = new ServiceCollection();
    
    DbContextOptions<MyAppDbContext> dbContextOptions = new DbContextOptionsBuilder<MyAppDbContext>()
       .UseNpgsql(Configuration.GetConnectionString("MyApp.Db"))
       .Options;
    services.AddSingleton(dbContextOptions);
    services.AddDbContext<MyAppDbContext>(options => options.UseNpgsql(Configuration.GetConnectionString("MyApp.Db"), options => options.EnableRetryOnFailure()), ServiceLifetime.Transient);
    services.AddTransient<IMyAppDbContext>(s => s.GetService<MyAppDbContext>());

    // (...)
}

然后命令以这种方式使用它:

public class RecalculateSomething : IRequest
{
    public Guid SomeId { get; set; }

    public class Handler : IRequestHandler<RecalculateSomething>
    {
        private IMyAppDbContext context;
        private readonly IMediator mediator;

        public Handler(IMyAppDbContext context, IMediator mediator)
        {
            this.context = context ?? throw new ArgumentNullException(nameof(context));
            this.mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        }
        
        public async Task<Unit> Handle(RecalculateSomething request, CancellationToken ct)
        {       
            // (...)
        }
    }
}

有谁知道问题是什么?是我在配置 DI 容器时做错了什么吗?或者其他东西,比如我在某个地方持有的参考资料(找不到它)。解决此类问题的最佳方法是什么?

顺便说一句,我通过强制它每次从 DbContextOptions 创建一个新的 DbContext 来“修复它”,但这更像是一种解决方法。想知道核心问题是什么。

标签: c#dependency-injectionentity-framework-core

解决方案


您几乎从不想注册一个DbContext. 最好注册一个工厂,您可以使用它来DbContext根据每个请求创建一个新工厂。虽然这看起来效率低下,但这种模式多年来一直在优化,并允许您根据每个请求确定性地回收资源。

文件

DbContext 的生命周期从创建实例开始,到释放实例结束。DbContext 实例旨在用于单个工作单元。这意味着 DbContext 实例的生命周期通常很短。

该文档继续解释说您可以注入它,您正在这样做,但问题是这缺乏确定性的资源回收。最好使用工厂将其保留在您的权力范围内,稍后将在同一文档中进行说明

某些应用程序类型(例如 ASP.NET Core Blazor)使用依赖注入,但不会创建与所需 DbContext 生命周期一致的服务范围。即使确实存在这种对齐方式,应用程序也可能需要在此范围内执行多个工作单元。例如,单个 HTTP 请求中的多个工作单元。

在这些情况下,AddDbContextFactory 可用于注册工厂以创建 DbContext 实例。

我敢打赌,正是这种不确定性、查询的大尺寸以及查询的频率在您的应用程序中造成了一些内存压力。

您可以查看添加工厂 ( AddDbContextFactory ) 并使用它来创建上下文是否有帮助;再次,请参阅文档中上面引用的相同部分以获取实际代码。

如果您想知道为什么DbContext瞬态的 a 是不确定的,您可能需要深入研究 .NET Core 代码库以查看何时回收瞬态资源。这可能不是在您退出处理程序之后,而是在回滚(可能很深)调用堆栈之后的某个地方。

(如果是这种情况,可以这样想——许多试图完成的调用正在展开他们的堆栈;这种短暂的情况是可能在一段时间内建立的压力)。

您的 DI 本身并没有错,但对于您要完成的工作以及您正在运行的代码的上下文而言,它似乎并不是最佳选择。


推荐阅读