From a63802b54fb136da90d720299e853110ed80e964 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Tue, 30 Jul 2024 10:23:39 +0100 Subject: [PATCH] Handle projections of nested owned entity in Cosmos Fixes #34067 --- ...ionBindingRemovingExpressionVisitorBase.cs | 32 +++++++++++++------ .../Query/OwnedQueryCosmosTest.cs | 24 ++++++++------ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs index cd358df9eeb..393b7155eba 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs @@ -109,12 +109,12 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) break; } - Expression innerAccessExpression; + Expression valueExpression; switch (projectionExpression) { case ObjectArrayAccessExpression objectArrayProjectionExpression: - innerAccessExpression = objectArrayProjectionExpression.Object; _projectionBindings[objectArrayProjectionExpression] = parameterExpression; + valueExpression = CreateGetValueExpression(objectArrayProjectionExpression.Object, storeName, parameterExpression.Type); break; case EntityProjectionExpression entityProjectionExpression: @@ -123,13 +123,27 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) switch (accessExpression) { - case ObjectAccessExpression innerObjectAccessExpression: - innerAccessExpression = innerObjectAccessExpression.Object; - _ownerMappings[accessExpression] = - (innerObjectAccessExpression.Navigation.DeclaringEntityType, innerAccessExpression); - break; case ObjectReferenceExpression: - innerAccessExpression = jTokenParameter; + valueExpression = CreateGetValueExpression(jTokenParameter, storeName, parameterExpression.Type); + break; + + case ObjectAccessExpression: + // Access to an owned type may be nested inside another owned type, so collect the store names + // and add owner mappings for each. + var storeNames = new List(); + while (accessExpression is ObjectAccessExpression objectAccessExpression) + { + accessExpression = objectAccessExpression.Object; + storeNames.Add(objectAccessExpression.PropertyName); + _ownerMappings[objectAccessExpression] + = (objectAccessExpression.Navigation.DeclaringEntityType, accessExpression); + } + + valueExpression = CreateGetValueExpression(accessExpression, (string)null, typeof(JObject)); + for (var i = storeNames.Count - 1; i >= 0; i--) + { + valueExpression = CreateGetValueExpression(valueExpression, storeNames[i], typeof(JObject)); + } break; default: throw new InvalidOperationException( @@ -141,8 +155,6 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) throw new UnreachableException(); } - var valueExpression = CreateGetValueExpression(innerAccessExpression, storeName, parameterExpression.Type); - return MakeBinary(ExpressionType.Assign, binaryExpression.Left, valueExpression); } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs index 50d940fe7c1..c94ce08760d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs @@ -391,15 +391,21 @@ ORDER BY c["Id"] """); }); - // TODO: #34068 - public override async Task Project_owned_reference_navigation_which_does_not_own_additional(bool async) - { - // Always throws for sync. - if (async) - { - await Assert.ThrowsAsync(() => base.Project_owned_reference_navigation_which_does_not_own_additional(async)); - } - } + // Issue #34068 + public override Task Project_owned_reference_navigation_which_does_not_own_additional(bool async) + => CosmosTestHelpers.Instance.NoSyncTest( + async, async a => + { + await base.Project_owned_reference_navigation_which_does_not_own_additional(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA") +ORDER BY c["Id"] +"""); + }); public override Task No_ignored_include_warning_when_implicit_load(bool async) => CosmosTestHelpers.Instance.NoSyncTest(