EF Core 3.1异步方法单元测试IAsyncQueryProvider报错解决方案问询
错误根因
你直接调用IEnumerable<T>.AsQueryable()得到的是内存集合默认的查询提供器,该提供器未实现EF Core要求的IAsyncQueryProvider接口,因此无法支持EF异步扩展方法的执行。
方案1:自定义适配EF Core 3.1的异步查询提供器
直接实现一套兼容内存集合的异步查询包装类,代码如下:
using Microsoft.EntityFrameworkCore.Query; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; public class AsyncQueryableWrapper<T> : IAsyncQueryProvider, IQueryable<T> { private readonly IQueryable<T> _inner; public AsyncQueryableWrapper(IQueryable<T> inner) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); } public Expression Expression => _inner.Expression; public Type ElementType => _inner.ElementType; public IQueryProvider Provider => this; public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression) { var result = _inner.Provider.Execute<IEnumerable<TResult>>(expression); return new AsyncEnumerableWrapper<TResult>(result); } public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) { var expectedResultType = typeof(TResult).GetGenericArguments()[0]; var executionResult = typeof(IQueryProvider) .GetMethod(nameof(IQueryProvider.Execute), 1, new[] { typeof(Expression) }) .MakeGenericMethod(expectedResultType) .Invoke(_inner.Provider, new object[] { expression }); return (TResult)typeof(Task).GetMethod(nameof(Task.FromResult)) .MakeGenericMethod(expectedResultType) .Invoke(null, new[] { executionResult }); } public object Execute(Expression expression) => _inner.Provider.Execute(expression); public TResult Execute<TResult>(Expression expression) => _inner.Provider.Execute<TResult>(expression); public IEnumerator<T> GetEnumerator() => _inner.GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _inner.GetEnumerator(); private class AsyncEnumerableWrapper<TInner> : IAsyncEnumerable<TInner> { private readonly IEnumerable<TInner> _inner; public AsyncEnumerableWrapper(IEnumerable<TInner> inner) { _inner = inner; } public IAsyncEnumerator<TInner> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new AsyncEnumeratorWrapper<TInner>(_inner.GetEnumerator()); } } private class AsyncEnumeratorWrapper<TInner> : IAsyncEnumerator<TInner> { private readonly IEnumerator<TInner> _inner; public AsyncEnumeratorWrapper(IEnumerator<TInner> inner) { _inner = inner; } public TInner Current => _inner.Current; public ValueTask DisposeAsync() { _inner.Dispose(); return ValueTask.CompletedTask; } public ValueTask<bool> MoveNextAsync() { return ValueTask.FromResult(_inner.MoveNext()); } } } // 便捷扩展方法 public static class AsyncQueryableExtensions { public static IQueryable<T> AsAsyncQueryable<T>(this IEnumerable<T> source) { return new AsyncQueryableWrapper<T>(source.AsQueryable()); } }
测试代码改造
把原来测试里的postcodeExclusionSet.AsQueryable()替换为调用上面的AsAsyncQueryable()即可:
var isSupported = await postcodeExclusionSet.AsAsyncQueryable().CheckPostcodeIsSupportedAsync(customerId, postcode);
方案2:使用EF Core InMemory数据库(更推荐)
你要求查询逻辑和转换后的SQL行为完全一致,自定义提供器仅能校验表达式逻辑,无法模拟EF查询转换的实际行为,用EF官方的InMemory提供器测试更符合需求:
- 先安装NuGet包:
Microsoft.EntityFrameworkCore.InMemory - 定义你的DbContext类:
public class MyDbContext : DbContext { public DbSet<PostcodeExclusion> PostcodeExclusions { get; set; } public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } }
- 测试代码改造:
[Theory] [MemberData(nameof(CheckPostcodeExclusion_should_validate_postcode_Inputs))] public async Task CheckPostcodeExclusion_should_validate_postcode(bool expectedPostcodeIsSupported, string postcode, int customerId, IEnumerable<PostcodeExclusion> postcodeExclusionSet) { // 每个测试用独立数据库避免数据干扰 var options = new DbContextOptionsBuilder<MyDbContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var context = new MyDbContext(options); context.PostcodeExclusions.AddRange(postcodeExclusionSet); await context.SaveChangesAsync(); var isSupported = await context.PostcodeExclusions.CheckPostcodeIsSupportedAsync(customerId, postcode); Assert.Equal(expectedPostcodeIsSupported, isSupported); }
该方案完全复用EF Core 3.1官方查询逻辑,和生产环境的行为一致性更高。
内容的提问来源于stack exchange,提问作者Marcin Piwowarski
相关产品推荐
相关产品推荐

