Saa*_*aab 5 c# entity-framework-core .net-core
我正在使用 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`...
Run Code Online (Sandbox Code Playgroud)
我知道还有另一个关于相同错误的已回答问题。但我没有做显式的异步操作。
[HttpGet]
[ProducesResponseType(200, Type = typeof(Car[]))]
public IActionResult Index()
{
return Ok(_carsDataModelContext.Cars.AsEnumerable());
}
Run Code Online (Sandbox Code Playgroud)
_carDataModelContext.Car 只是一个简单的实体,将一对一映射到数据库中的表。 public virtual DbSet<Car> Cars { get; set; }
原来我回来Ok(_carsDataModelContext.Cars.AsQueryable())是因为我们需要支持OData。但为了确保不是 OData 把事情搞砸了,我尝试返回 AsEnumerable,并从方法中删除“[EnableQuery]”属性。但这仍然以相同的错误结束。
解决这个问题的唯一方法是,如果我回来 Ok(_carsDataModelContext.Cars.ToList())
所有 Ef CoreIQueryable<T>实现 ( DbSet<T>, EntityQueryable<T>) 也实现标准IAsyncEnumerable<T>接口(当从 .NET Core 3 使用时),因此AsEnumerable(),AsQueryable()并且AsAsyncEnumerable()简单地将相同的实例转换为相应的接口。
您可以使用以下代码段轻松验证:
var queryable = _carsDataModelContext.Cars.AsQueryable();
var enumerable = queryable.AsEnumerable();
var asyncEnumerable = queryable.AsAsyncEnumerable();
Debug.Assert(queryable == enumerable && queryable == asyncEnumerable);
Run Code Online (Sandbox Code Playgroud)
因此,即使您没有明确返回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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后使用
return Ok(_carsDataModelContext.Cars.ToEnumerable());
Run Code Online (Sandbox Code Playgroud)
如果你想回来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);
}
}
Run Code Online (Sandbox Code Playgroud)
查询供应商实现是不完全正确的,因为它假定只有自定义Queryable<T>将调用Execute的方法创建IEnumerable<T>的,只有像直接方法外部调用将被使用Count,FirstOrDefault,Max等,但它应该为这种情况下工作。
此实现的其他缺点是所有 EF Core 特定Queryable扩展都不起作用,如果 OData$expand依赖于Include/ 之类的方法,这可能是一个问题/阻碍ThenInclude。但是解决这个问题需要更复杂的实现,深入研究 EF Core 内部。
话虽如此,当然用法是:
return Ok(_carsDataModelContext.Cars.ToQueryable());
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
602 次 |
| 最近记录: |