Reputation: 603
Here is my ODATA controller with an ODATA action.
public class MyController : ODataController
{
private readonly Repository_repo;
public MyController(IRepository repo)
{
_repo = repo;
}
[EnableQuery(PageSize = 10, EnsureStableOrdering = false)]
public IActionResult Get()
{
var data = _repo.GetData();
return Ok(data)
}
}
Here is my Repository method.
public IQueryable<DataModel> GetData() => _db.DbSet.Select(data=> new DataModel
{
// Implement model
}).
Now I understand there is no point making the GetData method in the repo as async because that is just returning a queryable which doesn't get executed till you call it's enumerator.
So the part to make async is the action method. How would I make this an async await- able call? Odata EnableQuery method expects an IQueryable as far as I am aware.
Upvotes: 4
Views: 1767
Reputation: 103
You need to return either an IAsyncEnumerable<T>
or ActionResult<IAsyncEnumerable<T>>
to ensure that the call is asynchronous.
An example of async calls using Microsoft.AspNetCore.OData
from 8.2.3+
[HttpGet]
[EnableQuery]
public IAsyncEnumerable<Customer> Get()
{
return this.context.Customers.AsAsyncEnumerable();
}
[HttpGet("{id}")]
[EnableQuery]
public ActionResult<IAsyncEnumerable<Customer>> GetById(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return BadRequest();
}
return Ok(this.context.Customers.AsAsyncEnumerable());
}
Check out the the GitHub discussion for more details and answers from OData repository contributors.
You can also create a custom EnableQueryAsyncAttribute
to simplify code changes if your project already has a few endpoints that need to be adjusted to asynchronous.
Partial solution by Shiney that may not cover all the cases:
public class CustomEnableQueryAttribute : EnableQueryAttribute
{
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
if (actionExecutedContext.Result is ObjectResult { DeclaredType: null } objectResult)
{
if (objectResult.Value is IAsyncEnumerable<Customer>)
{
objectResult.DeclaredType = typeof(IAsyncEnumerable<Customer>);
}
}
}
}
Upvotes: 0
Reputation: 376
You don't need to specify or apply with your own hands the EF Core Asyncronous operation in OData.
According to Microsoft, when you return an IQueryable
, the OData can execute using ef core capabilities.
See in https://learn.microsoft.com/en-us/odata/webapi/first-odata-api#update-the-controller
But, the answer is a bit implicit, so I tested it.
I created a BlockNonAsyncQueriesInterceptor
, that basically blocks (throws an exception and doesn't allow to execute the query) non async database methods.
public class BlockNonAsyncQueriesInterceptor : DbCommandInterceptor
{
private const string SyncCodeError = @"Synchronous code is not allowed for performance reasons.";
private readonly ILogger _logger;
public BlockNonAsyncQueriesInterceptor(ILogger logger)
{
_logger = logger;
}
public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Ok NonQueryExecutingAsync");
return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
}
public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
{
_logger.LogError("Error NonQueryExecuting");
throw new InvalidOperationException(SyncCodeError);
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Ok ReaderExecutingAsync");
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
_logger.LogError("Error ReaderExecuting");
throw new InvalidOperationException(SyncCodeError);
}
public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Ok ScalarExecutingAsync");
return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
}
public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
{
_logger.LogError("Error ScalarExecuting");
throw new InvalidOperationException(SyncCodeError);
}
}
And then, after I checked a fake end point that uses OData and Entity Framework Queryable
, I noticed that the OData executions are all really asynchronous.
See the output after the request: Ok ReaderExecutingAsync
To see more details about these tests, please see: https://github.com/GuilhermeBley/TestODataWithEf
Upvotes: 0
Reputation: 239260
You do not need to. The action method is just one part of the request pipeline; it not being async does not preclude other things in the pipeline from being async. The middleware that's processing the OData query is what is actually sending the query to the database, and very likely does so async (I'm not familiar with source code, and cannot say definitively). Regardless, your action need only be async if you're actually doing something async in it. Otherwise, don't worry about it.
Upvotes: -1