单例中工作单元的自动工厂

本文关键字:工厂 工作 单元 单例中 | 更新日期: 2023-09-27 18:36:50

正如

标题所暗示的那样简单明了,这是使用Autofac依赖注入的可能吗?我一直在尝试和寻找到处..我在这里失去了希望。

我的班级必须是单例的——这不能改变。我希望它采用工作单元的工厂 - 用于数据库事务。

请帮我弄清楚这一点,我是分开的。

尝试了 Func<>并以各种可能的方式(各种生命周期,外部拥有与否)注册我的工作单元,但失败了,因为工作单元中的 DbContext 被释放并且不会在第一次请求后再次创建

编辑

添加了代码,希望有助于理解我的问题:

public class SingletonDataService : IDataService
{
    private _uowFactory;
    public SingletonDataService(Func<IEFUnitOfWork> uowFactory)
    {
        _uowFactory = uowFactory
    }
    public List<Folder> GetAllFolders ()
    {
        using (uow = uowFactory())
        {
            return uow.FoldersRepository.GetAll();
        }
    }
}
public MyDbContext : DbContext
{
    public DbSet<Folder> Folders {get; set;}
    public DbSet<Letter> Letters {get; set;}
    public MyDbContext() : base("myContext...")
}
public EFUnitOfWork : IEFUnitOfWork, IDisposable
{
    public IRepository<Folder> FoldersRepository;
    public IRepository<Letter> LettersRepository;
    private DbContext _context;
    public EFUnitOfWork(IRepository<Folder> folders, IRepository<Letter> letters, DbContext context)
    {
        _folders = folders;
        _letters = letters;
        _context = context;
    }
    private bool disposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
            disposed = true;
        }
    }
}
public Repository<T> : IRepository<T> where T: BaseEntity
{
    private DbContext _context;
    private DbSet<T> _set
    public Repository(DbContext context)
    {
        _context = context;
        _set = _context.Set<T>();
    }
}

public LettersController : ApiController
{
    private IDataService _dataService;
    public LettersController(IDataService dataService)
    {
        _dataService = dataService;
    }
    [HttpGet]
    public IHttpActionResult GetAllLetters()
    {
        return Ok(_dataService.GetAllLetters());
    }
}

// Must be singleton
builder.Register<SingletonDataService>().As(IDataService).SingleInstance();
builder.RegisterGeneric(typeof(Repository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();
builder.Register<EFUnitOfWork>().As(IEFUnitOfWork).InstancePerLifetimeScope();
builder.Register<DbContext>().As(AppDbContext).InstancePerLifetimeScope();

第一个请求中一切正常,在第二个第三个请求中,依此类推,我得到这个异常:

system.ObjectDisposedException: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.

我清楚地看到它发生了,因为我的存储库中的上下文现在为空,但我不知道如何使用 DI 更改它。

如果没有 DI,我想要实现的目标是如此简单:

如何使用Autofac实现以下目标?

public class UowFactory
{
    public UowFactory()
    {
    }
    public IEFUnitOfWork Create()
    {
        var context = new AppDbContext()
        var uow = new EFUnitOfWork(new Repository<Folder>(context), new Repository<Letter>(context), context);
        return uow;
    }
}

单例中工作单元的自动工厂

问题

您正在向InstancePerLifetimeScope()注册关键组件

构建 autofac 容器时,它还会创建一个根ILifetimeScope,该根一直存在到调用IContainer.Dispose()。现在,除非您在链中的某个地方创建嵌套ILifetimeScopeSingletonDataService,否则组件使用的ILifetimeScope是根ILifetimeScopeInstancePerLifetimeScope()实际上等同于SingleInstance()

溶液

可能的解决方案之一是为每个IEFUnitOfWork及其子项创建一个ILifetimeScope。Autofac 通过提供Owned<T>类型来促进这一点。我们可以将其与Func<>工厂结合使用(另请参阅文档):

public class SingletonDataService : IDataService
{
    private Func<Owned<IEFUnitOfWork>> _uowFactory;
    public SingletonDataService(Func<Owned<IEFUnitOfWork>> uowFactory)
    {
        _uowFactory = uowFactory
    }
    public List<Folder> GetAllFolders ()
    {
        using (var uow = _uowFactory())
        {
            return uow.Value.FoldersRepository.GetAll();
        }
    }
}

这应该可以很好地与以下注册配合使用:

// Must be singleton
builder.Register<SingletonDataService>().As(IDataService).SingleInstance();
builder.RegisterGeneric(typeof(Repository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();
builder.Register<EFUnitOfWork>().As(IEFUnitOfWork).InstancePerLifetimeScope();
builder.Register<DbContext>().As(AppDbContext).InstancePerLifetimeScope();

但是,请注意,DbContextInstancePerLifetimeScope约束基本上使手动处置EFUnitOfWork多余。

关于正确处置的旁注

由于IDisposable类型应该支持优雅的多处置,因此应该能够简化EFUnitOfWork.Dispose()

public void Dispose()
{
    _context.Dispose();   
}

另请注意,我省略了呼叫GC.SuppressFinalize(this);.此调用仅在类实现自定义终结器(~EFUnitOfWork方法)或派生类可以这样做的情况下相关,否则该对象无论如何都不会放在终结器队列中。

您的问题是您有一个单一实例(SingletonDataService)依赖于生存期较短的服务(EFUnitOfWork)。当 autofac 创建 SingletonDataService 实例时,它会获取 EFUnitOfWork 的实例,但此实例将始终保持不变(其实际生存期将比您预期的要长),因此会被释放并再次使用,从而产生错误。

您有两种可能的解决方案:

一种是创建一个类似于您在底部定义的UowFactory类(但依赖于IRepository<Folder>, IRepository<Letter>, DbContext),将其注册为任何内容(例如单例,但这无关紧要),并使SingletonDataService依赖于它。不过,这对您来说可能不是一个可行的解决方案,因为它还会延长IRepository<Folder>, IRepository<Letter>, DbContext实例的生命周期并在那里产生问题。

正确的解决方案是删除您希望 SingletonDataService 成为单一实例(可能是某个缓存)的原因。并将其移动到 SingletonDataService 所依赖的新服务(CacheService?),并使该新服务成为单一实例。