0

When creating basic ASP.NET Core 2.1 MVC API web application using Entity, you can generate controller automatically after selecting model and dbContext (RMB -> Add -> Controller... -> API Controller with actions, using Entity Framework).

I wonder why all the methods generated are asynchronous, except for the basic Get() which returns all the table? Is it an error? It's a database call, therefore I'd expect it to be asynchronous. Is there no real benefit of using asynchronous call in this case? If yes, why?

    // GET: api/User
    [HttpGet]
    public IEnumerable<User> GetUsers()
    {
        return _context.Users;
    }
Maciej Wanat
  • 1,527
  • 1
  • 11
  • 23
  • That's just some code based on a default template. You have to wrtite your *own* code to do what you want. Sticking `async` in the signature won't make the method run asynchronously either. `async` is just syntactic sugar that allows using `await` to await *already* asynchronous operations, like `ToListAsync()`. – Panagiotis Kanavos Jan 03 '19 at 14:03

2 Answers2

2

return _context.Users simply returns an object of type DbSet<Users>. It doesn't iterate over it or do any work it's just passing an object that allows you access to database data.

DbSet<T> is also an IQueryable<T>, which means the database isn't called until you call some sort of executing function like .ToList() or .Single(x=>x.Id == idToLookFor)

If you were to iterate over it asynchronously then you would have an async Get() method, ex;

return await _context.Users.ToListAsync()


Update

I realized that actually didn't answer your question,

It's very unlikely that you would ever want to return your entire table. (SELECT * FROM [Users]) So what the Get() method here is an anti-pattern (in my opinion**) known as 'exposing an IQueryable'

So in your controller you can do something like

_context.Get().Where(user=>user.FirstName == 'Steve').ToList()

or you could make it async as you figure you should be doing on a database call

await _context.Get().Where(user=>user.FirstName == 'Steve').ToListAsync()

So, is the template generated Get() an error? No, but I have an opinion that you shouldn't be exposing an IQueryable as a public method, so I disagree with it.


IQueryable<T>

var query = _context.Users;  //SQL:  * FROM [Users]

query = query.Where(x=>x.Name == "Steve"); 
//SQL: * FROM [Users] WHERE Name = 'Steve'

query = query.Where(x=>x.wearsHats == true);
//SQL: * FROM [Users] WHERE Name = 'Steve' AND WearsHats = true

query = query.Select(x=>x.Name);
//SQL: Name FROM [Users] WHERE Name = 'Steve' AND WearsHats = true

var result = query.ToList()
//SQL: SELECT Name FROM [Users] WHERE Name = 'Steve' AND WearsHats = true
Community
  • 1
  • 1
Adam Vincent
  • 3,281
  • 14
  • 38
  • So is `_context.Users` cached? What does `.ToList()` change exactly? It's still asking for a records in the database, and returning same data (users), which you have to get from the database. – Maciej Wanat Jan 03 '19 at 15:11
  • I'm not an EF expert, there's a lot more that goes on under the hood than I care to learn about but the gist is DbSet is empty until you ask for _something_. When you ask for _something_, it's translated into an SQL query, which fetches your _something_ from the database, and hands you your _something_ in the type you requested. – Adam Vincent Jan 03 '19 at 15:17
  • If I understand what you've said correctly then exposing bare `DbSet` shouldn't give you any result (you haven't asked for _anything_), but it works correctly - it provides users table data from the database. How is that so? – Maciej Wanat Jan 03 '19 at 15:21
  • I'm short on time, check the update. I added some pseudo code which may or may not help. Of note... EF core doesn't really generate 'part of a SQL command', it's just there as explanatory. – Adam Vincent Jan 03 '19 at 15:41
  • 1
    @Peace the MVC infrastructure will iterate over the collection you returned from the controller when writing the response to the client. So the query execution is delayed until then. IMO it's a better practice to execute the query yourself by using `ToList()` or more preferably `await _context.Users.ToListAsync()`. – Henk Mollema Jan 03 '19 at 15:43
0

Yes, this is very inconsistent with all the other async actions. I don't think there is any specific reason for this. I think that Microsoft just forgot to update this part of the template. I would expect the following to be generated:

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

Even a one liner action can still benefit from being async - the IIS thread will not be blocked during the database query. You can read more about that in other questions on SO, for example here: When should I use Async Controllers in ASP.NET MVC?

EDIT: You can find the template sources on github. You can see in lines 38-42 that in the latest version this action is updated and will be generated in an async way exactly as I've written above.

LambdaCruiser
  • 498
  • 4
  • 12
  • I don't think the template is outdated. All the other methods are written with modern patterns and seems up-to-date. Whole template was updated not so long ago. I think there must be some reason why Microsoft made it this way. – Maciej Wanat Jan 03 '19 at 15:29
  • @Peace I guess "outdated template" is not exactly the right expression. What I meant is I think that Microsoft forgot to update the template part for this specific action. The fact that all the other actions are written in a modern way makes this one lonely synchronous action look even more out of place. – LambdaCruiser Jan 03 '19 at 16:11