ABP vNext 不使用工作单元为什么会抛出异常

该问题经常出现在 ABP vNext 框架当中,要复现该问题十分简单,只需要你注入一个 IRepository<T,TKey> 仓储,在任意一个地方调用 IRepository<T,TKey>.ToList() 方法。

[Fact] public void TestMethod() { var rep = GetRequiredService<IHospitalRepository>(); var result = rep.ToList(); }

例如上面的测试代码,不出意外就会提示 System.ObjectDisposedException 异常,具体的异常内容信息:

System.ObjectDisposedException : Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

其实已经说得十分明白了,因为你要调用的 DbContext 已经被释放了,所以会出现这个异常信息。

二、原因 2.1 为什么能够调用 LINQ 扩展?

我们之所以能够在 IRepository<TEntity,TKey> 接口上面,调用 LINQ 相关的流畅接口,是因为其父级接口 IReadOnlyRepository<TEntity,TKey> 继承了 IQueryable<TEntity> 接口。如果使用的是 Entity Framework Core 框架,那么在解析 IRepository<T,Key> 的时候,我们得到的是一个 EfCoreRepository<TDbContext, TEntity,TKey> 实例。

针对这个实例,类型 EfCoreRepository<TDbContext, TEntity> 则是它的基类型,继续跳转到其基类 RepositoryBase<TEntity> 我们就能看到它实现了 IQueryable<T> 接口必备的几个属性。

public abstract class RepositoryBase<TEntity> : BasicRepositoryBase<TEntity>, IRepository<TEntity> where TEntity : class, IEntity { // ... 忽略的代码。 public virtual Type ElementType => GetQueryable().ElementType; public virtual Expression Expression => GetQueryable().Expression; public virtual IQueryProvider Provider => GetQueryable().Provider; // ... 忽略的代码。 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator<TEntity> GetEnumerator() { return GetQueryable().GetEnumerator(); } protected abstract IQueryable<TEntity> GetQueryable(); // ... 忽略的代码。 } 2.2 IQueryable 使用的 DbContext

上一个小节的代码中,我们可以看出最后的 IQueryable<TEntity> 是通过抽象方法 GetQueryable() 取得的。这个抽象方法,在 EF Core 当中的实现如下。

public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IEfCoreRepository<TEntity> where TDbContext : IEfCoreDbContext where TEntity : class, IEntity { public virtual DbSet<TEntity> DbSet => DbContext.Set<TEntity>(); DbContext IEfCoreRepository<TEntity>.DbContext => DbContext.As<DbContext>(); protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext(); private readonly IDbContextProvider<TDbContext> _dbContextProvider; // ... 忽略的代码。 public EfCoreRepository(IDbContextProvider<TDbContext> dbContextProvider) { _dbContextProvider = dbContextProvider; // ... 忽略的代码。 } // ... 忽略的代码。 protected override IQueryable<TEntity> GetQueryable() { return DbSet.AsQueryable(); } // ... 忽略的代码。 }

所以我们就可以知道,当调用 IQueryable<TEntity>.ToList() 方法时,实际是使用的 IDbContextProvider<TDbContext> 解析出来的数据库上下文对象。

跳转到这个 DbContextProvider 的具体实现,可以看到他是通过 IUnitOfWorkManager(工作单元管理器) 得到可用的工作单元,然后通过工作单元提供的 IServiceProvider 解析所需要的数据库上下文对象。

public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext> where TDbContext : IEfCoreDbContext { private readonly IUnitOfWorkManager _unitOfWorkManager; public UnitOfWorkDbContextProvider( IUnitOfWorkManager unitOfWorkManager) { _unitOfWorkManager = unitOfWorkManager; } // ... 上述代码有所精简。 public TDbContext GetDbContext() { var unitOfWork = _unitOfWorkManager.Current; // ... 忽略部分代码。 // 重点在 CreateDbContext() 方法内部。 var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, () => new EfCoreDatabaseApi<TDbContext>( CreateDbContext(unitOfWork, connectionStringName, connectionString) )); return ((EfCoreDatabaseApi<TDbContext>)databaseApi).DbContext; } private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) { // ... 忽略部分代码。 using (DbContextCreationContext.Use(creationContext)) { var dbContext = CreateDbContext(unitOfWork); // ... 忽略部分代码。 return dbContext; } } private TDbContext CreateDbContext(IUnitOfWork unitOfWork) { return unitOfWork.Options.IsTransactional ? CreateDbContextWithTransaction(unitOfWork) // 重点 !!! : unitOfWork.ServiceProvider.GetRequiredService<TDbContext>(); } public TDbContext CreateDbContextWithTransaction(IUnitOfWork unitOfWork) { // ... 忽略部分代码。 if (activeTransaction == null) { // 重点 !!! var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>(); // ... 忽略部分代码。 return dbContext; } else { // ... 忽略部分代码。 // 重点 !!! var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>(); // ... 忽略部分代码。 return dbContext; } } } 2.3 DbContext 和工作单元的销毁

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxyxj.html