-
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: Top projection should fully materialize and track entities before they are passed to client methods #12761
Comments
Maybe just rephrase to:
|
Thanks @ErikEJ for your response. Yes, indeed we could do that. But our problem is the massive (millions of LOC) legacy code-base which does not have full test coverage. We can try to fix places we know, but there is a good chance of missing many other places. Is there any possibility that EFCore will support this kind of usage in the future? |
@RoshanJeewantha Have you tried disabling the exception as detailed in the message? |
@ajcvickers The data needs to be loaded, so suppressing the exception won’t help. In the query above, is it possible to force device to be tracked somehow? If required, we could do query rewriting to include an additional method (e.g. TrackProjectionInputs()). |
You are using a static method You can convert your method to be of type Expression<Func<>> then EF Core can use the method inside ExpressionTree. Or as @ErikEJ posted, you need to call |
@smitpatel I appreciate the predicament, but the reality is that LINQ to SQL dealt with this scenario. I acknowledge the workarounds, I'm just wondering if there's a more global way of solving this. It seems to me that, logically, we're asking for the if (!entityIsTracked && !entityIsReferencedInProjection) {
// add DetachedLazyLoadingWarning
} How you'd determine I could be totally misunderstanding, but as I said, LINQ to SQL was able to solve this problem (and I get that they're totally different libraries). Looking forward to hearing your thoughts. |
I'm thinking about doing this via expression re-writing. Is there a way of identifying whether a given expression is going to result in client-side execution? |
@optiks, @smitpatel and I talked about this and we are not sure there is a way to workaround it by only rewriting the expression. Given the current behavior of EF Core, the entity needs to be tracked in order for lazy loading to work, so you need to make sure the entity is returned in the top-most projection that EF Core executes. Also, you need to invoke the method after EF Core has finished materialized that row. I.e. each of the IEnumerable.MoveNext() methods from EF Core need to return before you can invoke the client method. That leaves you with something like this: var employees = from device in context.Set<EmployeeDevice>() select device;
var employeeDtos = employees
.AsEnumerable()
.Select(device => DtoFactory.CreateEmployeeDto(device))
.ToList(); Of course if you know beforehand what navigation properties CreateEmployeeDto is going to dereference, you may choose to use Include() to make sure they are loaded, but that is an orthogonal. Anyway, I would like to clarify that I agree that some of the design choices we made in the EF Core implementaiton (FWIW, a lot of the complexity derives from the idea that that |
@divega, just to clarify, the expression re-writing would be to transparently insert the .ToList().AsQueryable(). Where it gets tricky is that some methods are evaluated and then passed into the resultant SQL query as parameters. e.g. .Where(x => x.SomeDate < DateUtility.Now()). In that case we don’t want to force materialisation, because it’ll result in an inefficient query, which is why I was wondering if there’s a way of checking whether a given expression would result in client-side execution. Cheers. |
@optiks Besides a few cases explained in #12672, and unless x.SomeDate is a client-only property (e.g. a property that isn't mapped to column in the database) a query like this: var q = xs.Where(x => x.SomeDate < DateUtility.Now()); Should cause DateUtility.Now() to be evaluated on the client then the value passed to a parameter. I.e. the query should still have a WHERE clause and be efficient. For the cases in which a filter predicate is really evaluated on the client, EF Core produces client-evaluation warnings. You should definitively check your logs to see if those can occur in cases in which a large number of rows could be returned and filtered on the client. You may even decide to turn those wranings into errors. That is documented at https://docs.microsoft.com/en-us/ef/core/querying/client-eval. I don't think you need to be concerned as much with that case, because if you are going to try to workaround this you should only be looking at what happens in the top-most Select() anyway. Besides that, you are not going to be able to resolve this introducing a call to Assuming you are already wrapping EF Core's query provider, what you will have to do is is rewrite the query to remove the top |
@divega Here's what I was thinking: https://github.com/avinash-phaniraj-readify/EFCoreTestBed/tree/spike/AutomagicMaterialization Obviously it needs to be generalised, but it appears to work. Can you see foresee any issues with this approach? |
@divega When using I'm thinking something like: var query =
context.Set<Device>()
.Config(c => c.Warnings.Ignore(RelationalEventId.QueryClientEvaluationWarning))
.Select(SomeFactoryMethod); |
@optiks I have been also thinking about how to provide more finely-grained control over client evaluation. However my conclusion was that an extension method that you can include in the query (e.g. just to give you an idea Edit: filed #12844 for this. |
@optiks yes, I think that approach implements the same thing I had in mind. I misunderstood you when you said you would rewrite the expression tree because I assumed that you would then try to pass it to our query provider, but you are actually evaluating ToList().AsQueryable().Select(...) using LINQ to Objects, and only the rest of the query with our provider. Keep in mind that when you call |
@divega, I've now integrated the spike into our code base, and it's looking good 👍 . |
Cool. Also, consider restricting this approach to only the top-most projection. AFAIR, that should be sufficient for LINQ to SQL parity. |
@divega, yep, done. I've got some test cases if you're interested. |
@optiks I have been thinking a bit more about the workaround and wondering about its performance impact. As usual, measuring is key, avoid premature optimization, etc., but if the impact is significant, and if you are not doing this already, consider that you shouldn't need to apply the workaround to all queries. You can probably add logic to recognize the pattern to decide whether you need it. |
Thanks @divega. I've emailed some more context around our specific workaround. |
We ended up giving up on this approach, as we lost the EF Core null-coalescing benefits. We've now also increased the scope of our transformation to deal with existing performance issues, which means we now Include() any required navigations up-front. This is still a design issue though IMO. |
EFCore throws DetachedLazyLoading exception when I try something like below,
Is this by design? This is a big problem for us as we are trying to migrate an existing code-base with Linq2Sql (this was a common pattern through out that code-base).
Exception message:
Stack trace:
Steps to reproduce
Repro included at https://github.com/avinash-phaniraj-readify/EFCoreTestBed/tree/DetachedNavProperty
Further technical details
EF Core version: 2.1
Database Provider: EntityFrameworkCore.SqlServerCompact35
Operating system: Windows 10.0.17134
IDE: Visual Studio 2017 15.7.4
The text was updated successfully, but these errors were encountered: