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

