OData / OData/AspNetCoreOData

Strange projection behavior 8.0.8

Open
#540 3 comments 0 reactions 1 assignee View on GitHub

@habbes is already working on this.

Since Mar 29, 2022.

investigating P2
Dominant language
C#
Stars
505
Forks
186
PR merge metrics
No merged PRs in 30d

Description

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 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"

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.