Mock TenantDbContext中IQueryable对象时遇EF异步查询提供器错误
解决EF Core异步查询Mock的IAsyncQueryProvider错误
方法一:使用EF Core InMemory数据库(推荐)
直接用EF Core的内存数据库模拟真实DbContext行为,无需手动Mock查询提供器,简单且贴近真实场景:
- 安装依赖包:确保项目已安装
Microsoft.EntityFrameworkCore.InMemory - 编写测试代码:
// 配置内存数据库选项 var options = new DbContextOptionsBuilder<TenantDbContext>() .UseInMemoryDatabase(databaseName: "Test_Stok_Db") .Options; // 初始化测试数据 using var context = new TenantDbContext(options); context.Stok.Add(new Stok { Id = 1, /* 填充其他测试属性 */ }); context.SaveChanges(); // 实例化待测试的仓库 var repository = new StokRepository(context); // 执行异步查询测试 var result = await repository.GetByIdAsync(1); // 断言验证结果 Assert.NotNull(result); Assert.Equal(1, result.Id);
这种方式完全复刻EF Core的真实查询逻辑,天然支持异步操作,不会触发IAsyncQueryProvider相关错误。
方法二:自定义AsyncQueryProvider配合Moq
如果必须用Moq来Mock DbContext,需要自定义实现IAsyncQueryProvider的类,替换默认的同步查询提供器:
- 实现异步查询相关基础类:
public class AsyncQueryProvider<TEntity> : IAsyncQueryProvider { private readonly IQueryProvider _innerProvider; public AsyncQueryProvider(IQueryProvider innerProvider) { _innerProvider = innerProvider; } public IQueryable CreateQuery(Expression expression) { return new AsyncEnumerable<TEntity>(expression); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return new AsyncEnumerable<TElement>(expression); } public object Execute(Expression expression) { return _innerProvider.Execute(expression); } public TResult Execute<TResult>(Expression expression) { return _innerProvider.Execute<TResult>(expression); } public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression) { return new AsyncEnumerable<TResult>(expression); } public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) { return Task.FromResult(Execute<TResult>(expression)); } } public class AsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T> { public AsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { } public AsyncEnumerable(Expression expression) : base(expression) { } public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new AsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); } IQueryProvider IQueryable.Provider => new AsyncQueryProvider<T>(this.Provider); } public class AsyncEnumerator<T> : IAsyncEnumerator<T> { private readonly IEnumerator<T> _innerEnumerator; public AsyncEnumerator(IEnumerator<T> innerEnumerator) { _innerEnumerator = innerEnumerator; } public ValueTask DisposeAsync() { _innerEnumerator.Dispose(); return ValueTask.CompletedTask; } public ValueTask<bool> MoveNextAsync() { return ValueTask.FromResult(_innerEnumerator.MoveNext()); } public T Current => _innerEnumerator.Current; }
- 在Moq中替换查询提供器:
// 准备测试数据集合 var testStoks = new List<Stok> { new Stok { Id = 1 } }.AsQueryable(); // Mock DbSet并替换为异步查询实现 var mockStokSet = new Mock<DbSet<Stok>>(); mockStokSet.As<IAsyncEnumerable<Stok>>() .Setup(m => m.GetAsyncEnumerator(It.IsAny<CancellationToken>())) .Returns(new AsyncEnumerator<Stok>(testStoks.GetEnumerator())); mockStokSet.As<IQueryable<Stok>>() .Setup(m => m.Provider) .Returns(new AsyncQueryProvider<Stok>(testStoks.Provider)); mockStokSet.As<IQueryable<Stok>>().Setup(m => m.Expression).Returns(testStoks.Expression); mockStokSet.As<IQueryable<Stok>>().Setup(m => m.ElementType).Returns(testStoks.ElementType); mockStokSet.As<IQueryable<Stok>>().Setup(m => m.GetEnumerator()).Returns(testStoks.GetEnumerator()); // Mock DbContext并关联Mock的DbSet var mockContext = new Mock<TenantDbContext>(); mockContext.Setup(c => c.Stok).Returns(mockStokSet.Object); // 执行测试 var repository = new StokRepository(mockContext.Object); var result = await repository.GetByIdAsync(1); // 断言验证 Assert.NotNull(result); Assert.Equal(1, result.Id);
核心原因说明
普通List.AsQueryable()返回的查询提供器是同步实现,不支持EF Core的异步扩展方法(如FirstOrDefaultAsync、ToListAsync),这些方法要求查询提供器必须实现IAsyncQueryProvider接口,这就是报错的根源。
两种方案中,InMemory数据库是优先选择,无需编写冗余的自定义代码,还能模拟EF Core的查询跟踪、关系映射等真实特性,测试可信度更高。
内容的提问来源于stack exchange,提问作者Hakan Kaban
相关产品推荐
相关产品推荐

