Strange projection behavior 8.0.8
@habbes is already working on this.
Since Mar 29, 2022.
- 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
- 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 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"
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.