温馨提示:本文翻译自stackoverflow.com,查看原文请点击:c# - 'AsyncEnumerableReader' reached the configured maximum size, but not using AsyncEnumerable
.net-core c# entity-framework-core

c# - 'AsyncEnumerableReader'已达到配置的最大大小,但未使用AsyncEnumerable

发布于 2020-04-09 10:00:41

我正在使用EF Core 3.1.1(dotnet core 3.1.1)。我想退回大量的Car实体。不幸的是,我收到以下错误消息:

'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type 'Microsoft.EntityFrameworkCore.Internal.InternalDbSet`...

我知道关于相同错误还有另一个回答的问题。但是我没有做明确的异步操作。

[HttpGet]
[ProducesResponseType(200, Type = typeof(Car[]))]
public IActionResult Index()
{
   return Ok(_carsDataModelContext.Cars.AsEnumerable());
}

_carDataModelContext.Car只是一个简单的实体,将一对一映射到数据库中的表。 public virtual DbSet<Car> Cars { get; set; }

我最初返回Ok(_carsDataModelContext.Cars.AsQueryable())是因为我们需要支持OData。但是要确保不是OData搞砸了,我想返回AsEnumerable,然后从方法中删除“ [EnableQuery]”属性。但这仍然以相同的错误结尾。

解决此问题的唯一方法是,如果我返回 Ok(_carsDataModelContext.Cars.ToList())

查看更多

提问者
Saab
被浏览
109
Ivan Stoev 2020-02-11 05:28

所有EF核心IQueryable<T>的实现(DbSet<T>EntityQueryable<T>)也实现了标准IAsyncEnumerable<T>接口(从.NET核心3中使用时),因此AsEnumerable()AsQueryable()AsAsyncEnumerable()简单地将返回相同实例浇铸到相应的接口。

您可以使用以下代码段轻松地进行验证:

var queryable = _carsDataModelContext.Cars.AsQueryable();
var enumerable = queryable.AsEnumerable();
var asyncEnumerable = queryable.AsAsyncEnumerable();
Debug.Assert(queryable == enumerable && queryable == asyncEnumerable);

因此,即使您没有显式返回IAsyncEnumerable<T>,底层对象也可以实现它并且可以查询它。知道Asp.Net Core自然是异步框架,我们可以放心地假设它检查对象是否实现了新标准IAsyncEnumerable<T>,并在幕后而不是在幕后使用了它IEnumerable<T>

当然,在使用时ToList(),返回的List<T>类不会实现IAsyncEnumerable<T>,因此唯一的选择是使用IEnumerable<T>

这应该解释3.1行为。请注意,在3.0之前,没有标准IAsyncEnumerable<T>接口。EF Core正在实现并返回其自己的异步接口,但是.Net Core基础结构并未意识到这一点,因此无法代表您使用它。


不使用ToList()/ ToArray()和类似方法来强制执行先前行为的唯一方法是隐藏基础源(因此IAsyncEnumerable<T>)。

因为IEnumerable<T>这很容易。您需要做的就是创建使用C#迭代器的自定义扩展方法,例如:

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

然后使用

return Ok(_carsDataModelContext.Cars.ToEnumerable());

如果您想返回IQueryable<T>,事情会变得更加艰难。创建自定义IQueryable<T>包装器还不够,您必须创建自定义IQueryProvider包装器,以确保在返回的包装器中进行排版IQueryable<T>将继续返回包装器,直到请求最终包装IEnumerator<T>(或IEnumerator),并且使用上述方法隐藏了返回的底层异步枚举。

这是上面的简化实现:

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

    class Queryable<T> : IQueryable<T>
    {
        internal Queryable(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(Queryable<>).MakeGenericType(query.ElementType),
                this, query.Expression);
        }
        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
            => new Queryable<TElement>(this, expression);
        public object Execute(Expression expression) => source.Execute(expression);
        public TResult Execute<TResult>(Expression expression) => source.Execute<TResult>(expression);
    }
}

查询供应商实现是不完全正确的,因为它假定只有自定义Queryable<T>将调用Execute的方法创建IEnumerable<T>的,只有像直接方法外部调用将被使用CountFirstOrDefaultMax等,但它应该为这种情况下工作。

此实现的另一个缺点是,所有EF Core特定Queryable扩展都无法使用,如果OData $expand依赖于Include/之类的方法,则这可能是一个问题/问题ThenInclude但是要解决此问题,需要更复杂的实施,并深入EF Core内部。

话虽这么说,用法当然是:

return Ok(_carsDataModelContext.Cars.ToQueryable());