I've been looking around to find an answer to this question, but I can't seem to find any definitive answer. We use OData v4, using ODataQueryOptions.ApplyTo to apply the OData options to a query. We also use ODataQuerySettings to set the pagesize. When we set a page size, we cannot use ToListAsync() anymore on the IQueryable that is returned from ODataQueryOptions.ApplyTo. The error message says that the provider of the IQueryable is no longer from Entity Framework.
I found this is because when using a pagesize, OData resolves the IQueryable by passing it through a TruncatedCollection. This TruncatedCollection retrieves all (pagesize + 1) results from the database to check if there were more than pagesize results. However, ApplyTo is not an async method, so I can safely assume this database query is not performed asynchronously.
Is there something I can do to make sure the query is performed asynchronously? Surely the OData team has thought of this? Or is it fine to keep it synchronous as it is? To me it seems asynchronous IO is nearly a necessity nowadays, as we want our API to scale well and not have all our threads blocked while waiting for IO.
Thanks for the help!
Edit 1:
I was asked to give some code to explain what I mean.
In BaseController.cs:
public class BaseController : ODataController
{
private static readonly ODataQuerySettings DefaultSettings = new ODataQuerySettings() { PageSize = 60 };
protected Task<IHttpActionResult> ODataResult<T>(IQueryable<T> query, ODataQueryOptions<T> options)
{
IQueryable result = options.ApplyTo(query, DefaultSettings);
return Task.FromResult(ODataOk(result));
}
}
In CustomerController.cs:
public class CustomerController : BaseController
{
ICustomerService customerService;
public async Task<IHttpActionResult> Get(ODataQueryOptions<Customer> options)
{
var query = customerService.Query();
return await ODataResult(query, options);
}
}
As I said above though, the issue is in the underlying code of ApplyTo. This is a method from OData itself. The line:
IQueryable result = options.ApplyTo(query, DefaultSettings);
already executes the database query, due to the fact that we define a pagesize in the DefaultSettings. Defining a pagesize causes the underlying code in ApplyTo to retrieve all the data from the database, and then returns the retrieved list as a queryable. This means the database is queried in a synchronous function.
So, my question is: Is there a way to implement paging into OData without giving up on async reads? Or am I overcomplicating things when attempting to do this?
OData Client for . NET provides a serial of Begin/End methods to support asynchronous operations, such as executing queries and saving changes.
The Open Data Protocol (OData) enables the creation of REST-based data services, which allow resources, identified using Uniform Resource Locators (URLs) and defined in an Entity Data Model (EDM), to be published and edited by Web clients using simple HTTP messages.
OData defines parameters that can be used to modify an OData query. The client sends these parameters in the query string of the request URI. For example, to sort the results, a client uses the $orderby parameter: http://localhost/Products?$orderby=Name. The OData specification calls these parameters query options.
OData is a REST-based protocol for querying and updating data. It is built on technologies like HTTP, ATOM/XML and JSON.
One can implement paging in OData without giving up async reads. Paging basically means applying one more expression to the IQueryable instance. After calling IQueryable.Take, one can call IQueryable.ToListAsync(where enumeration would actually happen).
But Microsoft Web Api OData v4 implementation is such that the query enumeration is happening synchronously. See here. ODataQuerySettings.ApplyTo method is using TruncatedCollection internally. TruncatedCollection is inherited from System.Collections.Generic.List and when being created it passes down IQueryable constructor parameter to List's constructor which accepts IEnumerable, iterates it and copies into internal array.
So, you can fork Web Api OData (since it's open source), tweak it and make it async. Or implement your own version of ODataQuerySettings.ApplyTo which would be async.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With