-
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
Projection filtering (Select().Where()) could not be translated on 2nd level #20826
Comments
This is because efcore is now blocking implicit execution of client-side LINQ-queries. When a LINQ query is being evaluated against a DbSet, EF Core tries to convert the entire LINQ tree to an SQL query. In efcore 2.2, when a part of the LINQ query could not be converted to SQL, the conversion stops and the query is being translated to sql early on. This caused performance issues on large databases (millions of records) to remain undetected. Your linq query:
is not entirely convertible to SQL, since in your Select-clause you're converting all items to anonymous objects. When the result for the LINQ query is being yielded, efcore detects that it cannot translate the Select-clause and therefor stops the conversion, throwing the exception stating that the LINQ query cannot be translated. This prevents performance issues from going undetected at development stage. Try following code instead:
EF Core allows untranslatable LINQ-statements up to 1 level (just enough to convert entities to dtos). In my opinion, this issue can be closed. |
There's no part which denies a translation to a simple SQL-92 query - because it's just a projection which renames fields of entities to a reduced subset. Level 1 projections are working, right. EF6 and EFCore2 where able to do level2 projections (and more) via SQL without any local transformation. EF3 allows this too - only the following 'WHERE'-limitation breaks. So, in my opinion, this is an issue which shouldnt be closed, because its a major drawback limitation in how we design a DTO (flat vs complex) only to get the 'WHERE' part of a query working. |
Perhaps related second level null checking: #13220 Error message in 3.1 seems similar, would be interesting for you to try 5.0 preview and see if the error message changes similarly. |
Might also be related: #12148 Seems like strange things happen when null checking in anonymous objects - @smitpatel might know why and if there is any improvements planned in this field for 5.0 - still holding my thumbs for a query translator that works with more complex queries. |
Same exception in 5.0.0-preview.3.20181.2 |
Okay, i figured it out how it ran on efcore2 (sqlite): SELECT "o"."ManagerId", "o.Manager"."FirstName", "o.Manager"."RoomNr", "o"."Id", "o"."Requirement", CASE
WHEN "o"."ManagerId" IS NOT NULL
THEN 1 ELSE 0
END, "o.Manager"."RoomNr" AS "Room"
FROM "Orders" AS "o"
LEFT JOIN "Employees" AS "o.Manager" ON "o"."ManagerId" = "o.Manager"."Id"
warn: Microsoft.EntityFrameworkCore.Query[20500]
The LINQ expression 'where ((IIF((Property([o], "ManagerId") != null), new <>f__AnonymousType2`2(FirstName = [o.Manager]?.FirstName, Room = Convert([o.Manager]?.RoomNr, Int32)), null) != null) AndAlso (IIF((Property([o], "ManagerId") != null), new <>f__AnonymousType2`2(FirstName = [o.Manager]?.FirstName, Room = Convert([o.Manager]?.RoomNr, Int32)), null).Room == 105))' could not be translated and will be evaluated locally.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] So basically we've got everything to do a WHERE in the server query, but unfortuenatly it was executed clientside. :( |
OK, perhaps not the same root cause then. But still, seems like null propagating in dto has some issues to be worked out in different ways regarding anonymous objects (dto). |
No, it seems more like a visitor mismatch while generating/translating the expression (o.Inner). Same error with concrete types public class ConreteOrder
{
public int Id { get; set; }
public String? Requirement { get; set; }
public ConreteEmployee? ManagerInfo { get; set; }
}
public class ConreteEmployee
{
public String? FirstName { get; set; }
public int Room { get; set; }
}
...
var projection = orders.Select(
o => new ConreteOrder
{
Id = o.Id,
Requirement = o.Requirement,
ManagerInfo = o.Manager != null
? new ConreteEmployee { FirstName = o.Manager.FirstName, Room = o.Manager.RoomNr}
: null
});
var expected = (from p in projection where p.ManagerInfo != null && p.ManagerInfo.Room == 105 select p).ToList();
even without nullability checks (which wouldnt run clientside either): var projection = orders.Select(
o => new ConreteOrder
{
Id = o.Id,
Requirement = o.Requirement,
ManagerInfo = o.Manager = new ConreteEmployee { FirstName = o.Manager.FirstName, Room = o.Manager.RoomNr}
});
var expected = (from p in projection where p.ManagerInfo.Room == 105 select p).ToList();
|
The only workaround is not using ProjectTo, but doing ToList() first, and after that issuing AutoMapper.Map(...) |
Sure, this would be a workaround, but it would load/deserialize tons of needless data, because the WHERE runs clientside. Again, this only happens with follow-up operations when the follow-up accesses a child property when the child is a transformation and not a plain entity. So I think it's a bug in the TranslatingExpressionVisitor which provides the wrong inner expression. Btw: sorry for the typo in Con_c_rete :) |
Not really @Reris . Please fix that guys, as this is a blocker for anyone who uses optional relationships. |
this has been fixed in 7c3a117 Originally posted by @maumar in #14321 (comment) EF CORE 3.X workaround (JUST A WORKAROUND !!!): copy QueryOptimizingExpressionVisitor override your DbContext class OnConfiguring method,
|
Duplicate of #20711 |
I'm currently upgrading my little API helper Riql to standard2.1/core3.1. Everything is fine, except efcore since 3.0.
If I try to reduce the transferred data with a select-projection, it works fine unless the where-part tries to compare compare a second level property (or any deeper)
There are NO external function calls nor anything else which should run locally - the exception only happens with inner projections with a following Where() statement.
Steps to reproduce
Reproduction code is available on Riql:
Test Apply_WhereProjectionLevel1_ShouldFilterProjection works:
Test Apply_WhereProjectionLevel2_ShouldFilterProjection causes the error:
The highlighted part is: p.ManagerInfo.Room == 105, accessing the second level of the projection.
The same happens with strict projection types / non-anonymous projections.
All versions prior to efcore 3 simply translated the projection to an inner selection and everything ran serverside.
Got Exception:
Further technical details
EF Core version: 3.1
Database provider: Sqlite, Postgres
Target framework: .NET Core 3.1
Operating system: Windows 10
IDE: Visual Studio 2019 16.5.4
Edit: Formatting, Added missing line in 1st example
The text was updated successfully, but these errors were encountered: