我正在使用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())
所有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>
的,只有像直接方法外部调用将被使用Count
,FirstOrDefault
,Max
等,但它应该为这种情况下工作。
此实现的另一个缺点是,所有EF Core特定Queryable
扩展都无法使用,如果OData $expand
依赖于Include
/之类的方法,则这可能是一个问题/问题ThenInclude
。但是要解决此问题,需要更复杂的实施,并深入EF Core内部。
话虽这么说,用法当然是:
return Ok(_carsDataModelContext.Cars.ToQueryable());