You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In our project we use Entity Framework Core 3.1 and throw when a synchronous DB call is detected (through a DbCommandInterceptor).
When doing a $count=true the oData framework sets request.ODataFeature().TotalCountFunc with Queryable.LongCount and executes that synchronously.
We have worked around the synchronous call by applying a custom [EnableQueryAsync] attribute instead of [EnableQuery] on the controller methods. See code below.
This workaround took a lot of effort and the oData library could perhaps provide an easier way.
Assemblies affected
Microsoft.AspNetCore.OData v7.5.0
Reproduce steps
GET /api/MyEntities$count=true
[EnableQuery]
public ActionResult<IQueryAble<MyEntity>> Get()
{
var queryable = this.dbContext.MyEntities.AsQueryable();
return this.Ok(queryable);
}
Expected result
In the future the oData library should make an asynchronous call when a provided async LongCount method is configured for a given IQueryProvider.
Actual result
A synchronous Queryable.LongCount call is made.
Additional detail
This is the custom attribute:
public class EnableQueryAsyncAttribute : EnableQueryAttribute
{
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// note: don't call the base method. This method does the same as the base method in ActionFilterAttribute, except
// it executes await OnActionExecutedAsync(...) before executing this.OnActionExecuted(...)
this.OnActionExecuting(context);
if (context.Result == null)
{
var resultContext = await next().ConfigureAwait(false);
await this.OnActionExecutedAsync(resultContext).ConfigureAwait(false);
}
}
private async Task OnActionExecutedAsync(ActionExecutedContext resultContext)
{
if (resultContext.Result is ObjectResult objectResult &&
objectResult.Value is IQueryable queryable &&
queryable.Provider is IAsyncQueryProvider)
{
var request = resultContext.HttpContext.Request;
var queryContext = new ODataQueryContext(request.GetModel(), queryable.ElementType, request.ODataFeature().Path);
var queryOptions = new ODataQueryOptions(queryContext, request);
if (queryOptions.Count.Value)
{
var filteredQueryable = (queryOptions.Filter == null ? queryable : queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()))
as IQueryable<dynamic>;
var cancellationToken = resultContext.HttpContext.RequestAborted;
var count = await filteredQueryable.LongCountAsync(cancellationToken).ConfigureAwait(false);
// Setting the TotalCount causes oData to not execute the TotalCountFunc.
request.ODataFeature().TotalCount = count;
if (count == 0)
{
// No need to have oData execute the queryable.
var instance = Activator.CreateInstance(typeof(List<>).MakeGenericType(queryable.ElementType));
resultContext.Result = new OkObjectResult(instance);
}
}
}
this.OnActionExecuted(resultContext);
}
}
I understand that the oData library can't know about the specific IQueryProvider's that's being used.
A nicer solution would be if we could configure the oData endpoint to use a specific asynchronous LongCount method for a given IQueryProvider in Startup.cs. Something like:
Currently OData looks rather messy in terms of using sync / async execution, at any given time you can't be sure if you will end up with a synchronous operation. I stumbled across this issue completely randomly and I was absolutely clueless that something like it could even be a thing.
In our project we use Entity Framework Core 3.1 and throw when a synchronous DB call is detected (through a
DbCommandInterceptor
).When doing a
$count=true
the oData framework setsrequest.ODataFeature().TotalCountFunc
withQueryable.LongCount
and executes that synchronously.We have worked around the synchronous call by applying a custom
[EnableQueryAsync]
attribute instead of[EnableQuery]
on the controller methods. See code below.This workaround took a lot of effort and the oData library could perhaps provide an easier way.
Assemblies affected
Microsoft.AspNetCore.OData v7.5.0
Reproduce steps
GET /api/MyEntities$count=true
Expected result
In the future the oData library should make an asynchronous call when a provided async LongCount method is configured for a given
IQueryProvider
.Actual result
A synchronous
Queryable.LongCount
call is made.Additional detail
This is the custom attribute:
I understand that the oData library can't know about the specific IQueryProvider's that's being used.
A nicer solution would be if we could configure the oData endpoint to use a specific asynchronous LongCount method for a given
IQueryProvider
inStartup.cs
. Something like:and then the
EnableQueryAttribute
could use the registered LongCountAsync method and similar code as ^^^.The text was updated successfully, but these errors were encountered: