#asp.net-core #odata
#asp.net-core #odata
Вопрос:
У меня есть контроллер OData, который выглядит довольно стандартно.
[HttpGet]
[ODataRoute("GridData")]
[EnableQuery]
public async Task<IQueryable<GridData>> GetGridData(ODataQueryOptions<GridData> odataOptions)
{
var query = odataOptions.ApplyTo(_service.GetGridDataQueryable()) as IQueryable<GridData>
return query;
}
Проекция выглядит следующим образом :
.Select(async x =>
{
//Pretty resource heavy
x.Ownership = await _ownershipService.ComputeAsync(_currentUser));
return x;
})
.Select(t => t.Result)
.ToList();
Теперь проблема в том, что мне нужно фактически вернуть объект GridDataDTO из этого вызова. Существует некоторая обработка, которая не может быть выполнена на уровне базы данных. Обработка довольно тяжелая, поэтому я бы не хотел добавлять ее в GetGridDataQueryable().Кроме того, обработка выполняется асинхронно, и для ее применения требуется материализованный набор результатов.
Мне также нужно вернуть IQueryable в контроллере, чтобы иметь возможность извлекать выгоду из $count, $ select и т.д.
Это подключается к довольно сложной сетке с множеством опций для фильтрации / сортировки, поэтому я бы не хотел удалять функциональность OData.
Есть ли простой способ добавить постобработку здесь? После того, как результат материализуется, спроецируйте его на мой GridDataDTO ?
Нет необходимости в поддержке вставки / обновления / удаления, поскольку это будет использоваться только для операций чтения.
Комментарии:
1. Не зная структуры рассматриваемых объектов, мы не можем легко предложить вам явные решения, но как только вы материализуете набор записей, возможно, с помощью . С помощью функции ToList() вы можете выполнять любые необходимые манипуляции, прежде чем возвращать набор или совершенно новый.
2. Проблема с . ToList() заключается в том, что он не будет поддерживать вызовы $ count таким образом. Ничего не возвращается в терминах «@odata.*****». Кажется, мне нужно позволить процессору OData творить чудеса, а затем применить постобработку к результату.
3. Если вы выполняете постобработку, $ count на самом деле больше не может означать столько же, это $ count до или после обработки набора записей? Вы можете подделать результат IQueryable из списка в памяти, но $count теперь может применяться только к измененному набору результатов, а не к базовой таблице, которая может не подходить для всех приложений
4. Манипуляция выполняется с точки зрения проецирования на другой DTO, изменяя класс. Количество элементов не изменилось.
5. Смотрите мой ответ тогда, хотя никакого проекта не было, должно быть очевидно, как и когда вы могли бы это добавить, в следующий раз добавьте эту деталь и еще некоторые особенности в свой пост, например, логику для вашего проекта, чтобы получить более полезный ответ.
Ответ №1:
Для вашего метода контроллера не требуется передавать только запрос из базы данных, фактически вашему методу вообще не нужно возвращать IQueryable<T>
результат!
Вы все еще можете извлечь выгоду из OData
$select
,$expand
и$filter
операторов для наборов результатов, которые не являютсяIQueryable<T>
, но вы теряете большую часть преимуществ в производительности от этого, и вам нужно подготовить данные, чтобы операторы могли быть обработаны, и вам придется явно украсить свою конечную точку[EnableQuery]
атрибутом.
В следующем примере ваш текущий запрос материализуется в памяти, после применения параметров запроса мы можем выполнить итерацию по набору и манипулировать им по мере необходимости.
В конце возвращается тот же набор записей с измененными записями, приведенными как запрашиваемые для соответствия сигнатуре метода, однако метод все равно будет функционировать так же, если результат был IEnumerable<T>
Существует веский аргумент, который гласит, что вы должны вернуть
IEnumerable<T>
, потому что он передает правильную информацию о том, что набор записей был реализован и не отложен.
[HttpGet]
[ODataRoute("GridData")]
public async Task<IQueryable<GridDataDTO>> GetGridData(ODataQueryOptions<GridData> odataOptions)
{
// NOTE: GridDataDTO : GridData
// apply $filter, $top and $skip to the DB query
IQueryable<GridData> query = odataOptions.ApplyTo(_service.GetGridDataQueryable());
// materialize
var list = query.ToList();
// project into DTO
List<GridDataDTO> output = list.Select(async x =>
{
var o = new GridDataDTO(x);
o.Ownership = await _ownershipService.ComputeAsync(_currentUser));
}).ToList();
// return, as Queryable
return output.AsQueryable();
}
Обновить:
Когда манипуляции включают проекцию в новый тип, то для правильной поддержки параметров запроса OData тип, определенный в вашем, ODataQueryOptions<>
должен быть присваиваемым из типа выходного элемента. Вы можете сделать это с помощью наследования или с помощью неявных определений приведения.
Если требуется явное приведение (или приведение вообще не доступно), вам придется вручную проверять ApplyTo
логику, ODataQueryOptions
должна быть допустимая ссылка на тип, чтобы соответствовать результату.
Комментарии:
1. Проблема, с которой я сталкиваюсь при таком подходе, заключается в том, что фильтрация / top / skip будет применена только после завершения работы метода. Это приведет к действительно плохой производительности, поскольку манипуляции будут выполняться со всем набором из базы данных.
2. Вы не описали свою логику, поэтому сложно дать вам универсальное решение для всех. Вы можете применить $top, $ skip, $ filter перед вашей проекцией, именно это делает
.ApplyTo
вызов, но если вы примените сначала, то ваше выражение $ filter должно работать с неизмененными полями. При необходимости можно также применять различные параметры запроса независимо или полностью вручную. Вам нужен более конкретный пример кода, если вы хотите более конкретное решение.3. Другой вариант — применить проекции исключительно в linq, возможно, возвращая другой DTO или начиная с другого запроса данных. Это, безусловно, можно сделать, но решение будет зависеть от вашего запроса, параметров OData, которые вы хотите поддерживать, и характера ваших прогнозов.
4. Думал, что будет простой способ применить эту постобработку (какой бы она ни была) после того, как конвейер OData завершит свою работу. Я не хочу переписывать фильтрацию OData в Linq. Что-то вроде перехватчика.
5.Вы упускаете суть, в первой строке
odataOptions.ApplyTo
ЭТО применение операторов входящих запросов к отложенному запросу. Он максимально оптимизирован. То, что вы пытаетесь сделать, в целом довольно просто, и да, вы можете использовать перехватчик, но мне это нужно было только в очень небольшом наборе очень специфичных сценариев. Не забывайте, что постобработка — это, как правило, компромисс между тем, сколько манипуляций вам НУЖНО выполнить на сервере, и тем, сколько из них легче выполнить на клиенте.