自动FAC注册和处置问题

本文关键字:问题 FAC 注册 自动 | 更新日期: 2023-09-27 18:35:54

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

我在内部注入了带有 DbContext 的工作单元,两者都是外部拥有的,因此我可以自己处理它们。

DbContext registered PerLifetimeScope,

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

当执行第一个 http Get 操作时,一切都可以正常工作,我看到在响应来自数据库后处理了上下文的工作单元,这很棒。

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

无法执行该操作,因为已释放 DbContext。

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

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

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(); 
}

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

谢谢!

自动FAC注册和处置问题

问题是每个请求只实例化一个Repository和一个DbContext,但每次都实例化一个新IUnitOfWork

因此,当您调用GetFolders时,您正在创建一个新IUnitOfWork并释放它(这会释放DbContext -on IUnitOfWork.Dispose() -):因此,当您再次调用GetFolders时,当您创建第二个IUnitOfWork时,由于它是相同的生存期范围,因此它会注入已创建的存储库和已创建的DbContext,该存储库已释放(容器不会尝试创建新实例,因为您在相同的生命周期范围内)...

因此,在第二次调用时,您的RepositoryIUnitOfWork正在尝试使用DbContext的已释放实例,因此您看到的错误。

<小时 />

作为一种解决方案,您不能在IUnitOfWork上处置DbContext,而只能在请求结束时处置它......或者你甚至根本无法处理它:这听起来可能很奇怪,但请查看这篇文章

我正在复制重要的部分,以防链接失效,由迭戈维加:

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

鉴于此默认行为,在许多实际情况下,离开上下文而不释放上下文并仅依赖垃圾回收是无害的。

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

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

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

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

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