简体   繁体   English

ToArrayAsync() 抛出“源 IQueryable 未实现 IAsyncEnumerable”

[英]ToArrayAsync() throws "The source IQueryable doesn't implement IAsyncEnumerable"

I have a MVC project on ASP.NET Core, my problem is connected with IQueryable and asynchronous.我在 ASP.NET 核心上有一个 MVC 项目,我的问题与 IQueryable 和异步有关。 I wrote the following method for search in IQueryable<T> :我在IQueryable<T>中编写了以下搜索方法:

private IQueryable<InternalOrderInfo> WhereSearchTokens(IQueryable<InternalOrderInfo> query, SearchToken[] searchTokens)
{
    if (searchTokens.Length == 0)
    {
        return query;
    }
    var results = new List<InternalOrderInfo>();
    foreach (var searchToken in searchTokens)
    {
        //search logic, intermediate results are being added to `results` using `AddRange()`
    }

    return results.Count != 0 ? results.Distinct().AsQueryable() : query;
}

I call this in method ExecuteAsync() :我在方法ExecuteAsync()中调用它:

public async Task<GetAllInternalOrderInfoResponse> ExecuteAsync(GetAllInternalOrderInfoRequest request)
{
    //rest of the code
    if (searchTokens != null && searchTokens.Any())
    {
        allInternalOrderInfo = WhereSearchTokens(allInternalOrderInfo, searchTokens);
    }
    var orders = await allInternalOrderInfo.Skip(offset).Take(limit).ToArrayAsync();
    //rest of the code
}

When I test this I get an InvalidOperationException on line where I call ToArrayAsync()当我测试这个时,我在调用ToArrayAsync()的地方得到一个 InvalidOperationException

The source IQueryable doesn't implement IAsyncEnumerable.源 IQueryable 未实现 IAsyncEnumerable。 Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.只有实现 IAsyncEnumerable 的源才能用于实体框架异步操作。

I had changed ToArrayAsync() to ToListAsync() but nothing have changed.我已将ToArrayAsync()更改为ToListAsync()但没有任何改变。 I have searched this problem for a while, but resolved questions are connected mostly with DbContext and entity creating.我已经搜索了这个问题一段时间,但解决的问题主要与DbContext和实体创建有关。 EntityFramework is not installed for this project and it's better not to do it because of application architecture.该项目没有安装EntityFramework,由于应用程序架构,最好不要安装。 Hope someone has any ideas what to do in my situation.希望有人对我的情况有什么想法。

If you are not going to change your design - you have several options:如果您不打算更改您的设计 - 您有多种选择:

1) Change AsQueryable to another method which returns IQueryable which also implements IDbAsyncEnumerable . 1) 将AsQueryable更改为另一个返回IQueryable方法,该方法也实现了IDbAsyncEnumerable For example you can extend EnumerableQuery (which is returned by AsQueryable ):例如,您可以扩展EnumerableQuery (由AsQueryable返回):

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

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

    public IDbAsyncEnumerator<T> GetAsyncEnumerator() {
        return new InMemoryDbAsyncEnumerator<T>(((IEnumerable<T>) this).GetEnumerator());
    }

    IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() {
        return GetAsyncEnumerator();
    }

    private class InMemoryDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> {
        private readonly IEnumerator<T> _enumerator;

        public InMemoryDbAsyncEnumerator(IEnumerator<T> enumerator) {
            _enumerator = enumerator;
        }

        public void Dispose() {
        }

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

        public T Current => _enumerator.Current;

        object IDbAsyncEnumerator.Current => Current;
    }
}

Then you change那你改变

results.Distinct().AsQueryable()

to

new AsyncEnumerableQuery<InternalOrderInfo>(results.Distinct())

And later, ToArrayAsync will not throw exception any more (obviously you can create your own extension method like AsQueryable ).稍后, ToArrayAsync将不再抛出异常(显然您可以创建自己的扩展方法,如AsQueryable )。

2) Change ToArrayAsync part: 2) 更改ToArrayAsync部分:

public static class EfExtensions {
    public static Task<TSource[]> ToArrayAsyncSafe<TSource>(this IQueryable<TSource> source) {
        if (source == null)
            throw new ArgumentNullException(nameof(source));
        if (!(source is IDbAsyncEnumerable<TSource>))
            return Task.FromResult(source.ToArray());
        return source.ToArrayAsync();
    }
}

And use ToArrayAsyncSafe instead of ToArrayAsync , which will fallback to synchronous enumeration in case IQueryable is not IDbAsyncEnumerable .并使用ToArrayAsyncSafe代替ToArrayAsync ,这将回退到同步枚举的情况下IQueryableIDbAsyncEnumerable In your case this only happens when query is really in-memory list and not query, so async execution does not make sense anyway.在您的情况下,这只发生在查询确实是内存列表而不是查询时,因此异步执行无论如何都没有意义。

For EF Core:对于 EF 核心:

public static class QueryableExtensions
{
    public static IQueryable<T> AsAsyncQueryable<T>(this IEnumerable<T> input)
    {
        return new NotInDbSet<T>( input );
    }

}

public class NotInDbSet< T > : IQueryable<T>, IAsyncEnumerable< T >, IEnumerable< T >, IEnumerable
{
    private readonly List< T > _innerCollection;
    public NotInDbSet( IEnumerable< T > innerCollection )
    {
        _innerCollection = innerCollection.ToList();
    }


    public IAsyncEnumerator< T > GetAsyncEnumerator( CancellationToken cancellationToken = new CancellationToken() )
    {
        return new AsyncEnumerator( GetEnumerator() );
    }

    public IEnumerator< T > GetEnumerator()
    {
        return _innerCollection.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public class AsyncEnumerator : IAsyncEnumerator< T >
    {
        private readonly IEnumerator< T > _enumerator;
        public AsyncEnumerator( IEnumerator< T > enumerator )
        {
            _enumerator = enumerator;
        }

        public ValueTask DisposeAsync()
        {
            return new ValueTask();
        }

        public ValueTask< bool > MoveNextAsync()
        {
            return new ValueTask< bool >( _enumerator.MoveNext() );
        }

        public T Current => _enumerator.Current;
    }

    public Type ElementType => typeof( T );
    public Expression Expression => Expression.Empty();
    public IQueryProvider Provider => new EnumerableQuery<T>( Expression );
}

I found I had to do a bit more work to get things to work nicely:我发现我必须做更多的工作才能让事情顺利进行:

namespace TestDoubles
{
    using Microsoft.EntityFrameworkCore.Query.Internal;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Threading;
    using System.Threading.Tasks;

    public static class AsyncQueryable
    {
        /// <summary>
        /// Returns the input typed as IQueryable that can be queried asynchronously
        /// </summary>
        /// <typeparam name="TEntity">The item type</typeparam>
        /// <param name="source">The input</param>
        public static IQueryable<TEntity> AsAsyncQueryable<TEntity>(this IEnumerable<TEntity> source)
            => new AsyncQueryable<TEntity>(source ?? throw new ArgumentNullException(nameof(source)));
    }

    public class AsyncQueryable<TEntity> : EnumerableQuery<TEntity>, IAsyncEnumerable<TEntity>, IQueryable<TEntity>
    {
        public AsyncQueryable(IEnumerable<TEntity> enumerable) : base(enumerable) { }
        public AsyncQueryable(Expression expression) : base(expression) { }
        public IAsyncEnumerator<TEntity> GetEnumerator() => new AsyncEnumerator(this.AsEnumerable().GetEnumerator());
        public IAsyncEnumerator<TEntity> GetAsyncEnumerator(CancellationToken cancellationToken = default) => new AsyncEnumerator(this.AsEnumerable().GetEnumerator());
        IQueryProvider IQueryable.Provider => new AsyncQueryProvider(this);

        class AsyncEnumerator : IAsyncEnumerator<TEntity>
        {
            private readonly IEnumerator<TEntity> inner;
            public AsyncEnumerator(IEnumerator<TEntity> inner) => this.inner = inner;
            public void Dispose() => inner.Dispose();
            public TEntity Current => inner.Current;
            public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(inner.MoveNext());
#pragma warning disable CS1998 // Nothing to await
            public async ValueTask DisposeAsync() => inner.Dispose();
#pragma warning restore CS1998
        }

        class AsyncQueryProvider : IAsyncQueryProvider
        {
            private readonly IQueryProvider inner;
            internal AsyncQueryProvider(IQueryProvider inner) => this.inner = inner;
            public IQueryable CreateQuery(Expression expression) => new AsyncQueryable<TEntity>(expression);
            public IQueryable<TElement> CreateQuery<TElement>(Expression expression) => new AsyncQueryable<TElement>(expression);
            public object Execute(Expression expression) => inner.Execute(expression);
            public TResult Execute<TResult>(Expression expression) => inner.Execute<TResult>(expression);
            public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression) => new AsyncQueryable<TResult>(expression);
            TResult IAsyncQueryProvider.ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) => Execute<TResult>(expression);
        }
    }
}

This enables me to write tests like this:这使我能够编写这样的测试:

    [TestCase("", 3, 5)]
    [TestCase("100", 2, 4)]
    public async Task GetOrderStatusCounts_ReturnsCorrectNumberOfRecords(string query, int expectedCount, int expectedStatusProductionCount)
    {
        // omitted CreateOrder helper function

        const int productionStatus = 6;
        const int firstOtherStatus = 5;
        const int otherOtherStatus = 7;

        var items = new[]
        {
            CreateOrder(1, "100000", firstOtherStatus, 1),
            CreateOrder(2, "100000", firstOtherStatus, 4),
            CreateOrder(3, "100000", productionStatus, 4),
            CreateOrder(4, "100001", productionStatus, 4),
            CreateOrder(5, "100100", productionStatus, 4),
            CreateOrder(6, "200000", otherOtherStatus, 4),
            CreateOrder(7, "200001", productionStatus, 4),
            CreateOrder(8, "200100", productionStatus, 4)
        }.AsAsyncQueryable(); // this is where the magic happens

        var mocker = new AutoMocker();

        // IRepository implementation is also generic and calls DBCntext
        // for easier testing
        mocker.GetMock<IRepository<Order>>() 
            .Setup(m => m.BaseQuery()
            .Returns(items); 
            // the base query is extended in the system under test.
            // that's the behavior I'm testing here

        var sut = mocker.CreateInstance<OrderService>();

        var counts = await sut.GetOrderStatusCountsAsync(4, query);

        counts.Should().HaveCount(expectedCount);
        counts[OrderStatus.Production].Should().Be(expectedStatusProductionCount);
    }

The AsQueryable() will not transform the result list into an Entity Framework IQueryable . AsQueryable()不会将result列表转换为实体框架IQueryable And as the error states, the IQueryable that are used with ToArrayAsync() should implement IAsyncEnumerable , which is not what AsQueryable will return.正如错误所述,与ToArrayAsync()一起使用的IQueryable应该实现IAsyncEnumerable ,这不是AsQueryable将返回的内容。

You can read more about the uses of AsQueryable on enumerables here .您可以在此处AsQueryable有关可枚举的AsQueryable使用的更多信息。

As noted by @Titian Cernicova-Dragomir the exception means that List<InternalOrderInfo> doesn't implement IAsyncEnumerable正如@Titian Cernicova-Dragomir 所指出的,异常意味着List<InternalOrderInfo>没有实现IAsyncEnumerable

But here is a logical/design error.但这是一个逻辑/设计错误。 If your method works with IQueryable and returns IQueryable it should work with it as with IQueryable and not as with IEnumarable that assumes that collection is in a memory of app.如果您的方法适用于IQueryable并返回IQueryable则它应该像IQueryable一样使用它,而不是像IEnumarable那样假定集合在应用程序的内存中。 You really need to read more about the difference between IQueryable and IEnumarable and what you should return from the method.你真的需要阅读更多关于IQueryableIEnumarable之间的区别以及你应该从方法返回什么的信息。 A good point to start is to read answers here and here一个好的开始是在这里这里阅读答案

So, since you already fetched results from db in WhereSearchTokens method or even before, there is no reason to do asynchronous request to db which is would be done by ToArrayAsync and return IQueryable .因此,由于您已经在WhereSearchTokens方法中甚至之前从 db 获取了结果,因此没有理由对 db 进行异步请求,这将由ToArrayAsync完成并返回IQueryable

You have two options here:您在这里有两个选择:

1) If your collection of InternalOrderInfo is fetched from db into memory before WhereSearchTokens make your all actions in synchronous mode ie call ToArray instead of ToArrayAsync , and return IEnumerable instead of Taks<IQueryable> from both WhereSearchTokens and ExecuteAsync . 1) 如果您的InternalOrderInfo集合在WhereSearchTokens使您的所有操作处于同步模式之前从数据库提取到内存中,即调用ToArray而不是ToArrayAsync ,并从WhereSearchTokensExecuteAsync返回IEnumerable而不是Taks<IQueryable>

2) If your collection of InternalOrderInfo is fetched inside WhereSearchTokens and you want to do the async request to db you need to call async EF API only somewhere in //search logic, intermediate results are being added to results using AddRange() and again return Taks<IEnumerable> istead of Taks<IQueryable> from WhereSearchTokens 2) 如果您的InternalOrderInfo集合是在WhereSearchTokens获取的,并且您想对 db 执行异步请求,则您只需要在//search logic, intermediate results are being added to results using AddRange()某处调用异步 EF API //search logic, intermediate results are being added to results using AddRange()并再次返回Taks<IEnumerable>代替了来自WhereSearchTokensTaks<IQueryable>

For EFCore对于 EFCore

Bit late to the party but to others looking to resolve this type of problem, one of possible solution is to change code to use Task.FromResult() method in this way:参加聚会有点晚,但对于希望解决此类问题的其他人来说,可能的解决方案之一是更改代码以通过这种方式使用Task.FromResult()方法:

var result= await allInternalOrderInfo.Skip(offset).Take(limit);
var orders = await Task.FromResult(result.ToArray());

I wrote an ICollection extension AsAsyncQueryable that I use in my tests我写了一个 ICollection 扩展AsAsyncQueryable用于我的测试

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace Whatevaaaaaaaa
{
    public static class ICollectionExtensions
    {
        public static IQueryable<T> AsAsyncQueryable<T>(this ICollection<T> source) =>
            new AsyncQueryable<T>(source.AsQueryable());
    }

    internal class AsyncQueryable<T> : IAsyncEnumerable<T>, IQueryable<T>
    {
        private IQueryable<T> Source;

        public AsyncQueryable(IQueryable<T> source)
        {
            Source = source;
        }

        public Type ElementType => typeof(T);

        public Expression Expression => Source.Expression;

        public IQueryProvider Provider => new AsyncQueryProvider<T>(Source.Provider);

        public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
        {
            return new AsyncEnumeratorWrapper<T>(Source.GetEnumerator());
        }

        public IEnumerator<T> GetEnumerator() => Source.GetEnumerator();
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    internal class AsyncQueryProvider<T> : IQueryProvider
    {
        private readonly IQueryProvider Source;

        public AsyncQueryProvider(IQueryProvider source)
        {
            Source = source;
        }

        public IQueryable CreateQuery(Expression expression) =>
            Source.CreateQuery(expression);

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression) =>
            new AsyncQueryable<TElement>(Source.CreateQuery<TElement>(expression));

        public object Execute(Expression expression) => Execute<T>(expression);

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



    internal class AsyncEnumeratorWrapper<T> : IAsyncEnumerator<T>
    {
        private readonly IEnumerator<T> Source;

        public AsyncEnumeratorWrapper(IEnumerator<T> source)
        {
            Source = source;
        }

        public T Current => Source.Current;

        public ValueTask DisposeAsync()
        {
            return new ValueTask(Task.CompletedTask);
        }

        public ValueTask<bool> MoveNextAsync()
        {
            return new ValueTask<bool>(Source.MoveNext());
        }
    }
}

It's better to implement collection with IAsyncEnumerable<T> and IQueryable<T> rather than create your own ToListAsync extensions.最好使用IAsyncEnumerable<T>IQueryable<T>实现集合,而不是创建自己的ToListAsync扩展。

You can't apply your extensions in libraries.您不能在库中应用您的扩展。

For EF Core 5 and above check this implementation and tests .对于 EF Core 5 及更高版本,请检查此实现测试

Short version:精简版:

public sealed class FixedQuery<T> : IAsyncEnumerable<T>, IQueryable<T>
{
    public static readonly IQueryable<T> Empty = Create(ArraySegment<T>.Empty);

    public static IQueryable<T> Create(params T[] items)
    {
        return Create((IEnumerable<T>)items);
    }

    public static IQueryable<T> Create(IEnumerable<T> items)
    {
        return new FixedQuery<T>(items ?? ArraySegment<T>.Empty).AsQueryable();
    }

    private readonly IQueryable<T> _items;

    private FixedQuery(IEnumerable<T> items)
    {
        _items = (items ?? throw new ArgumentNullException(nameof(items))).AsQueryable();
    }

    #pragma warning disable CS1998
    public async IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
    #pragma warning restore CS1998
    {
        foreach (var item in _items)
        {
            yield return item;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public Type ElementType => _items.ElementType;
    public Expression Expression => _items.Expression;
    public IQueryProvider Provider => _items.Provider;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 获取 mongodb 文档但 Iqueriable 上的 ToListAsync 引发异常源 IQueryable 未实现 IAsyncEnumerable - Getting mongodb documents but ToListAsync on Iqueriable throws exception The source IQueryable doesn't implement IAsyncEnumerable .Net Core 单元测试错误 - 源 IQueryable 没有实现 IAsyncEnumerable&lt;…&gt; - .Net Core Unit Test Error - The source IQueryable doesn't implement IAsyncEnumerable<…> MSTest v2 和源 IQueryable 未使用 Entity Framework Core 实现 IAsyncEnumerable - MSTest v2 and The source IQueryable doesn't implement IAsyncEnumerable with Entity Framework Core 源 IQueryable 的提供者没有实现 IAsyncQueryProvider - The provider for the source IQueryable doesn't implement IAsyncQueryProvider 源 IQueryable 的提供程序未实现 IDbAsyncQueryProvider - The provider for the source IQueryable doesn't implement IDbAsyncQueryProvider 尝试模拟时,源 IQueryable 未实现 IDbAsyncEnumerable - The source IQueryable doesn't implement IDbAsyncEnumerable when trying to mock .Net 5 Web Api - PagedList - 源“IQueryable”的提供者没有实现“IAsyncQueryProvider” - .Net 5 Web Api - PagedList - The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider' 在实体框架 6 SelectMany to Join 选项中查询抛出错误源 IQueryable 未实现 IDbAsyncEnumerable<MyModel> - In entity framework 6 SelectMany to Join option for query throwing error The source IQueryable doesn't implement IDbAsyncEnumerable<MyModel> 实现 IAsyncEnumerable 的空 IQueryable - Empty IQueryable that implements IAsyncEnumerable 列表<t>实现 IQueryable<t></t></t> - List<T> to implement IQueryable<T>
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM