【问题标题】:Autofac factory of unit of work in a singleton单例中工作单元的 Autofac 工厂
【发布时间】:2016-03-02 20:18:24
【问题描述】:

正如标题所暗示的那样简单明了,这是否可能使用 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;
    }
}

【问题讨论】:

  • 你注册DbContext的时间是多久?
  • 我什么都试过了.. PerLifetimeScope、PerRequest、PerOwned.. 似乎没有任何效果
  • 你需要展示一些代码,展示失败的东西。
  • 编辑并添加了我的代码。
  • 您不应该这样做using (uow = uowFactory()) - 这是处理 uow 而不是将其留给容器来决定的行。将该行更改为uow = uowFactory();

标签: c# entity-framework dependency-injection autofac


【解决方案1】:

问题

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

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

解决方案

一种可能的解决方案是为每个 IEFUnitOfWork 及其子项创建一个 ILifetimeScope。 Autofac 通过提供Owned&lt;T&gt; 类型来促进这一点。我们可以将它与Func&lt;&gt; 工厂结合使用(另见documentation):

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 方法)或派生类可以这样做的情况下相关,否则该对象无论如何都不会放在终结器队列中。

【讨论】:

  • 非常感谢!我很高兴我终于可以通过,我几乎放弃了!!我用我自己的抽象 Autofac.Owned 并将我自己的抽象注册为外部拥有,它完美地工作!!!!!!!!!我想另外解释一下为什么在我的情况下处置应该只是 _context.dispose();我不太明白。
  • 参见here - 请注意,它包含性能 改进,除非您确实遇到性能问题,否则没有必要。另外请不要忘记投票/接受答案;-)
  • 托管或非托管是什么意思?有什么区别?我当然会投票!如果可以的话,我会投票一百万次!
  • 抱歉,与此同时,我已经编辑了我之前的评论,以提供一个更广泛地解释事情的链接。然而,这是一个“复杂”的话题,所以你可能还想看看herehere。但考虑一下,如果你想了解并正确理解它,你应该投入几个小时!
【解决方案2】:

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

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

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

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

【讨论】:

  • 它是单例的真正原因是它也是在单例内部创建的等等。我无法更改整个架构并从根更改所有单例
  • @S.Peter BatteryBackupUnit 的另一个答案将为您工作。我不知道 autofac 的 Owned
猜你喜欢
  • 2016-01-04
  • 1970-01-01
  • 2012-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
相关资源
最近更新 更多