You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mock TenantDbContext中IQueryable对象时遇EF异步查询提供器错误

解决EF Core异步查询Mock的IAsyncQueryProvider错误

方法一:使用EF Core InMemory数据库(推荐)

直接用EF Core的内存数据库模拟真实DbContext行为,无需手动Mock查询提供器,简单且贴近真实场景:

  1. 安装依赖包:确保项目已安装Microsoft.EntityFrameworkCore.InMemory
  2. 编写测试代码:
// 配置内存数据库选项
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的类,替换默认的同步查询提供器:

  1. 实现异步查询相关基础类:
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;
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 07:53:26