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

EF6单元测试Mock DbSet异步方法 投影返回自定义类型报IDbAsyncEnumerable错误

问题根因

你当前仅为原始DbSet<Item>实现了EF要求的IDbAsyncEnumerable异步接口和配套查询提供器,但经过GroupBy、Select这类投影操作后,返回的新IQueryable<OrderSummary>使用的是默认LINQ to Objects提供器,没有实现EF异步扩展方法要求的接口,因此调用ToDictionaryAsync、ToListAsync这类方法时就会抛出对应异常。

解决方案

方案1:手动实现通用异步查询辅助类

你需要将原来绑定单一实体类型的异步辅助类改为通用实现,支持任意投影类型的异步查询:

步骤1:添加3个通用异步辅助类

using System.Data.Entity.Infrastructure;
using System.Linq.Expressions;
using System.Collections;

// 通用异步枚举器
internal class DbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public DbAsyncEnumerator(IEnumerator<T> inner)
    {
        _inner = inner ?? throw new ArgumentNullException(nameof(inner));
    }

    public void Dispose() => _inner.Dispose();

    public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(_inner.MoveNext());
    }

    public T Current => _inner.Current;
    object IDbAsyncEnumerator.Current => Current;
}

// 通用异步查询提供器
internal class DbAsyncQueryProvider<T> : IDbAsyncQueryProvider
{
    private readonly IQueryProvider _inner;

    public DbAsyncQueryProvider(IQueryProvider inner)
    {
        _inner = inner ?? throw new ArgumentNullException(nameof(inner));
    }

    public IQueryable CreateQuery(Expression expression)
    {
        Type elementType = expression.Type.GetGenericArguments().First();
        Type asyncEnumerableType = typeof(DbAsyncEnumerable<>).MakeGenericType(elementType);
        return (IQueryable)Activator.CreateInstance(asyncEnumerableType, expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new DbAsyncEnumerable<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));
}

// 通用异步可枚举查询类
internal class DbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
    public DbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { }
    public DbAsyncEnumerable(Expression expression) : base(expression) { }

    public IDbAsyncEnumerator<T> GetAsyncEnumerator() => new DbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
    IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() => GetAsyncEnumerator();
    IQueryProvider IQueryable.Provider => new DbAsyncQueryProvider<T>(this);
}

步骤2:修改你的WithItems方法

首先修正你代码里的变量名错误:原代码里的stockTakes应该是传入的items参数,修改后代码如下:

public Mock<DbSet<Item>> WithItems(List<Item> items)
{
    var data = new DbAsyncEnumerable<Item>(items);

    MockDbSetItem.As<IDbAsyncEnumerable<Item>>()
        .Setup(m => m.GetAsyncEnumerator())
        .Returns(data.GetAsyncEnumerator());

    MockDbSetItem.As<IQueryable<Item>>()
        .Setup(m => m.Provider)
        .Returns(data.Provider);
    
    MockDbSetItem.As<IQueryable<Item>>().Setup(m => m.Expression).Returns(data.Expression);
    MockDbSetItem.As<IQueryable<Item>>().Setup(m => m.ElementType).Returns(data.ElementType);
    MockDbSetItem.As<IQueryable<Item>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

    return MockDbSetItem;
}

修改完成后,无论你查询投影成匿名类型还是自定义的OrderSummary类型,都会自动生成支持EF异步接口的IQueryable对象,不会再抛出该异常。

方案2:使用现成Nuget包简化实现

如果不想手动维护辅助类,可以直接安装MockQueryable.EntityFramework6包,Mock DbSet的代码可以简化为:

public Mock<DbSet<Item>> WithItems(List<Item> items)
{
    MockDbSetItem = items.AsQueryable().BuildMockDbSet();
    return MockDbSetItem;
}

库已经内置了所有异步接口的通用实现,无需额外配置即可支持任意投影类型的异步查询。

额外优化提示

  • 你的业务代码方法签名有误:Public Taks<Dictionary<int, OrderSummary>应修正为public async Task<Dictionary<int, OrderSummary>>
  • 单元测试建议使用await替代.Result调用异步方法,避免出现线程死锁,可将测试方法返回值改为async Task

内容的提问来源于stack exchange,提问作者khinhvuphiduong

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 23:18:00