-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple non-related dbset counts without filter executes as separate queries #27728
Comments
|
@smitpatel for me as a user this is a very confusing default, since it fires a lot of extra queries to the sql server. Just because it can be evaluated in advance, I do not agree it is something ef core should do. The extra queries add latency and hurts performance. When would it ever be a good thing to evaluate something like this in advance? In my real world example I found this when I tried to combined a query with 20+ counts, where only a few of them are filtered. Expected a single query to the server and instead got this crazy burst of requests. I would expect everything rooted on the same context to be inlined. If I wanted to do extra roundtrip to the sql server I would have assigned a variable separately and included in the query such as var userCount = await context.Users.CountAsync();
var bad = await context.Users.Select(u => new {
userCount = userCount,
animalCount = context.Animals.Count()
}).FirstAsync(); Would it be possible to reconsider this for 7.0? |
Can you share actual query? |
@smitpatel It doesn't matter what table I start off with, I just pick one that I know will contain at least one row. The real query looks like this var counts = await _context.Users.Select(u => new
{
offerCount = _context.Offers.Where(x => true).Count(),
notificationCount = _context.Notifications.Where(x => true).Count(),
messageCount = _context.Messages.Where(x => true).Count(),
onlineCount = _context.HubConnections
.Where(HubConnection.IsOnline)
.Select(hc => hc.UserId)
.Distinct()
.Count(),
contactCount = _context.Contacts.Where(x => true).Count(),
// .. +15 other aggregations
}).AsSingleQuery().FirstAsync(); Purpose is just to get some totals to show in admin pages for an application. The FirstAsync is just to not get more than one row of data, since each row will contain exactly the same information. Even better would be to have some kind of possibility to do one-off queries without a root to project as a single anonymous or dto object, so that I could write something like var counts = await _context.UnrootedQuery(q => new {
rowCount = _context.SomeDbSet.Count()
}); but that is actually a bit out of scope for this issue, I just would like ef core to ENSURE that all aggregations will be inlined in a single sql server query. (without the ugly |
Query batching (Tracked by #10879) would allow this to be done using LINQ in a relatively clear form with a single roundtrip. Until then, or if a specific form of SQL is required, the query could be written using FromSql. @joakimriedel Is the issue here that you want a single round trip, or is it important that this be a single query? |
@ajcvickers I haven't done any benchmarking, but I would assume the latency from roundtrips is worse than the additional query time from running 20+ simple scalar queries instead of one larger combined query where the columns do not relate to each other. Perhaps you have some better estimates on this for the work done on #10879 ? Still, I think the current implementation where |
Batching two SELECTs in a single roundtrip is likely to yield very similar performance to having the two selects as subqueries in a single SELECT query, i.e.: SELECT COUNT(*) FROM [Users];
SELECT COUNT(*) FROM [Animals] AS [a]; ... is very likely roughly equivalent to: SELECT TOP(1) (
SELECT COUNT(*)
FROM [Users] AS [u0]) AS [userCount], (
SELECT COUNT(*)
FROM [Animals] AS [a]) AS [animalCount]
FROM [Users] AS [u] (this is quite trivial to check). Batching two selects also has the advantage of possibly reusing query plans when the same So I do believe #10879 is the right general solution to this. I agree it's odd for us to do roundtrips only when Where is omitted, but whether that's worth fixing is an implementation complexity question (so @smitpatel). |
This a bit out of scope here. But it looks confusing for me. var good = await context.Users
.Select(u => new
{
userCount = context.Users.Where(x => true).Count(),
animalCount = context.Animals.Where(x => true).Count()
})
.FirstAsync(); Because we are using Select on Users DbSet but not using anything from that. It's tricky. So isn't it better to have a var good = await context.Query(ctx => new
{
userCount = ctx.Users.Where(x => true).Count(),
animalCount = ctx.Animals.Where(x => true).Count()
})
.ExecuteAsync(); This is more cleaner and easier to understand. |
We've discussed something like context.Query() in other contexts (e.g. for top-level aggregate methods, see #28264). @dotnet/efteam do we already have an issue tracking this? |
@divega Do you know if you ever created an issue for having a general-purpose |
See #29484. |
Including multiple non-related dbset counts in one query will perform multiple SQL queries if the dbsets are not filtered by a dummy where clause.
AsSingleQuery
does not help. I would prefer not having to remember to add the.Where(x => true)
part to prevent multiple queries.See full gist here.
Include your code
Include provider and version information
EF Core version: 6.0.3
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
The text was updated successfully, but these errors were encountered: