经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
Abp vNext 自定义 Ef Core 仓储引发异常
来源:cnblogs  作者:myzony  时间:2019/11/15 9:03:24  对本文有异议

问题

在使用自定义 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 查询。

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

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

XXXEntityFrameworkCoreModule 代码:

  1. public class XXXEntityFrameworkCoreModule : AbpModule
  2. {
  3. public override void ConfigureServices(ServiceConfigurationContext context)
  4. {
  5. context.Services.AddAbpDbContext<XXXDbContext>(op =>
  6. {
  7. op.AddDefaultRepositories();
  8. });
  9. Configure<AbpDbContextOptions>(op => op.UsePostgreSql());
  10. }
  11. }

EfCoreStudentRepository 代码:

  1. public class EfCoreStudentRepository : EfCoreRepository<IXXXDbContext, Student, long>, IStudentRepository
  2. {
  3. public EfCoreStudentRepository(IDbContextProvider<IXXXDbContext> dbContextProvider) : base(dbContextProvider)
  4. {
  5. }
  6. public Task<int> GetCountWithStudentlIdAsync(long studentId)
  7. {
  8. return DbSet.CountAsync(x=>x.studentId == studentId);
  9. }
  10. }

原因

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

  1. 什么原因导致两个仓储内部的 DbContext 不一致?
  2. 为什么 ABP vNext 自己实现的仓储能够进行关联查询呢?

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

  1. public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IEfCoreRepository<TEntity>
  2. where TDbContext : IEfCoreDbContext
  3. where TEntity : class, IEntity
  4. {
  5. public virtual DbSet<TEntity> DbSet => DbContext.Set<TEntity>();
  6. DbContext IEfCoreRepository<TEntity>.DbContext => DbContext.As<DbContext>();
  7. // 这里可以看到,是通过 IDbContextProvider 来获得 DbContext 的。
  8. protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext();
  9. protected virtual AbpEntityOptions<TEntity> AbpEntityOptions => _entityOptionsLazy.Value;
  10. private readonly IDbContextProvider<TDbContext> _dbContextProvider;
  11. private readonly Lazy<AbpEntityOptions<TEntity>> _entityOptionsLazy;
  12. // ... 其他代码。
  13. }

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

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

通过以上代码我们就可以知道,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 类型。

  1. protected virtual Type GetDefaultRepositoryImplementationType(Type entityType)
  2. {
  3. var primaryKeyType = EntityHelper.FindPrimaryKeyType(entityType);
  4. // 重点在于构造仓储类型时,传递的 Options.DefaultRepositoryDbContextType 参数,这个参数就是后面 EfCoreRepository 的 TDbContext 泛型。
  5. if (primaryKeyType == null)
  6. {
  7. return Options.SpecifiedDefaultRepositoryTypes
  8. ? Options.DefaultRepositoryImplementationTypeWithoutKey.MakeGenericType(entityType)
  9. : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType);
  10. }
  11. return Options.SpecifiedDefaultRepositoryTypes
  12. ? Options.DefaultRepositoryImplementationType.MakeGenericType(entityType, primaryKeyType)
  13. : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType, primaryKeyType);
  14. }

最后我发现这个就是在模块调用 AddAbpContext<TDbContext> 所提供的泛型参数。

  1. public abstract class AbpCommonDbContextRegistrationOptions : IAbpCommonDbContextRegistrationOptionsBuilder
  2. {
  3. // ... 其他代码
  4. protected AbpCommonDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services)
  5. {
  6. OriginalDbContextType = originalDbContextType;
  7. Services = services;
  8. DefaultRepositoryDbContextType = originalDbContextType;
  9. CustomRepositories = new Dictionary<Type, Type>();
  10. ReplacedDbContextTypes = new List<Type>();
  11. }
  12. // ... 其他代码
  13. }
  14. public class AbpDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions, IAbpDbContextRegistrationOptionsBuilder
  15. {
  16. public Dictionary<Type, object> AbpEntityOptions { get; }
  17. public AbpDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services)
  18. : base(originalDbContextType, services) // 之类调用的就是上面的构造方法。
  19. {
  20. AbpEntityOptions = new Dictionary<Type, object>();
  21. }
  22. }
  23. public static class AbpEfCoreServiceCollectionExtensions
  24. {
  25. public static IServiceCollection AddAbpDbContext<TDbContext>(
  26. this IServiceCollection services,
  27. Action<IAbpDbContextRegistrationOptionsBuilder> optionsBuilder = null)
  28. where TDbContext : AbpDbContext<TDbContext>
  29. {
  30. // ... 其他代码。
  31. var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services);
  32. // ... 其他代码。
  33. return services;
  34. }
  35. }

所以,我们的默认仓储的 dbContextKeyXXXDbContext,我们的自定义仓储继承 EfCoreRepository<IXXXDbContext,TEntity,TKey> ,所以它的 dbContextKey 就是 IXXXDbContext 。所以自定义仓储获取到的 DbContext 就与自定义仓储的不一致了,从而提示上述异常。

解决

找到自定自定义仓储的定义,修改它 EfCoreReposiotry<TDbContext,TEntity,TKey>TDbContext 泛型参数,变更为 XXXDbContext 即可。

  1. public class EfCoreStudentRepository : EfCoreRepository<XXXDbContext, Student, long>, IStudentRepository
  2. {
  3. public EfCoreStudentRepository(IDbContextProvider<XXXDbContext> dbContextProvider) : base(dbContextProvider)
  4. {
  5. }
  6. public Task<int> GetCountWithStudentlIdAsync(long studentId)
  7. {
  8. return DbSet.CountAsync(x=>x.studentId == studentId);
  9. }
  10. }

原文链接:http://www.cnblogs.com/myzony/p/11863489.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号