繁体   English   中英

Autofac注册和处置问题

[英]Autofac registration and disposal issues

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

我在内部注入了DbContext的工作单元,它们都是外部拥有的,所以我可以自己处置它们。

DbContext注册了PerLifetimeScope,

工作单位是工厂,因此根据依赖关系进行注册。

当执行第一个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 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(); 
}

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

谢谢!

问题是您每个实例仅实例化一个Repository和一个DbContext ,但是每次实例化一个新的IUnitOfWork

因此,当您调用GetFolders您将创建一个新的IUnitOfWork并将其进行处置(这将DbContext -on IUnitOfWork.Dispose() -上):因此,当您再次调用GetFolders时,当您创建第二个IUnitOfWork ,由于它是相同的生命周期范围,它注入已创建的存储库和已创建的DbContext ,该数据库将被丢弃(由于您处于相同的生命周期范围内,因此容器不会尝试创建新实例)...

因此,在第二个电话,您的RepositoryIUnitOfWork要使用的配置实例DbContext ,所以你看到的错误。


作为解决方案,您不能只将DbContext放在IUnitOfWork ,而仅在请求结束时才将其处置...或者甚至根本无法处置:这听起来很奇怪,但是请查看这篇文章。

我正在复制重要的部分,以防万一链接失效,作者:Diego Vega:

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

考虑到这种默认行为,在许多实际情况下,无需丢弃上下文而仅依靠垃圾回收就可以保留上下文。

也就是说,我们的示例代码倾向于始终使用“使用”或以其他方式处置上下文的两个主要原因是:

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

  2. DbContext按照建议的模式实现IDiposable,该模式包括公开一个虚拟的受保护的Dispose方法,例如,如果需要将其他非托管资源聚合到上下文的生命周期中,派生类型可以覆盖该方法。

因此,基本上,除非您正在管理连接或有特定的需要来处置它,否则不这样做是安全的。

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

暂无
暂无

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

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