在使用自定义 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 查询。ide
若是将自定义仓储改成 IRepository<TEntity,TKey>
进行注入,是能够与 _courseRepostory
进行关联查询的。post
我在 XXXEntityFrameworkCoreModule
的配置,以及自定义仓储 EfCoreStudentRepository
代码以下。ui
XXXEntityFrameworkCoreModule
代码:this
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); } }
缘由在异常信息已经说得十分清楚了,这里咱们须要了解两个问题。code
首先咱们得知道,仓储内部的 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>
内部的核心代码:blog
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
所定义的实体,而后为这些实体创建默认的仓储。
在注入仓储的时候,找到了得到默认仓储实现类型的方法,能够看到这里它使用的是 DefaultRepositoryDbContextType
做为默认的 TDbContext
类型。
protected virtual Type GetDefaultRepositoryImplementationType(Type entityType) { var primaryKeyType = EntityHelper.FindPrimaryKeyType(entityType); // 重点在于构造仓储类型时,传递的 Options.DefaultRepositoryDbContextType 参数,这个参数就是后面 EfCoreRepository 的 TDbContext 泛型。 if (primaryKeyType == null) { return Options.SpecifiedDefaultRepositoryTypes ? Options.DefaultRepositoryImplementationTypeWithoutKey.MakeGenericType(entityType) : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType); } return Options.SpecifiedDefaultRepositoryTypes ? Options.DefaultRepositoryImplementationType.MakeGenericType(entityType, primaryKeyType) : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType, primaryKeyType); }
最后我发现这个就是在模块调用 AddAbpContext<TDbContext>
所提供的泛型参数。
public abstract class AbpCommonDbContextRegistrationOptions : IAbpCommonDbContextRegistrationOptionsBuilder { // ... 其余代码 protected AbpCommonDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services) { OriginalDbContextType = originalDbContextType; Services = services; DefaultRepositoryDbContextType = originalDbContextType; CustomRepositories = new Dictionary<Type, Type>(); ReplacedDbContextTypes = new List<Type>(); } // ... 其余代码 } public class AbpDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions, IAbpDbContextRegistrationOptionsBuilder { public Dictionary<Type, object> AbpEntityOptions { get; } public AbpDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services) : base(originalDbContextType, services) // 之类调用的就是上面的构造方法。 { AbpEntityOptions = new Dictionary<Type, object>(); } } public static class AbpEfCoreServiceCollectionExtensions { public static IServiceCollection AddAbpDbContext<TDbContext>( this IServiceCollection services, Action<IAbpDbContextRegistrationOptionsBuilder> optionsBuilder = null) where TDbContext : AbpDbContext<TDbContext> { // ... 其余代码。 var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services); // ... 其余代码。 return services; } }
因此,咱们的默认仓储的 dbContextKey
是 XXXDbContext
,咱们的自定义仓储继承 EfCoreRepository<IXXXDbContext,TEntity,TKey>
,因此它的 dbContextKey
就是 IXXXDbContext
。因此自定义仓储获取到的 DbContext
就与自定义仓储的不一致了,从而提示上述异常。
找到自定自定义仓储的定义,修改它 EfCoreReposiotry<TDbContext,TEntity,TKey>
的 TDbContext
泛型参数,变动为 XXXDbContext
便可。
public class EfCoreStudentRepository : EfCoreRepository<XXXDbContext, Student, long>, IStudentRepository { public EfCoreStudentRepository(IDbContextProvider<XXXDbContext> dbContextProvider) : base(dbContextProvider) { } public Task<int> GetCountWithStudentlIdAsync(long studentId) { return DbSet.CountAsync(x=>x.studentId == studentId); } }