Home > Back-end >  IQueryable, IEnumerable and async in REST Web API for ASP.NET Core and OData
IQueryable, IEnumerable and async in REST Web API for ASP.NET Core and OData

Time:06-06

I'm playing around w/ OData and Web API for a project. The default generated EF controller in ASP gives endpoints/actions that look like :

// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
    return await _context.Users.ToListAsync();
}

From what i can gather, this returns a list of User objects that are serialized into JSON and returned to the consumer as a big JSON array. It is async to allow the server to serve other requests while EF queries the database in the background. The management of serving requests happens upstream by ASP and the ControllerBase class.

Following the example here: https://docs.microsoft.com/en-gb/shows/on-net/supercharging-your-web-apis-with-odata-and-aspnet-core?wt.mc_id=ondotnet-c9-cephilli, I can maintain a conventional REST API w/ Swagger/OpenAPI support by simply adding a Queryable attribute above the Get action.

However, this got me thinking: is this the optimal solution? If I have 1 million users, but the consumer is only calling a Top 100, will the Get Action wait to get the full Users DbSet before OData starts to query it for only 100 records to return to the consumer? Should I be returning an IQueryable in the form of the DbSet?:

// GET: api/Users
[HttpGet]
[EnableQuery]
public ActionResult<IQueryable<User>> GetUsers()
{
    return _context.Users;
}

Will this change either conventional API calls or OData calls now that the Action is no longer async? Is there some other optimal or recommended solution for returning queries that works across both conventional and OData APIs?

CodePudding user response:

There is a massive difference between IQueryable and IEnumerable.

IQueryable is an interface that allows LINQ to SQL meaning that it will run filtered query runs in the database whereas IEnumerable is LINQ to Object meaning it executes the query in the database, then filters out in the memory.

For example, In your case

public async Task<ActionResult<IQueryable<User>>> GetUsers()
{
    return await _context.Users;
}

this will return all users from the database so not much of a difference between IQueryable and IEnumerable, but as you mentioned you have 1 million records and you need only 100 records, then your query becomes

public async Task<ActionResult<IQueryable<User>>> GetUsers()
{
    return await _context.Users.Take(100);
}

Now, returning IEnumerable will load all of your million records to be loaded in memory and then give you the top 100, whereas IQueryable will get you only 100 from the database.

CodePudding user response:

Yes, you should return an IQueryable to translate your OData query into a database query:

Returning System.Linq.IQueryable or ActionResult enables OData to translate queries to SQL queries using ef core capabilities. Returning other types such as IEnumerable causes OData to perform queries in the app.

https://docs.microsoft.com/en-us/odata/webapi/first-odata-api#update-the-controller

Since support for IQueryable (and ef core) is baked into OData I would assume your action is still async, the difference is that the calls to ToListAsync and similar methods are now handled by the OData library.

  • Related