Tengo un controlador OData que parece bastante estándar.

[HttpGet]
[ODataRoute("GridData")]
[EnableQuery]
public async Task<IQueryable<GridData>> GetGridData(ODataQueryOptions<GridData> odataOptions)
{
    var query = odataOptions.ApplyTo(_service.GetGridDataQueryable()) as IQueryable<GridData>
    return query;
}

La proyección se ve así:

.Select(async x =>
{
   //Pretty resource heavy
   x.Ownership = await _ownershipService.ComputeAsync(_currentUser)); 
   return x;
})
.Select(t => t.Result)
.ToList();

Ahora el problema es que necesito devolver un objeto GridDataDTO de esta llamada. Hay algún procesamiento que no se puede realizar a nivel de base de datos. El procesamiento es bastante pesado, por lo que no me gustaría agregarlo dentro de GetGridDataQueryable (). Además, el procesamiento es asíncrono y necesita un conjunto de resultados materializados para poder aplicarlo.

También necesito devolver el IQueryable en el controlador para poder beneficiarme de $ count, $ select, etc.

Esto se conecta a una cuadrícula bastante compleja con muchas opciones para filtrar / ordenar, por lo que no me gustaría eliminar la funcionalidad de OData.

¿Existe una forma sencilla de agregar posprocesamiento aquí? Después de que se materialice el resultado, ¿proyectarlo en mi GridDataDTO?

No es necesario el soporte para insertar / actualizar / eliminar, ya que esto solo se utilizará para operaciones de lectura.

2
Ndy 31 ago. 2020 a las 16:24

1 respuesta

La mejor respuesta

No hay ningún requisito para que su método de controlador solo pase a través de una consulta de la base de datos, de hecho, su método no necesita devolver un resultado IQueryable<T> en absoluto.

Aún puede beneficiarse de los operadores $select, $expand y $filter de OData en conjuntos de resultados que no son IQueryable<T>, pero pierde la mayoría de los beneficios de rendimiento de hacerlo y tiene para preparar sus datos para que los operadores puedan ser procesados, y tendrá que decorar explícitamente su punto final con el atributo [EnableQuery].

En el siguiente ejemplo, su consulta actual se materializa en la memoria, después de aplicar las opciones de consulta, podemos iterar sobre el conjunto y manipularlo según sea necesario.
Al final, se devuelve el mismo conjunto de registros, con los registros modificados, convertido como consultable para que coincida con la firma del método; sin embargo, el método seguiría funcionando igual si el resultado fuera IEnumerable<T>

Existe un fuerte argumento que dice que debe devolver IEnumerable<T> porque transmite la información correcta de que el juego de registros se ha materializado y no se ha aplazado.

[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();
}

Actualizar:

Cuando las manipulaciones implican la proyección en un nuevo tipo, entonces para admitir adecuadamente las opciones de consulta de OData, el tipo definido en su ODataQueryOptions<> debe ser asignable desde el tipo de elemento de salida. Puede hacer esto mediante herencia o con definiciones de conversión implícitas.

Si se requiere una conversión explícita (o no hay ninguna conversión disponible), tendrá que validar manualmente la lógica ApplyTo, la ODataQueryOptions debe ser una referencia de tipo válida para coincidir con la salida.

1
Chris Schaller 31 ago. 2020 a las 15:32