10

我正在使用返回 IAsyncEnumerable<> 的 async/await 方法从 SQL Server 数据库中检索行并通过 Web API .Net Core 3.0 接口提供它们。它工作正常,直到我超过 8192 行。超出该点的行将失败。我尝试了 3 个不同的表,每个表的行大小都非常不同。每次在产生 8192 行后发生故障。这是我看到的错误:

An unhandled exception occurred while processing the request.
InvalidOperationException: 'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable<>' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]' into a list rather than increasing the limit.
Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase<T>(IAsyncEnumerable<object> value)

Stack Query Cookies Headers Routing
InvalidOperationException: 'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable<>' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]' into a list rather than increasing the limit.
Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase<T>(IAsyncEnumerable<object> value)
Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase<T>(IAsyncEnumerable<object> value)
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, IAsyncEnumerable<object> asyncEnumerable)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Show raw exception details
System.InvalidOperationException: 'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable<>' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]' into a list rather than increasing the limit.
   at Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase[T](IAsyncEnumerable`1 value)
   at Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase[T](IAsyncEnumerable`1 value)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, IAsyncEnumerable`1 asyncEnumerable)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
reached the configured maximum size of the buffer when enumerating a value of type Microsoft.EntityFrameworkCore DbSet  This limit is in place to prevent infinite streams from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting Microsoft.EntityFrameworkCore DbSet  into a list rather than increasing the limit.

Microsoft.EntityFrameworkCore.DbSet AsyncEnumerable
4

4 回答 4

17

发生了什么

限制来自MvcOptions.MaxIAsyncEnumerableBufferLimit 属性

获取或设置该 ObjectResultExecutor 将缓冲的 IAsyncEnumerable 的最多条目数。

当 Value 是 IAsyncEnumerable 的实例时,ObjectResultExecutor 将在调用选定的格式化程序之前急切地读取枚举并添加到同步集合中。此属性确定允许执行程序缓冲的最大条目数。

(...)

默认为 8192。

使固定

在我看来,从最好到最差。

A.(首选)不要返回这么多记录

我建议不要从 Web api 返回这么多记录。

分页是处理大量记录的一种方法。您可以查看教程:添加排序、过滤和分页 - 带有 EF Core 的 ASP.NET MVC

B. 调整限额

MvcOptions.MaxIAsyncEnumerableBufferLimit可以设置为另一个值。

C、不要使用IAsyncEnumerable

您可以返回IEnumerable- 它将能够处理更多的记录。

于 2019-11-22T03:23:32.670 回答
1

IAsyncEnumerable在 ASP.NET Core 5 中,确实已通过在内存中缓冲序列并一次性格式化缓冲集合来处理该类型的实例。这解释了为什么您会看到该异常。

但是,在 ASP.NET Core 6.0 中,这将不再发生!

在 ASP.NET Core 6 中,使用 System.Text.Json 进行格式化时,MVC 不再缓冲 IAsyncEnumerable 实例。相反,MVC 依赖于 System.Text.Json 为这些类型添加的支持([参考][1])

有关如何使用 IAsyncEnumerable 的更多详细信息,请参阅关于 IAsyncEnumerable 如何与 ASP.NET Web API一起使用的说明。

于 2021-08-23T07:14:53.923 回答
0

异常消息和描述完美地描述了这种情况。您正在将 (n 异常大) 数量的数据读取到缓冲区中,并且框架警告您,您可能正在做一些您不期望的事情,或者查询了太多信息。

由于您使用的是 WEBAPI,因此您会收到警告,因为 WEBAPI 和网站的目标通常是响应时间较短,并且必须收集 8K+ 记录的数据通常无法实现这一目标。话虽如此,我们不知道您的应用程序的实际用例,您可能并不关心,但无论如何,该框架正在尝试帮助您做“正确的事情”。

无论您的经验水平如何,不阅读/解释错误消息都是一个容易掉入的陷阱。相反,想想它们的意思,如果你不理解它们,请重新阅读和研究,直到你理解为止。自从上师冥想以来,我们已经走了很长一段路,所以不要认为这些有用的信息是理所当然的。

TL;DR 您一次读取的数据过多。找到一种方法将数据分成更小的部分以返回给客户端。较小的页面大小是一个很好的起点。

于 2019-11-22T03:33:38.843 回答
0

就我而言,我在将 odata-endpoints 从 .net core 2.2 迁移到 .net core 3.1 的情况下遇到了这个问题。作为解决方案,我遇到了提供解决方案的文章。长话短说,我们应该创建自己的扩展方法 ToQueryable(),它没有实现 IAsyncEnumerable 接口,并使用它来代替现有的 AsQueryable()。

它就像一个魅力,无需额外过滤查询字符串。但是当我们尝试在过滤查询中发送 $top=3 时,我们得到了错误:'InvalidCastException: Unable to cast object of type Queryableto type System.Linq.IOrderedQueryable'。解决方案是更新上面引用的文章中提供的代码,将 IQueryable 替换为 IOrderedQueryable。结果我得到了:

    public static partial class CustomOrderedQueryable
    {
        public static IEnumerable<T> ToEnumerable<T>(this IEnumerable<T> source)
        {
            foreach (var item in source)
                yield return item;
        }
    }

    public static partial class CustomOrderedQueryable
    {
        public static IOrderedQueryable<T> ToQueryable<T>(this IQueryable<T> source)
            => new OrderedQueryable<T>(new QueryProvider(source.Provider), source.Expression);

        class OrderedQueryable<T> : IOrderedQueryable<T>
        {
            internal OrderedQueryable(IQueryProvider provider, Expression expression)
            {
                Provider = provider;
                Expression = expression;
            }
            public Type ElementType => typeof(T);
            public Expression Expression { get; }
            public IQueryProvider Provider { get; }
            public IEnumerator<T> GetEnumerator() => Provider.Execute<IEnumerable<T>>(Expression)
                .ToEnumerable().GetEnumerator();
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }

        class QueryProvider : IQueryProvider
        {
            private readonly IQueryProvider source;
            internal QueryProvider(IQueryProvider source) => this.source = source;
            public IQueryable CreateQuery(Expression expression)
            {
                var query = source.CreateQuery(expression);
                return (IQueryable)Activator.CreateInstance(
                    typeof(OrderedQueryable<>).MakeGenericType(query.ElementType),
                    this, query.Expression);
            }
            public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
                => new OrderedQueryable<TElement>(this, expression);
            public object Execute(Expression expression) => source.Execute(expression);
            public TResult Execute<TResult>(Expression expression) => source.Execute<TResult>(expression);
        }
    }
于 2021-04-27T10:31:01.710 回答