-
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
Query: Provide a way to indicate that part of a query should translate to server or throw #24509
Comments
Short answer no, just juse an AsEnumerable before your projection since you are done querying the DB anyway and the rest of the code is on the client side. |
@AndreyYurashevich Can you explain a bit more how having client evaluation in the final projection is causing you problems? |
@ajcvickers let's consider an entity:
In reality, Bio can be a number of columns, it doesn't really matter.
As a result, I get a pretty much light query that potentially may benefit from the DB index that includes *Name properties but not Bio.
So far so good, but some team member notices a way to refactor by extracting a method:
No visible behavior changes and no test can spot the difference, but after the refactoring, both queries always load and materialize the whole User object which hurts performance a lot. To make things worse the query doesn't look that simple in my real-world scenario as I'm projecting with AutoMapper. So it's easy to miss the change during code review. Ideally, there is some switch that I can turn on, and instead of falling back to any client evaluation, I get an exception when this happens. |
We discussed this in triage, and can see that it may have value. Backlogging for now to gather additional feedback. |
Second this. We perform a lot of heavy queries and we have a big development team. Sometimes its difficult to tell without logging the queries in debug mode, whether the query will execute serverside or clientside. We would like the opportunity to disable clientside evaluation by throwing, which would let us catch performance issues during development and testing without having to sift through debug logs. |
Shouldn't this be the default behavior, a not just an opt-in? EF team already made the decision to align client/server evaluation logic with user expectation by requiring ToList() and similar for everything except Select. Why make that distinction? Personally, I've seen a lot of cases where team mates accidentally added client evaluation to a server side query without even realizing it. There are numerous examples of people being confused in this repo as well, for example, when they don't realize they are mixing client and server evaluated Queryables with Concat/Union #16243 @ajcvickers are there any downsides to this proposal that were discussed in the triage? Is there any specific feedback EF team is looking for, other than experiencing this issue? I think most people would be satisfied with an opt-in, although opt-out seems like a better default to me. This breaking change can be managed in the same manner as the one you did for client queries in Net Core 3. |
I suggest something like |
@janseris changing the default to not allow client evaluation would be a massive breaking change for a huge number of existing applications. There's very little chance we'd do that. |
@roji It's a massive pain in .NET6, as a developer has no indication if something is evaluated locally except by manually checking the generated SQL. Unfortunately, there is also no Roslyn Code Analyzer to help, so we're stuck in being veeeeeery careful in PR reviews.
As you've changed this behavior several times now, wouldn't it have been easier to make this configurable instead of having implicit logic around, which nobody can catch automatically now? |
@DrMueller as far as I can remember, there was only one change here in the history of EF Core: the switch from allowing client evaluation everywhere before EF Core 3.0 to allowing it only in the top-most projection (and in parts of the query which don't reference the database at all). In the last 4.5 years since EF Core 3.0 there haven't been any changes around this, and we don't plan to make any. So I'm curious - can you provide more context on why it's important for you to be able to e.g. disable client evaluation altogether? Client evaluation in the top-level projection isn't problematic, in that it doesn't cause more data to be transferred from the server to the client. Specifically, we definitely don't intend to allow client-side evaluation everywhere again, like it was before EF Core 3.0. That has various drawbacks and risks to applications, not to mention the significant complexity of maintaining that mode in parallel to the normal one. |
The problem with client-side projection in other than top-level projection is that:
Why it's important to be able to disable client-side evaluation:
|
@janseris I'm not disagreeing that the option to fully disabling client evaluation could be useful (we've accepted this into the backlog etc.), but I'm still trying to make sure I understand what the negative impact of this is from your perspective. In other words, right - refactoring projections into a separate functions can accidentally cause client evaluation if that's done improperly, but what exactly negative consequence does that have for you? |
For a bit more context - I do fully understand that the above causes the entire entity to be loaded, since EF cannot translate your function; this can have a negative perf impact as you're fetching more database columns than you absolutely need to. I'm trying to understand if there's any other problem here in addition to that - this may help prioritze this higher. |
@roji A minor change that causes EF to switch to client side evaluation is hard to notice in PRs and can cause many issues. Just on top of my head (all of these things are not obvious from code):
Having client eval disabled by default (or by option) would align more with user expectation, and would prevent code with unexpected behavior from being merged. |
Can you provide a minimal code sample for this? I'm not aware of this.
Unless I'm missing something, disabling client evaluation would simply cause a different error to be thrown (since client evaluation is not possible). If that's the case, is that useful?
To set expectations, there's very little chance we'd disable client eval by default, because of the massive breakage this would cause for anyone relying on it. Yes, we did do a big break disabling most client eval in EF 3.0, but that's because universal client eval caused severe issues (e.g. inadvertently fetching an entire table because a Where clause got client-evaluated). I don't think there's anything even close to to that here in terms of importance. |
@roji Regarding your statement:
Probably I misunderstand the sentence, but isn't that exactly the case? If the content projection can't be translated to SQL, the logic fetches ALL fields from a table and silently applies the content projection locally. This isn't a problem for single entities, but in the current projects, there are a lot of huge tables. That's quite a bit performance hit, especially for stuff like lists, which, instead of fetching 2-3 columns, now fetch all columns, which goes over the wire to the memory. Why is this kinda problematic? Because a developer should NEVER intend to use this mechanic anyway; if he wants to use something in memory, he could use content projection with an anonymous type and then explicitly work with the in-memory list further. Therefore, all instances of the client-side evaluation are purely (technically invisible) developer mistakes. |
I've encountered this in code already, but you can try in a simple example:
|
This is right and because of this, I am changing my "feature request" or a proposed solution to something like:
which would cause a runtime exception or some kind of warning displayed in the IDE (maybe analyzer) about "accidental client-side projections not permitted" |
There's some truth to that; but fetching all columns of a row is generally far, far less of a problem compared to fetching all rows of a table, which is what universal client evaluation caused in pre-3.0 EF. It's very reasonable in many scenarios to pass through a function and fetch all columns, whereas it was almost never reasonable to trigger client evaluation in e.g. a Where clause in pre-3.0 EF.
That's a very strong, sweeping statement that doesn't correspond to what we're seeing with many users; the convenience of just using a Select typically outweighs the overhead of fetching a few extra columns.
Can you please open a separate issue with this with a minimal, runnable code sample? I'm not sure we're supposed to be doing that. /cc @ajcvickers |
Apart from performance other problem may occur. Compare:
vs
In addition from loading whole Blog entity nothing will let EF Core know to load Owner so we get NRE in runtime or even worse we can get incorrect results if related data is optional. |
I think I need to clarify:
If mapToDto is server evaluated then nothing is tracked. If mapToDto is client evaluated then EF is tracking unnecessarily even if the result of Select is a DTO. Just changing the fact that mapToDto is server or client translatable changes the way EF does tracking and user does not expect this. How is that not relevant to client evaluation? |
What exactly is mapToDto, can you show some code? |
Similar to the example I posted above https://dotnetfiddle.net/yHhWP9. |
I'm not seeing a server-evaluated mapToDto there. The server-evaluated projection projects to BlogId, which obviously means there's no tracked entity: context.Blogs.Select(b => b.BlogId).FirstOrDefault(); Whereas your client-evaled MapToDto receives the entire entity, which is very different: context.Blogs.Select(b => Extensions.MapToDto(b)).FirstOrDefault(); |
The point probably is that MapToDto is supposed to generate SQL which selects only required columns (which is reffered to as "server-side evaluation", probably named to be an opposite to "client-side evaluation") and thus the entity shouldn't be tracked. Posting code for the case that the issue isn't closed later for missing info if the link dies:
|
I think @roji you are missing the main point. It's possible to create a server evaluated MapToDto. For example using https://github.com/ClaveConsulting/Expressionify. I just didn't bother for this code example, because the main point is that we need a way to throw an error on client evaluation, because, in practice, when people accidently introduces client evaluation they are also fetching and tracking more data than they expect. |
God, stop mocking the developers. You are trying to prove to the whole world that your bug is a feature. It's a bug. If I want to do client-side logic, I'll call var blogs = context.Blogs
.AsEnumerable()
.Select(b => Extensions.MapToDto(b))
.ToArray(); And it will work exactly as I expect, as I programmed it. Perhaps this is not the best example, perhaps the bug is not reproduced here, but the point is clear. If you want to leave this feature - it's your wish. But, please, give us the opportunity to turn it off after all. |
This may be taken as a given but from a design point of view I would just like to suggest that this works similarly to tracking and query splitting options, in that you can configure a global behaviour and also use extension methods to opt both in and out at the query level. Disabling client-side evaluation globally could be a useful tool to find potentially problematic queries; you may then want to re-enable it for individual queries where it doesn't actually matter or there isn't an immediate solution. |
Everyone, I think the points here are clear and that further discussion isn't necessary. I think there's agreement that disabling client evaluation can be useful, as an opt in.
For some context here (we had a discussion on this in the team)... LINQ to SQL had some forms of client evaluation in the top projection. The old non-core EF did not, and the team got lots of complaints from users that this causes needless pain, and requested client evaluation in the top projection would be allowed, just like in LINQ to SQL. This was a main reason why it's allowed EF Core. Once again, we agree that disabling client evaluation entirely can make sense for some people/situations - but keep in mind that for many others, client evaluation is useful behavior that makes writing queries easier, and with very little actual, non-theoretical drawbacks. Beyond that, implementing this is a matter for priorization, and this issue currently has only 20 votes, so it will probably take us a while to get around to it. |
Thanks.
Could you give an example? |
I also don't agree it's useful behavior. In my opinion, people who are aware that EF is doing client side evaluation will want to be explicit and add ToList/AsEnumerable. On the other hand, people who don't realize this are the same ones that should be warned by EF. |
@irunjic it's very simple; in most scenarios, tables don't have many columns, and those columns aren't huge. In that typical scenario, fetching a few extra column makes very little difference in terms of perf. On the other hand, the fact that users don't have to constantly think about putting AsEnumerable before invoking a client function is helpful, especially for new users - we got ample signal from our userbase about that. In any case, it's really not worth debating further, since we're already agreed that an opt-out is a good idea, and that we're not going to change the default behavior because of the breaking change. |
Sorry, I understand that you'd like to move on, and I'd like to turn this around to a design discussion in a sec, but I wanted to give an example of why I agree that client evaluation really is useful behaviour at times. Disabling it completely could actually lead to the very scenario some want to avoid. This isn't arguing against the feature requested in this issue, which I would like to see too, I just think it's good to understand this from another angle. A lot of discussion seems to conflate client-eval with loading the full entity. This isn't always the case. // Currently we can do this. Although client evaluation is involved for the Select, EF Core is smart enough
// to only select the 3 columns needed for the client evaluation. The full entities are not selected.
var withClientEval = context.Blogs
.Where(x => x.Id < 5)
.Select(x => new BlogDto { MysteriousValue = context.DoMysteriousThing(x.Url, x.Title, x.Author) })
.ToList();
// If client evaluation was completely disabled, a developer might stick in an AsEnumerable like so, but this
// _would_ select the full entities - perhaps including a very large column. It would start tracking the entities etc.
var naiveWithoutClientEval = context.Blogs
.Where(x => x.Id < 5)
.AsEnumerable()
.Select(x => new BlogDto { MysteriousValue = context.DoMysteriousThing(x.Url, x.Title, x.Author) })
.ToList();
// To generate the same efficient query but without client-eval, a developer would need to select an intermediary
// type with only the columns they need first. This is clunky and would get worse if more columns are needed
var betterWithoutClientEval = context.Blogs
.Where(x => x.Id < 5)
.Select(x => new
{
x.Url,
x.Title,
x.Author
})
.AsEnumerable()
.Select(x => new BlogDto { MysteriousValue = context.DoMysteriousThing(x.Url, x.Title, x.Author) })
.ToList(); Moving on! 😀 Does this suggestion sound reasonable? #24509 (comment) |
@stevendarby @roji how about instead of disabling client evaluation, we add an option to disable client evaluation with entity capture (or whatever it's called when you pass the whole entity and EF has to fetch and track everything). That's the scenario I think everyone is having issues with in this thread. That would satisfy both camps? |
@irunjic @roji if targeting only entity capture (the term works for me!) then perhaps the simpler design is to raise a warning by default if this can be detected. This can then be configured to be ignored or to throw instead using the existing ConfigureWarnings pattern. Although it will raise a new warning for existing code, this isn't breaking, and steering people to use more targeted "column capture" (if not completely eliminating client-eval) seems like a good thing to do for everyone anyway. |
Yeah something like that works also. Having a warning (by default) for client evaluation with entity capture is good idea to resolve this whole problem. It's non breaking, it informs everybody of probable misuse, and it doesn't affect people that use client eval where EF can correctly optimize. |
@stevendarby thanks for making the point about entity capture vs. other forms of client evaluation - I agree that's important (and we briefly brought this up in some internal discussions). I'll bring this up with the team - a warning seems reasonable to me. Implementing this, though, may not be trivial. |
For anyone who is looking for a workaround for this bug (the part with refactoring mapping code into a separate method), you can use What
|
I meant in the context I'm working, a developer should never use the mechanic; not speaking on behalf of all developers of the world 😃 . When giving out guidelines, my main goal is to make the code as explicit as possible, making the code cleaner and better understandable for other developers and for analyzers. This feature is one of the implicit ones, which doesn't fit this bill of being explicitly clear, about what a developer tries to accomplish. Therefore, for us, it's just a hassle, but I think the topic was discussed enough, thank you for the feedbacks. |
No, when projecting with servers-side evaluation explicit Include is not needed, but it is needed when switching to the client evaluation. That was actually the main my concern initially as reduced performance is not that bad as code that starts throwing cryptic NREs. |
@stevendarby and others, we discussed this and we think it's reasonable to have a warning when the top-most client evaluation causes entity capture. |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Just add an option to disable client-side eval. Pragmatic programmers want predictable SQL queries from their LINQs, that's why. You can continue to evaluate on the client by default to keep backward compatibility. |
Just noting that there are two kinds of client-eval: on input (fragments in the LINQ query which can get evaluatable client-side and then sent as parameters with the query) and on output (final projection). I'm assuming nobody has an issue with the former. |
@roji please don't avoid the discussion. The goal is just an option to disable silent loading of unwanted data which is what EF does now when the projection is not inline, what @johnnyjob writes as well. Whatever it is called. |
I cannot tell for everybody, but I would like to avoid client side evaluation in the final projection. |
@roji well... I think this issue is definitely about the latter and there's much more demand for that. But the former is interesting.... As I understand it, client evaluation on input can sometimes involve compiling an expression and invoking it. For those wanting to eek out every last bit of performance, perhaps they would appreciate a warning or way to disable this, and be guided to evaluate into a variable outside of the LINQ query and use that variable instead - which I think would be less costly? But even if this made sense, it'd need tracking in a separate issue I think. |
It's a good question... For anything more complex than the simplest fragments (i.e. member access on a closure captured variable, aka a parameter) EF internally invokes the LINQ expression tree interpreter to evalute the tree fragment. This certainly isn't the most efficient thing in the world, but it's also a single up-front operation (per parameter) that's very, very unlikely to matter in any non-contrived scenario... I really don't see us adding a warning/opt-in for this - but it would be good to hear other opinions. |
I enjoy using EF Core 5.0 with Microsoft.EntityFrameworkCore.SqlServer on .NET 5.0, but it feels like Client evaluation in the top-level projection does not really fit into my workflow and I would like to be able to disable (either globally or on a per-query basis) falling back to client evaluation completely. By disabling I really mean any way to track it: throwing exceptions, issuing warnings, etc.
I couldn't find any references to this possibility so any information would help.
The text was updated successfully, but these errors were encountered: