.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
相关产品推荐
相关产品推荐

