Autofac注册和处理问题 [英] Autofac registration and disposal issues

查看:509
本文介绍了Autofac注册和处理问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的网络应用程序中使用实体框架6和Autofac。



我在DbContext里面注入了工作单元,外部拥有,所以我可以自己处理



DbContext注册PerLifetimeScope,



工作单位是一个工厂,因此根据依赖关系注册。 p>

当执行第一个http Get动作时,工作正常,我看到上下文的工作单位在响应来自数据库是很好的后处理。



我的问题是,每当我执行第二个请求时,上下文由于某些原因在我返回IQueryable之前被处理。因此,我得到一个执行说:


由于DbContext被处理,操作无法执行。


例如 - 调用GetFolders方法第一次工作,然后失败..



我看到上下文处理得太早了,我不明白是什么触发它太早在第二个请求..

  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 boolwhere = false;

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposal)
{
if(!disposal)
{
if(disposal)
{
_context.Dispose();
}

disposal = true;
}
}

public bool Commit()
{
try
{
return SaveChanges()> 0;
}
catch(DbEntityValidationException exc)
{
//只是为了缓解调试
foreach(exc.EntityValidationErrors中的var错误)
{
foreach(var errorMsg in error.ValidationErrors)
{
logger.Log(LogLevel.Error,尝试保存EF更改时出错 - + errorMsg.ErrorMessage);
}
}

return false;
抛出
}
}
}



public class Repository< T> :IRepository< T>
{
protected readonly DbContext Context;
受保护的只读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()。选择(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;}
}


启动代码:

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

这与单身人士有关吗?几乎所有的应用程序都是单例,DataService也是Singleton。任何人?



谢谢!

解决方案

问题是你是每个请求仅实例化一个 Repository 和一个 DbContext ,而是实例化一个新的 IUnitOfWork 每次



所以当你调用 GetFolders 你正在创建一个新的 IUnitOfWork 并处理它(它处理 DbContext -on IUnitOfWork.Dispose() - ):所以当您再次调用 GetFolders 时,创建第二个 IUnitOfWork ,因为它的使用寿命相同,它注入已经创建的存储库和已经创建的 DbContext ,这是被处理的(容器不会尝试创建一个新的实例,因为你在同一生命周期范围)...



所以在第二次调用时,你的 Repository IUnitOfWork 正在尝试使用已处置的 DbContext 的实例,因此您所看到的错误。






作为一个解决方案,您只能不处理 DbContext IUnitOfWork 中,只能在您的请求结束时处理...或者甚至不能处理它:这可能听起来很奇怪,但是查看这篇文章



我正在复制重要的部分,以防链接死亡,由Diego Vega:


DbContext的默认行为是底层连接在需要时自动打开,并在不再需要时关闭。例如。当您执行查询并使用foreach对查询结果进行迭代时,对IEnumerable.GetEnumerator()的调用将导致连接被打开,而后来没有更多的结果可用,foreach将负责调用Dispose在枚举器上,这将关闭连接。以类似的方式,调用DbContext.SaveChanges()将在将更改发送到数据库之前打开连接,并在返回之前关闭它。



鉴于这种默认行为,在许多现实世界的情况下,离开上下文而不处理它,只依靠垃圾回收是无害的。



这就是说,我们的示例代码往往总是使用使用或以其他方式处理上下文的两个主要原因:


  1. 默认的自动打开/关闭行为相对容易被覆盖:您可以通过手动打开连接来控制何时打开和关闭连接。一旦您在代码的某些部分开始执行此操作,那么忘记了上下文会变得有害,因为您可能会泄露打开的连接。


  2. DbContext根据推荐的模式实现IDiposable,其中包括暴露一个虚拟保护的Dispose方法,如果需要将其他非托管资源上下文的生命周期。



所以基本上,除非你正在管理连接,要么有特定的需要处理它,这是不安全的。



当然,我仍然建议处理它,但是如果你看不到它会在哪里一个好时机,你可能根本就不做。


I'm using entity framework 6 and Autofac in my web application.

I inject unit of work with DbContext inside, both externally owned so I can dispose them myself.

DbContext registered 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.

My issue is that whenever I execute a second request, the context for some reason is disposed before I return an IQueryable. Therefore I get an execption saying:

The operation could not be executed because the DbContext is disposed.

For example - calling the GetFolders method works the first time, and afterwards fails..

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. 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.

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)...

So on the second call, your Repository and IUnitOfWork are trying to use the disposed instance of DbContext, thus the error you are seeing.


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

I'm copying the important part in case the link goes dead, by 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. E.g. 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. 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.

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.

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.

这篇关于Autofac注册和处理问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆