如何解决EF测试时IQueryable未实现IDbAsyncQueryProvider的报错
报错根因
报错核心是EF版本不兼容导致的接口不互通:业务项目依赖的System.Data.Entity(即EF6)中的异步查询扩展方法(如FirstOrDefaultAsync)要求数据源的IQueryable实现EF6专属的System.Data.Entity.Infrastructure.IDbAsyncQueryProvider接口,而测试项目中使用的EF Core内存数据库返回的IQueryable实现的是EF Core自家的异步查询提供器接口,二者完全不兼容,因此调用时会触发该报错。
解决方案(无需更换现有依赖)
推荐在测试项目中实现EF6异步接口的适配包装类,将EF Core返回的IQueryable做一层封装即可兼容EF6的异步方法调用,无侵入性且不需要调整现有生产依赖。
步骤1:实现3个适配类
直接在测试项目中新增以下3个类,不需要额外引入其他依赖:
using System.Collections; using System.Data.Entity.Infrastructure; using System.Linq.Expressions; // 适配EF6的异步枚举器 internal class Ef6AsyncEnumerator<T> : IDbAsyncEnumerator<T> { private readonly IEnumerator<T> _inner; public Ef6AsyncEnumerator(IEnumerator<T> inner) { _inner = inner; } public T Current => _inner.Current; object IDbAsyncEnumerator.Current => Current; public void Dispose() => _inner.Dispose(); public Task<bool> MoveNextAsync(CancellationToken cancellationToken) { return Task.FromResult(_inner.MoveNext()); } } // 适配EF6的异步查询提供器 internal class Ef6AsyncQueryProvider<T> : IDbAsyncQueryProvider { private readonly IQueryProvider _inner; public Ef6AsyncQueryProvider(IQueryProvider inner) { _inner = inner; } public IQueryable CreateQuery(Expression expression) => new Ef6AsyncQueryable<T>(expression); public IQueryable<TElement> CreateQuery<TElement>(Expression expression) => new Ef6AsyncQueryable<TElement>(expression); public object Execute(Expression expression) => _inner.Execute(expression); public TResult Execute<TResult>(Expression expression) => _inner.Execute<TResult>(expression); public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken) => Task.FromResult(Execute(expression)); public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) => Task.FromResult(Execute<TResult>(expression)); } // 适配EF6的异步查询集合 internal class Ef6AsyncQueryable<T> : IQueryable<T>, IDbAsyncEnumerable<T> { public IQueryable Inner { get; } public Type ElementType { get; } public Expression Expression { get; } public IQueryProvider Provider { get; } public Ef6AsyncQueryable(IQueryable inner) { Inner = inner; Provider = new Ef6AsyncQueryProvider<T>(inner.Provider); Expression = inner.Expression; ElementType = inner.ElementType; } public Ef6AsyncQueryable(Expression expression) { Expression = expression; ElementType = typeof(T); } public IEnumerator<T> GetEnumerator() => Inner.Cast<T>().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public IDbAsyncEnumerator<T> GetAsyncEnumerator() => new Ef6AsyncEnumerator<T>(GetEnumerator()); IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() => GetAsyncEnumerator(); }
步骤2:调用时包装EF Core的查询源
在测试代码中,只要将EF Core返回的DbSet用上述适配类包装一层,再传入业务逻辑即可正常运行:
// 示例:对要传入仓储的DbSet做包装 var compatibleCategorySet = new Ef6AsyncQueryable<Category>(_userDBContext.Categories); // 后续调用UpdateCategoryAsync时使用包装后的集合即可,不需要修改原有业务代码
如果是单元测试不需要真实执行查询,也可以直接用Mock框架模拟FirstOrDefaultAsync的返回结果,跳过真实查询逻辑,实现成本更低。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

