Abp vNext 自定义 Ef Core 仓储引发异常

在使用自定义 Ef Core 仓储和 ABP vNext 注入的默认仓储时,通过两个 Repository 进行 Join 操作,提示 Cannot use multiple DbContext instances within a single query execution. Ensure the query uses a single context instance. 。这个异常信息翻译成中文的大概意思就是,你不能使用两个 DbContext 里面的 DbSet 进行 Join 查询。

Abp vNext 自定义 Ef Core 仓储引发异常

Abp vNext 自定义 Ef Core 仓储引发异常

如果将自定义仓储改为 IRepository<TEntity,TKey> 进行注入,是可以与 _courseRepostory 进行关联查询的。

我在 XXXEntityFrameworkCoreModule 的配置,以及自定义仓储 EfCoreStudentRepository 代码如下。

XXXEntityFrameworkCoreModule 代码:

public class XXXEntityFrameworkCoreModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext<XXXDbContext>(op => { op.AddDefaultRepositories(); }); Configure<AbpDbContextOptions>(op => op.UsePostgreSql()); } }

EfCoreStudentRepository 代码:

public class EfCoreStudentRepository : EfCoreRepository<IXXXDbContext, Student, long>, IStudentRepository { public EfCoreStudentRepository(IDbContextProvider<IXXXDbContext> dbContextProvider) : base(dbContextProvider) { } public Task<int> GetCountWithStudentlIdAsync(long studentId) { return DbSet.CountAsync(x=>x.studentId == studentId); } } 原因

原因在异常信息已经说得十分清楚了,这里我们需要了解两个问题。

什么原因导致两个仓储内部的 DbContext 不一致?

为什么 ABP vNext 自己实现的仓储能够进行关联查询呢?

首先我们得知道,仓储内部的 DbContext是怎么获取的。我们的自定义仓储都会继承 EfCoreRepository ,而这个仓储是实现了 IQuerable<T> 接口的,最终它会通过一个 IDbContextProvider<TDbContext> 获得一个可用的 DbContext 。

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>(); // 这里可以看到,是通过 IDbContextProvider 来获得 DbContext 的。 protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext(); protected virtual AbpEntityOptions<TEntity> AbpEntityOptions => _entityOptionsLazy.Value; private readonly IDbContextProvider<TDbContext> _dbContextProvider; private readonly Lazy<AbpEntityOptions<TEntity>> _entityOptionsLazy; // ... 其他代码。 }

下面就是 IDbContextProvider<TDbContext> 内部的核心代码:

public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext> where TDbContext : IEfCoreDbContext { private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IConnectionStringResolver _connectionStringResolver; // ... 其他代码。 public TDbContext GetDbContext() { var unitOfWork = _unitOfWorkManager.Current; if (unitOfWork == null) { throw new AbpException("A DbContext can only be created inside a unit of work!"); } var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>(); var connectionString = _connectionStringResolver.Resolve(connectionStringName); // 会构造一个 Key,而这个 Key 刚好是泛型类型的 FullName。 var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}"; // 内部是从一个字典当中,根据 dbContextKey 获取 DbContext。如果不存在的话则调用工厂方法创建一个新的 DbContext。 var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, () => new EfCoreDatabaseApi<TDbContext>( CreateDbContext(unitOfWork, connectionStringName, connectionString) )); return ((EfCoreDatabaseApi<TDbContext>)databaseApi).DbContext; } // ... 其他代码。 }

通过以上代码我们就可以知道,ABP vNext 在仓储的内部是通过 IDbContextProvider<TDbContext> 中的 TDbContext 泛型,来确定是否构建一个新的 DbContext 对象。

不论是 ABP vNext 针对 IRepository<TEntity,TKey> ,还是我们自己实现的自定义仓储,它们最终的实现都是基于 EfCoreRepository<TDbContext,TEntity,TKey> 的。而我们 IDbContextProvider<TDbContext> 的泛型,也是这个仓储基类提供的,后者的 TDbContext 就是前者的泛型参数。

所以当我们在模块添加 DbContext 的过城中,只要调用了 AddDefaultRepositories() 方法,ABP vNext 就会遍历你提供的 TDbContext 所定义的实体,然后为这些实体建立默认的仓储。

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

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