I've got this query, which I'll simplify for brevity:
public IQueryable<User> GetByIdAsync(Guid userId)
{
return MyContext
.Users
//Bunch of Includes
//Most of which have a ThenInclude
//Followed by another ThenInclude
.FirstOrDefaultAsync(u => u.Id == userId)
}
When run for around 100 users, it takes over 15 seconds (running locally on my machine). Not great.
I've tried using AsNoTracking()
, as well as changing it to use a compiled query like so:
private static Func<MyContext, Guid, Task<User>> _getByIdAsync =
EF.CompileAsyncQuery((MyContext context, Guid userId) =>
context
.Users
//Same Includes as above
.Where(u => u.Id == userId)
.FirstOrDefault());
public IQueryable<User> GetByIdAsync(Guid userId)
{
return await _getByIdAsync(userId);
}
Still no difference.
I've had a look at this answer for a relevant thread, which suggests using plain old SQL:
https://stackoverflow.com/a/16977218/9778248
And I've had a look at this answer, which mentions clustered indexes:
https://stackoverflow.com/a/55557237/9778248
I certainly can't exclude any of the Includes
as the client depends on all this info. Redesigning is also not an option at this stage.
Questions
You have many ways but it all depends.
.FirstOrDefaultAsync(u => u.Id == userId)
which means that for 100 users you will go to database 100 times so in total 15 000 / 100 == equals 150 ms per request. To improve it try to get all 100 user at once using in
clause like .Where(u=> userIds.contains(u.Id))
Example.
private static Func<MyContext, Guid, Task<List<User>>> _getByIdAsync =
EF.CompileAsyncQuery((MyContext context, List<Guid> userIds) =>
context
.Users
//Same Includes as above
.Where(u => userIds.Contains(u.Id))).ToListAsync();
Example how you can query using joins
var query = (from users in context.Users
join otherTable in context.OtherTable on users.Id equals otherTable.UserId).ToList();
You can get sql using this sample
public IQueryable<User> GetByIdAsync(Guid userId)
{
var = query = MyContext
.Users
//Bunch of Includes
//Most of which have a ThenInclude
//Followed by another ThenInclude
var sql = query.ToSql(); // <--------------------------- sql query
return query.FirstOrDefaultAsync(u => u.Id == userId)
}
and use sql query to profile and see if its using indexes.
And lastly I really hate methods like this public IQueryable GetByIdAsync(Guid userId) problem is that most of the time you dont need all that includes, but you start using them more and more and become depend on them... This is why I would recommend use EF without repository pattern, EF itself is repository get from database only data you need.
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