AspNetCoreOData
AspNetCoreOData copied to clipboard
Strange projection behavior 8.0.8
Extra projections added to wrapped IQueryable upon using $expand
Assemblies affected
Microsoft.AspNetCore.OData 8.0.8
Reproduce steps
The test project was made available on Github
- Call a get endpoint with the followinf URL "http://localhost:5216/api/Firsts?$expand=Seconds"
- In debug observe logs generated by different approaches in FirstsController.Get(ODataQueryOptions<First> opts) method,
- Observe longs generated by each GetEnumerator() call.
Expected result
Projections and respected queries are the same
Actual result
Logs state that upon calling od1 and od2 there is a difference on amount on projected navigation properties (Seconds were projected twice). This result into extra JOIN of the same table in case of SingleQuery mode or two identical queries in case of SplitQueres mode.
Logs for ef1 and ef2 are the same
Additional detail
Logs:
ef1:
Microsoft.EntityFrameworkCore.Query: Debug: Generated query execution expression: 'queryContext => new SingleQueryingEnumerable<First>( (RelationalQueryContext)queryContext, RelationalCommandCache.SelectExpression( Client Projections: 0 -> 0 1 -> 1 2 -> Dictionary<IProperty, int> { [Property: Second.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 2], [Property: Second.Name (string) Required, 3] } 3 -> 4 4 -> 5 5 -> 2 SELECT f.Id, f.Name, t.Id, t.Name, t.FirstId, t.SecondId FROM Firsts AS f LEFT JOIN ( SELECT s.Id, s.Name, b.FirstId, b.SecondId FROM Bindings AS b INNER JOIN Seconds AS s ON b.SecondId == s.Id ) AS t ON f.Id == t.FirstId ORDER BY f.Id ASC, t.FirstId ASC, t.SecondId ASC), Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, First>, WebApplication2.MyContext, False, False, True )'
SELECT "f"."Id", "f"."Name", "t"."Id", "t"."Name", "t"."FirstId", "t"."SecondId" FROM "Firsts" AS "f" LEFT JOIN ( SELECT "s"."Id", "s"."Name", "b"."FirstId", "b"."SecondId" FROM "Bindings" AS "b" INNER JOIN "Seconds" AS "s" ON "b"."SecondId" = "s"."Id" ) AS "t" ON "f"."Id" = "t"."FirstId" ORDER BY "f"."Id", "t"."FirstId", "t"."SecondId"
ef2:
SELECT "f"."Id", "f"."Name", "t"."Id", "t"."Name", "t"."FirstId", "t"."SecondId" FROM "Firsts" AS "f" LEFT JOIN ( SELECT "s"."Id", "s"."Name", "b"."FirstId", "b"."SecondId" FROM "Bindings" AS "b" INNER JOIN "Seconds" AS "s" ON "b"."SecondId" = "s"."Id" ) AS "t" ON "f"."Id" = "t"."FirstId" ORDER BY "f"."Id", "t"."FirstId", "t"."SecondId"
apparently the execution expression was the same
od1:
Microsoft.EntityFrameworkCore.Query: Debug: Generated query execution expression: 'queryContext => new SingleQueryingEnumerable<SelectAllAndExpand<First>>( (RelationalQueryContext)queryContext, RelationalCommandCache.SelectExpression( Client Projections: 0 -> Dictionary<IProperty, int> { [Property: First.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 0], [Property: First.Name (string) Required, 1] } 1 -> 0 2 -> Dictionary<IProperty, int> { [Property: Second.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 2], [Property: Second.Name (string) Required, 3] } 3 -> 4 4 -> 5 5 -> 2 6 -> Dictionary<IProperty, int> { [Property: Second.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 6], [Property: Second.Name (string) Required, 7] } 7 -> 8 8 -> 9 9 -> 6 SELECT f.Id, f.Name, t.Id, t.Name, t.FirstId, t.SecondId, t0.Id, t0.Name, t0.FirstId, t0.SecondId FROM Firsts AS f LEFT JOIN ( SELECT s.Id, s.Name, b.FirstId, b.SecondId FROM Bindings AS b INNER JOIN Seconds AS s ON b.SecondId == s.Id ) AS t ON f.Id == t.FirstId LEFT JOIN ( SELECT s0.Id, s0.Name, b0.FirstId, b0.SecondId FROM Bindings AS b0 INNER JOIN Seconds AS s0 ON b0.SecondId == s0.Id ) AS t0 ON f.Id == t0.FirstId ORDER BY f.Id ASC, t.FirstId ASC, t.SecondId ASC, t.Id ASC, t0.FirstId ASC, t0.SecondId ASC), Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, SelectAllAndExpand<First>>, WebApplication2.MyContext, False, False, True )'
and
SELECT "f"."Id", "f"."Name", "t"."Id", "t"."Name", "t"."FirstId", "t"."SecondId", "t0"."Id", "t0"."Name", "t0"."FirstId", "t0"."SecondId" FROM "Firsts" AS "f" LEFT JOIN ( SELECT "s"."Id", "s"."Name", "b"."FirstId", "b"."SecondId" FROM "Bindings" AS "b" INNER JOIN "Seconds" AS "s" ON "b"."SecondId" = "s"."Id" ) AS "t" ON "f"."Id" = "t"."FirstId" LEFT JOIN ( SELECT "s0"."Id", "s0"."Name", "b0"."FirstId", "b0"."SecondId" FROM "Bindings" AS "b0" INNER JOIN "Seconds" AS "s0" ON "b0"."SecondId" = "s0"."Id" ) AS "t0" ON "f"."Id" = "t0"."FirstId" ORDER BY "f"."Id", "t"."FirstId", "t"."SecondId", "t"."Id", "t0"."FirstId", "t0"."SecondId"
od2:
Microsoft.EntityFrameworkCore.Query: Debug: Generated query execution expression: 'queryContext => new SingleQueryingEnumerable<SelectAllAndExpand<First>>( (RelationalQueryContext)queryContext, RelationalCommandCache.SelectExpression( Client Projections: 0 -> Dictionary<IProperty, int> { [Property: First.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 0], [Property: First.Name (string) Required, 1] } 1 -> 0 2 -> Dictionary<IProperty, int> { [Property: Second.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 2], [Property: Second.Name (string) Required, 3] } 3 -> 4 4 -> 5 5 -> 2 SELECT f.Id, f.Name, t.Id, t.Name, t.FirstId, t.SecondId FROM Firsts AS f LEFT JOIN ( SELECT s.Id, s.Name, b.FirstId, b.SecondId FROM Bindings AS b INNER JOIN Seconds AS s ON b.SecondId == s.Id ) AS t ON f.Id == t.FirstId ORDER BY f.Id ASC, t.FirstId ASC, t.SecondId ASC), Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, SelectAllAndExpand<First>>, WebApplication2.MyContext, False, False, True )'
and
SELECT "f"."Id", "f"."Name", "t"."Id", "t"."Name", "t"."FirstId", "t"."SecondId" FROM "Firsts" AS "f" LEFT JOIN ( SELECT "s"."Id", "s"."Name", "b"."FirstId", "b"."SecondId" FROM "Bindings" AS "b" INNER JOIN "Seconds" AS "s" ON "b"."SecondId" = "s"."Id" ) AS "t" ON "f"."Id" = "t"."FirstId" ORDER BY "f"."Id", "t"."FirstId", "t"."SecondId"
The test project was made available on Github
That project is empty. Is that the correct URL? Did you forget to push the code?
Sorry. My fault. Had to recreate the project for legal reasons. It should be fine now
@Criperum this could be related to #497. I will investigate this and let you know how it goes.