简体   繁体   English

Autofac注册和处置问题

[英]Autofac registration and disposal issues

I'm using entity framework 6 and Autofac in my web application. 我在Web应用程序中使用实体框架6和Autofac。

I inject unit of work with DbContext inside, both externally owned so I can dispose them myself. 我在内部注入了DbContext的工作单元,它们都是外部拥有的,所以我可以自己处置它们。

DbContext registered PerLifetimeScope, DbContext注册了PerLifetimeScope,

Unit of work is a factory, therefore registered as per dependency. 工作单位是工厂,因此根据依赖关系进行注册。

When Executing the first http Get action everthing works fine and I see the unit of work with the context are disposed after the response is coming from the db which is great. 当执行第一个http Get操作时,一切正常,并且我看到响应来自数据库之后,上下文的工作单元已经放置好了。

My issue is that whenever I execute a second request, the context for some reason is disposed before I return an IQueryable. 我的问题是,每当我执行第二个请求时,都会出于某种原因处理上下文,然后再返回IQueryable。 Therefore I get an execption saying: 因此,我得到一个执行:

The operation could not be executed because the DbContext is disposed. 由于已放置DbContext,因此无法执行该操作。

For example - calling the GetFolders method works the first time, and afterwards fails.. 例如-第一次调用GetFolders方法有效,然后失败。

I see the context is disposed too early, what I don't understand is what triggers it too soon in the second request.. 我看到上下文处理得太早了,我不明白是什么在第二个请求中触发了它。

public interface IUnitOfWork : IDisposable
{
    bool Commit();
}

public EFUnitOfWork : IUnitOfWork
{
    public IRepository<Folder> FoldersRepository {get; set;}

    public IRepository<Letter> LettersRepository {get; set;}

    private readonly DbContext _context;


    public EFUnitOfWork(DbContext context, IRepository<Folder> foldersRepo, IRepository<Letter> lettersRepo)
    {
        _context = context;
        _foldersRepo = foldersRepo;
        LettersRepository = lettersRepo;
    }

    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 bool Commit()
    {
        try
        {
            return SaveChanges() > 0;
        }
        catch (DbEntityValidationException exc)
        {
            // just to ease debugging
            foreach (var error in exc.EntityValidationErrors)
            {
                foreach (var errorMsg in error.ValidationErrors)
                {
                    logger.Log(LogLevel.Error, "Error trying to save EF changes - " + errorMsg.ErrorMessage);
                }
            }

            return false;
            throw exc;
        }
    }   
}



public class Repository<T> : IRepository<T>
{
    protected readonly DbContext Context;
    protected readonly DbSet<T> DbSet;

    public EFRepository(DbContext context)
    {
        Context = context;
    }

    public IQueryable<T> Get()
    {
        return DbSet;
    }

    public void Add(T item)
    {
        DbSet.Add(item);
    }

    public virtual Remove(T item)
    {
        DbSet.Remove(item);
    }

    public void Update(T item)
    {
        Context.Entry(item).State = EntityState.Modified;
    }

    public T FindById(int id)
    {
        return DbSet.Find(id); 
    }
}

public class DataService : IDataService
{
    private Func<IUnitOfWork> _unitOfWorkFactory;

    public (Func<IUnitOfWork> unitOfWorkFactory)
    {
        _unitOfWorkFactory = unitOfWorkFactory;             
    }

    public List<FolderPreview> GetFolders()
    {
        using(unitOfWork = _unitOfWorkFactory())
        {
            var foldersRepository = unitOfWork.FoldersRepository;

            var foldersData = foldersRepository.Get().Select(p => new FolderPreview
                                {
                                    Id = p.Id,
                                    Name = p.Name
                                }).ToList();

            return foldersData;
        }
    }
}

public class FolderPreview
{
    public int Id {get; set;}

    public string Name {get; set;}
}


Startup code:

{
    _container.RegisterGeneric<IRepository<>,Repository<>>().InstancePerLifetimeScope();
    _container.RegisterType<IDataService, DataService>().SingleInstance();
    _container.RegisterType<EFUnitOfWork, IUnitOfWork>().PerDepnendecny().ExternallyOwned();
    _container.RegisterType<DbContext, MyDbContext>().InstancePerLifetimeScope().ExternallyOwned(); 
}

Is this related to singletons some how? 这与单身人士有什么关系吗? Almost all of my application is singletons, the DataService is also Singleton. 我的几乎所有应用程序都是单例,DataService也是单例。 Anyone? 任何人?

Thanks! 谢谢!

The problem is that you are instancing only one Repository and one DbContext per request, but are instancing one new IUnitOfWork every time. 问题是您每个实例仅实例化一个Repository和一个DbContext ,但是每次实例化一个新的IUnitOfWork

So when you call GetFolders you are creating a new IUnitOfWork and disposing it (which disposes the DbContext -on IUnitOfWork.Dispose() -): so when you call GetFolders again, when you create a second IUnitOfWork , since it's the same lifetime scope, it's injecting the already-created repository and the already-created DbContext , which is disposed (the container doesn't try to create a new instance since you are on the same lifetime scope)... 因此,当您调用GetFolders您将创建一个新的IUnitOfWork并将其进行处置(这将DbContext -on IUnitOfWork.Dispose() -上):因此,当您再次调用GetFolders时,当您创建第二个IUnitOfWork ,由于它是相同的生命周期范围,它注入已创建的存储库和已创建的DbContext ,该数据库将被丢弃(由于您处于相同的生命周期范围内,因此容器不会尝试创建新实例)...

So on the second call, your Repository and IUnitOfWork are trying to use the disposed instance of DbContext , thus the error you are seeing. 因此,在第二个电话,您的RepositoryIUnitOfWork要使用的配置实例DbContext ,所以你看到的错误。


As a solution, you can just not dispose the DbContext on IUnitOfWork , and dispose it only at the end of your request... or you could even not dispose it at all: this may sound strange, but check this post 作为解决方案,您不能只将DbContext放在IUnitOfWork ,而仅在请求结束时才将其处置...或者甚至根本无法处置:这听起来很奇怪,但是请查看这篇文章。

I'm copying the important part in case the link goes dead, by Diego Vega: 我正在复制重要的部分,以防万一链接失效,作者:Diego Vega:

The default behavior of DbContext is that the underlying connection is automatically opened any time is needed and closed when it is no longer needed. DbContext的默认行为是在需要时自动打开基础连接,并在不再需要时关闭基础连接。 Eg when you execute a query and iterate over query results using “foreach”, the call to IEnumerable.GetEnumerator() will cause the connection to be opened, and when later there are no more results available, “foreach” will take care of calling Dispose on the enumerator, which will close the connection. 例如,当您执行查询并使用“ foreach”遍历查询结果时,对IEnumerable.GetEnumerator()的调用将导致连接打开,而当以后没有更多结果可用时,“ foreach”将负责调用放在枚举器上,它将关闭连接。 In a similar way, a call to DbContext.SaveChanges() will open the connection before sending changes to the database and will close it before returning. 以类似的方式,对DbContext.SaveChanges()的调用将在将更改发送到数据库之前打开连接,并在返回之前将其关闭。

Given this default behavior, in many real-world cases it is harmless to leave the context without disposing it and just rely on garbage collection. 考虑到这种默认行为,在许多实际情况下,无需丢弃上下文而仅依靠垃圾回收就可以保留上下文。

That said, there are two main reason our sample code tends to always use “using” or dispose the context in some other way: 也就是说,我们的示例代码倾向于始终使用“使用”或以其他方式处置上下文的两个主要原因是:

  1. The default automatic open/close behavior is relatively easy to override: you can assume control of when the connection is opened and closed by manually opening the connection. 默认的自动打开/关闭行为相对容易覆盖:您可以通过手动打开连接来控制何时打开和关闭连接。 Once you start doing this in some part of your code, then forgetting to dipose the context becomes harmful, because you might be leaking open connections. 一旦在代码的某些部分开始执行此操作,然后忘记丢弃上下文就变得有害,因为您可能会泄漏打开的连接。

  2. DbContext implements IDiposable following the recommended pattern, which includes exposing a virtual protected Dispose method that derived types can override if for example the need to aggregate other unmanaged resources into the lifetime of the context. DbContext按照建议的模式实现IDiposable,该模式包括公开一个虚拟的受保护的Dispose方法,例如,如果需要将其他非托管资源聚合到上下文的生命周期中,派生类型可以覆盖该方法。

So basically, unless you are managing the connection, or have a specific need to dispose it, it's safe to not do it. 因此,基本上,除非您正在管理连接或有特定的需要来处置它,否则不这样做是安全的。

I'd still recommend disposing it, of course, but in case you don't see where it'd be a good time to do it, you may just not do it at all. 当然,我仍然建议您处理它,但是如果您看不到在哪里做的好时机,您可能根本就不做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM