Все реализации Ef Core IQueryable<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);
Так что, даже если вы не возвращаете явно IAsyncEnumerable<T>
, базовый объект реализует его и может быть запрошен. Зная, что Asp. Net Ядро является естественным асинхронным c фреймворком, мы можем смело предположить, что он проверяет, реализует ли объект новый стандарт IAsyncEnumerable<T>
, и использует его за кадром вместо IEnumerable<T>
.
Конечно, когда вы используете ToList()
, возвращенный класс List<T>
не реализует IAsyncEnumerable<T>
, поэтому единственный вариант - использовать IEnumerable<T>
.
Это должно объяснить поведение 3.1. Обратите внимание, что до 3.0 не было стандартного интерфейса IAsyncEnumerable<T>
. EF Core внедрял и возвращал свой собственный асин c интерфейс, но инфраструктура. 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
), а возвращенный базовый асинхронный c перечисляемый скрыт с помощью вышеупомянутого метода.
Вот упрощенная реализация вышеприведенного:
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
, Max
, *1058* et c., но это должно работать для этого сценария.
Другим недостатком этой реализации является то, что все расширения EF Core, указанные c Queryable
, не будут работать, что может быть проблемой / showtopper, если OData $expand
использует такие методы, как Include
/ ThenInclude
. Но исправление, которое требует более сложной реализации, копаясь во внутренностях EF Core.
С учетом сказанного, использование, конечно, будет:
return Ok(_carsDataModelContext.Cars.ToQueryable());