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 开发博客项目 - 给项目瘦身,让它跑起来 ...
随机推荐
- python编程基础之六
运算符和表达式 +,-,*,/, 加减乘除 %, 模运算 **, 幂运算 // 整除 运算优先级方面:** >正负号(+,-)>//,%>*,/>+,- 模运算有一 ...
- 云计算之走进LINUX(一)
引言 小比特的随笔: 亲爱的博友所有随笔部分记录的是小比特的一些学习笔记,阅读性不是太强仅供有基础的博友参考,对小白来说阅读起来可能会有些吃力.当然也可以参考啦!小比特将在文章部分提供详细的内容介绍供 ...
- node的重点学习笔记(1)————node
node的重点学习笔记(1)----node 提到node就必须提一下他的npm了,npm是世界上最大的开放源代码的生态系统.通俗来说这就如同亚马逊丛林,要啥物种有啥物种,一个巨大的生态圈,里面有一堆 ...
- u盘安装操作系统相关
首先使用相关软件制作u盘启动盘,将所需要的系统镜像安装到u盘中 此时u盘中会出现一个EFI区(window可以看到,window7看不到,可以隐藏) 简单来说这个EFI分区中装的是一个低价的操作系统, ...
- 开启sql语句监控
开启sql执行语句监控 set global general_log=on; set global_log_output='table'; 修改mysql配置文件,在[mysqld]中加入 gener ...
- Ubuntu使用中遇到的的一些问题
制作ubuntu启动盘后,U盘只读. ubuntu自带的"启动盘创建器(usb-creator-gtk)"制作启动盘后,U盘只读. 打开ubuntu自带的"磁盘(hard ...
- hadoop-3.1.2启动httpfs
最近有一个需求,要求使用httpfs读取数据,一开始看到httpfs这个词,第一感觉是不是多了个f,是不是https,后来百度一下,其实不然. httpfs其实是使用http协议访问hdfs文件系统: ...
- 【MongoDB详细使用教程】四、python操作MongoDB
目录 1.安装pymongo 2.连接数据库 3.操作数据库 3.1.查 3.2.增 3.3.改 3.4.删 使用第三方库pymongo来实现python对MongoDB的操作 pymongo官方文档 ...
- zepto源码分析·整体架构
代码数量 1.2.0版本代码量为1650行,去掉注释大概1500左右 代码模块 默认版本只包括核心模块,事件模块,ajax模块,form模块和ie模块,其它模块需要自行拓展加入,其中form模块只包含 ...
- Creator 2.2.0 终于等来了这款Shader组件神器!一招搞定Effect特效
先看下视频演示: ShaderHelper2支持Creator 2.2.0 视频录完后才想起,还没在微信小游戏中测试,赶紧试试,下面是在微信开发者工具中的截图. 径向模糊 探照灯 提供了一个Shade ...