You need to enable JavaScript to run this app. EF Core 3.1异步方法单元测试IAsyncQueryProvider报错解决方案问询
优惠活动
大模型
产品
解决方案
定价
更多

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提供器测试更符合需求:

  1. 先安装NuGet包:Microsoft.EntityFrameworkCore.InMemory
  2. 定义你的DbContext类:
public class MyDbContext : DbContext
{
    public DbSet<PostcodeExclusion> PostcodeExclusions { get; set; }

    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
}
  1. 测试代码改造:
[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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 16:45:03