AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

Strange projection behavior 8.0.8

Open Criperum opened this issue 3 years ago • 3 comments
trafficstars

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

  1. Call a get endpoint with the followinf URL "http://localhost:5216/api/Firsts?$expand=Seconds"
  2. In debug observe logs generated by different approaches in FirstsController.Get(ODataQueryOptions<First> opts) method,
  3. 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"

Criperum avatar Mar 25 '22 12:03 Criperum

The test project was made available on Github

That project is empty. Is that the correct URL? Did you forget to push the code?

julealgon avatar Mar 29 '22 12:03 julealgon

Sorry. My fault. Had to recreate the project for legal reasons. It should be fine now

Criperum avatar Mar 29 '22 12:03 Criperum

@Criperum this could be related to #497. I will investigate this and let you know how it goes.

habbes avatar Mar 30 '22 13:03 habbes