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

.NET 8+NSubstitute单元测试:ToDictionaryAsync报IDbAsyncEnumerable未实现错误

解决.NET 8 + NSubstitute单元测试中ToDictionaryAsync抛出IDbAsyncEnumerable异常的问题

问题原因

EF Core提供的异步集合扩展方法(比如ToDictionaryAsync)要求源IQueryable必须实现IDbAsyncEnumerable接口,但普通内存集合转换的IQueryable(比如List<T>.AsQueryable())并没有实现该接口。而ToListAsync可能因为存在其他重载(比如System.Linq.Async中的版本)可以正常执行,导致出现行为差异。

解决方案

创建一套辅助类,将普通IQueryable包装为支持IDbAsyncEnumerable的类型,让EF的异步方法可以正常识别。

1. 实现辅助类

using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

public class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
    public TestDbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { }

    public TestDbAsyncEnumerable(Expression expression) : base(expression) { }

    public IDbAsyncEnumerator<T> GetAsyncEnumerator()
    {
        return new TestDbAsyncEnumerator<T>(AsEnumerable().GetEnumerator());
    }

    IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() => GetAsyncEnumerator();

    IQueryProvider IQueryable.Provider => new TestDbAsyncQueryProvider<T>(Provider);
}

public class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _innerEnumerator;

    public TestDbAsyncEnumerator(IEnumerator<T> innerEnumerator)
    {
        _innerEnumerator = innerEnumerator;
    }

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

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

    public T Current => _innerEnumerator.Current;

    object IDbAsyncEnumerator.Current => Current;
}

public class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
    private readonly IQueryProvider _innerProvider;

    public TestDbAsyncQueryProvider(IQueryProvider innerProvider)
    {
        _innerProvider = innerProvider;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return new TestDbAsyncEnumerable<TEntity>(expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new TestDbAsyncEnumerable<TElement>(expression);
    }

    public object Execute(Expression expression) => _innerProvider.Execute(expression);

    public TResult Execute<TResult>(Expression expression) => _innerProvider.Execute<TResult>(expression);

    public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute(expression));
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }
}

2. 添加扩展方法简化使用

public static class QueryableExtensions
{
    public static IQueryable<T> AsDbAsyncQueryable<T>(this IEnumerable<T> source)
    {
        return new TestDbAsyncEnumerable<T>(source);
    }
}

3. 在Mock仓储时使用

假设你的仓储接口定义如下:

public interface IRepository<T>
{
    IQueryable<T> GetAll();
}

Mock时将测试数据转换为支持异步的IQueryable:

// 准备测试数据
var testData = new List<MyEntity>
{
    new MyEntity { Id = 1, Name = "Test1" },
    new MyEntity { Id = 2, Name = "Test2" }
};

// Mock仓储
var mockRepo = Substitute.For<IRepository<MyEntity>>();
// 使用扩展方法包装成支持IDbAsyncEnumerable的IQueryable
mockRepo.GetAll().Returns(testData.AsDbAsyncQueryable());

这样配置后,调用ToDictionaryAsync等EF异步扩展方法时就不会再抛出异常了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 10:22:40