在没有异步的情况下运行重复调用时出现“无法访问已处置的上下文实例”异常

问题描述 投票:0回答:1

我在实体框架方面遇到了一些问题。

我需要每 5 分钟重新加载一些缓存对象,因此为此使用了 Timer 对象,当我第一次填充它时,它按预期工作,但在第二次运行时我收到了此错误:

'Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'PostgreSqlContext'.'

这里是启动配置:

services.AddDbContext<PostgreSqlContext>(options =>
{
    options.UseNpgsql(postgresConnectionString);
    options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
});

PostgreSqlContext 对象:

public class PostgreSqlContext : DbContext
{
    public PostgreSqlContext(DbContextOptions<PostgreSqlContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }

    public async Task<int> SaveChangesAsync()
    {
        ChangeTracker.DetectChanges();
        return await base.SaveChangesAsync();
    }
    
    public DbSet<Product> Product { get; set; }    
}

存储库逻辑:

public class ProductRepository : IProductRepository
{
    private readonly PostgreSqlContext _context;

    public ProductRepository(PostgreSqlContext context)
    {
        _context = context;
    }

    public List<Product> GetAllProducts()
    {
        var result = _context.Product.ToList();

        return result;
    }
}

用途:

public class SomeLogic
{
    private readonly IProductRepository _productRepository;
    private Timer _timer;

    public SomeLogic(IProductRepository productRepository)
    {
        _productRepository = productRepository;

        BuildCache();
        _timer = new Timer(ReloadHandler, null, 60000, Timeout.Infinite);
    }

    private void ReloadHandler(object state = null)
    {
        BuildCache();
        _timer.Change(60000, Timeout.Infinite);
    }

    public void BuildCache()
    {
        var products = _productRepository.GetAllProducts();
        // doing something with it
    }
}


我看到了很多关于异步/等待调用的答案,但就我而言,它不相关,因为我不使用任务

我明白了,发生这种情况是因为该对象已被处置,但我不知道如何使其恢复生机,或以某种方式重新初始化。

请让我知道错误。

谢谢!

UPD。也添加了定时器的使用,但是失败与PostgreSqlContext有关。

此外,如果还有其他方法可以做到这一点,我会很高兴知道

c# .net entity-framework dispose autodispose
1个回答
0
投票

如上所述,Timer 事件将在初始请求结束后很长时间内执行,因此作用域 DbContext 实例将被释放并且不可用。内存缓存可能不会有太大用处,因为它只包含读取时的产品,您可能希望反映当前数据状态而无需重新启动服务。

在处理多线程操作(包括计时器事件等)时,您需要将 DbContext 实例的范围限制在线程/事件范围内。干净地完成此操作的最简单方法是使用生命周期范围为单例的 DbContext Factory。然后,该工厂可以根据需要构造并提供消费者负责处置的 DbContext 实例。最好不要尝试使用额外的存储库层来抽象 EF。 EF 已经提供了

DbSet
形式的存储库。

private readonly IDbContextFactory _dbContextFactory;

private Timer _timer;

public SomeLogic(IDbContextFactory dbContextFactory)
{
    _dbContextFactory = dbContextFactory;

    BuildCache();
    _timer = new Timer(ReloadHandler, null, 60000, Timeout.Infinite);
}

private void ReloadHandler(object state = null)
{
    BuildCache();
    _timer.Change(60000, Timeout.Infinite);
}

public void BuildCache()
{
    using var dbContext = _dbContextFactory.Create();
    var products = dbContext.Products.ToList();
    // doing something with it
}

从技术上讲,代码可以通过将 DbContext 注册为单例来工作,但是您不希望将其作为解决方案,因为长期存在的 DbContext 将导致性能问题,并且最终会根据其跟踪缓存提供过时的数据行为。将 DbContext 范围保留为生命周期范围/请求意味着可以正常进行对 DbContext/存储库的所有常规调用。如果您确实想追求基于存储库的抽象,我建议您研究工作单元模式来包装它们,其中工作单元有一个可以注册为单例的工厂,并管理 DbContext 的生命周期范围,以便工厂并且存储库可以注册为 Singleton 以避免处置错误。这些存储库不会使用 DbContext 进行初始化,而是使用 Context 范围定位器来在需要时解析 DbContext。您可以查看 Medhi el Gueddari 的 DbContextScope。 (https://mehdi.me/ambient-dbcontext-in-ef6/) EF Core 有多个分支,我使用 Zejji 管理的一个,它保持了一致的约定和命名 (https://www. nuget.org/packages/Zejji.DbContextScope.EFCore)

© www.soinside.com 2019 - 2024. All rights reserved.