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 查询。


如果将自定义仓储改为 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 所定义的实体,然后为这些实体建立默认的仓储。
在注入仓储的时候,找到了获得默认仓储实现类型的方法,可以看到这里它使用的是 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);
}
}
Abp vNext 自定义 Ef Core 仓储引发异常的更多相关文章
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
随机推荐
- 关于Qt画点及计算机专业基础课程介绍
在计算机图形图像学中,开始都是先画点,我曾经在汇编上tc2.0上画点,后来是MFC,VB,Qt,Python,我觉得对于计算机专业的选择QT的原因是它是个C系的功能强大庞大的库,可以少写很多代码,但是 ...
- Vue中的循环以及修改差值表达式
0828自我总结 一.Vue中的循环 v-for 常见的4总情况 #第一种 <div v-for="item in items"></div> #第二种 & ...
- django-drf框架中排序和查询组件
0910自我总结 django-drf框架中排序和查询组件 一查询相关 1.模糊查询 1.导入模块组件 from rest_framework.filters import SearchFilter ...
- 微信小程序与用户交互
微信小程序与用户交互 一.显示消息提示框 wx.showToast({属性名:属性值}) 自定义一个提示框,时间到了会自动关闭 wx.showToast({ title:"成功", ...
- Spring Boot Actuator 整合 Prometheus
简介 Spring Boot 自带监控功能 Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况.Bean加载情况.环境变量.日志信息.线程信息等.这一节结合 Prometheus .G ...
- Neo4j:图数据库GraphDB(二)高级查找
接着上一篇,继续介绍图数据库Neo4j: http://www.cnblogs.com/rongyux/p/5537028.html 三 高级查找操作 1过滤 name = "Tom H ...
- Java SPI、servlet3.0与@HandlesTypes源码分析
关于Java SPI与servlet3.0的应用,这里说的很精炼,链接地址如下. https://blog.csdn.net/pingnanlee/article/details/80940993 以 ...
- 详细解读 Spring AOP 面向切面编程(一)
又是一个周末, 今天我要和大家分享的是 AOP(Aspect-Oriented Programming)这个东西,名字与 OOP 仅差一个字母,其实它是对 OOP 编程方式的一种补充,并非是取而代之. ...
- c++11::std::remove_reference
引用移除 : remove_reference 引用折叠规则 A& & 折叠成 A& A& && 折叠成 A& A&& &a ...
- vue —— 监听
vue的监听用途很大 比如:通过数据的值的变化,执行某个方法 首先:data中要有个变量初始值 finalTotalAmount的初始值是0 我们想当finalTotalAmount值发生变化时,执行 ...