From 8894fe41961d877443eeccdb9c5c5830369c517b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 24 Jul 2025 19:55:41 +0200 Subject: [PATCH] Support change tracking for complex JSON queries Part of #36296 --- ...ocessingExpressionVisitor.ClientMethods.cs | 8 ++--- ...sitor.ShaperProcessingExpressionVisitor.cs | 30 +++++++++++++------ .../ComplexPropertiesCollectionTestBase.cs | 13 +------- .../ComplexPropertiesMiscellaneousTestBase.cs | 13 +------- .../ComplexPropertiesProjectionTestBase.cs | 13 +------- .../ComplexPropertiesSetOperationsTestBase.cs | 13 +------- ...lexPropertiesStructuralEqualityTestBase.cs | 10 ------- 7 files changed, 29 insertions(+), 71 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs index f795d137eb1..fa7ced8e33a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs @@ -1092,7 +1092,7 @@ public static void IncludeJsonEntityReference TIncludingEntity entity, Func innerShaper, Action fixup, - bool trackingQuery) + bool performFixup) where TIncludingEntity : class where TIncludedEntity : class { @@ -1116,7 +1116,7 @@ public static void IncludeJsonEntityReference var included = innerShaper(queryContext, keyPropertyValues, jsonReaderData); - if (!trackingQuery) + if (performFixup) { fixup(entity, included); } @@ -1137,7 +1137,7 @@ public static void IncludeJsonEntityCollection innerShaper, Action getOrCreateCollectionObject, Action fixup, - bool trackingQuery) + bool performFixup) where TIncludingEntity : class where TIncludedCollectionElement : class { @@ -1181,7 +1181,7 @@ public static void IncludeJsonEntityCollection (ITypeBase)complexProperty.ComplexType, @@ -774,6 +771,12 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP _ => throw new UnreachableException() }; + // Disallow tracking queries to project owned entities (but not complex types) + if (relatedStructuralType is IEntityType && _isTracking) + { + throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); + } + // json entity collection at the root var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess( jsonProjectionInfo, @@ -1709,6 +1712,11 @@ private Expression CreateJsonShapers( inverseNavigation); innerFixupMap[navigationJsonPropertyName] = fixup; + + if (relatedStructuralType is IComplexType) + { + trackingInnerFixupMap[navigationJsonPropertyName] = fixup; + } } } @@ -1778,7 +1786,9 @@ private Expression CreateJsonShapers( shaperLambda, GetOrCreateCollectionObjectLambda(declaringClrType, relationship), fixup, - Constant(_isTracking)); + // Perform fixup only for non-tracking queries - for tracking queries the change tracker does fixup. + // Note that for complex JSON types we also perform fixup, even for tracking queries. + Constant(!_isTracking || structuralType is IComplexType)); return declaringClrType.IsAssignableFrom(containerEntityExpression.Type) ? includeJsonEntityCollectionMethodCall @@ -1796,7 +1806,9 @@ private Expression CreateJsonShapers( includingEntityExpression, shaperLambda, fixup, - Constant(_isTracking)); + // Perform fixup only for non-tracking queries - for tracking queries the change tracker does fixup. + // Note that for complex JSON types we also perform fixup, even for tracking queries. + Constant(!_isTracking || structuralType is IComplexType)); return declaringClrType.IsAssignableFrom(containerEntityExpression.Type) ? includeJsonEntityReferenceMethodCall diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesCollectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesCollectionTestBase.cs index 9a5c18602a3..90b8b488040 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesCollectionTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesCollectionTestBase.cs @@ -5,15 +5,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; public abstract class ComplexPropertiesCollectionTestBase(TFixture fixture) : RelationshipsCollectionTestBase(fixture) - where TFixture : ComplexPropertiesFixtureBase, new() -{ - // TODO: the following is temporary until change tracking is implemented for complex JSON types (#35962) - private readonly TrackingRewriter _trackingRewriter = new(QueryTrackingBehavior.NoTracking); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _trackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} + where TFixture : ComplexPropertiesFixtureBase, new(); diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs index 6f5fe01ec1f..bfae04d82e0 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs @@ -5,15 +5,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; public abstract class ComplexPropertiesMiscellaneousTestBase(TFixture fixture) : RelationshipsMiscellaneousTestBase(fixture) - where TFixture : ComplexPropertiesFixtureBase, new() -{ - // TODO: the following is temporary until change tracking is implemented for complex JSON types (#35962) - private readonly TrackingRewriter _trackingRewriter = new(QueryTrackingBehavior.NoTracking); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _trackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} + where TFixture : ComplexPropertiesFixtureBase, new(); diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs index 015cb8b715b..1985febdb1e 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs @@ -5,15 +5,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; public abstract class ComplexPropertiesProjectionTestBase(TFixture fixture) : RelationshipsProjectionTestBase(fixture) - where TFixture : ComplexPropertiesFixtureBase, new() -{ - // TODO: the following is temporary until change tracking is implemented for complex JSON types (#35962) - private readonly TrackingRewriter _trackingRewriter = new(QueryTrackingBehavior.NoTracking); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _trackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} + where TFixture : ComplexPropertiesFixtureBase, new(); diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesSetOperationsTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesSetOperationsTestBase.cs index dd94c7a10e3..fb556baaa85 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesSetOperationsTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesSetOperationsTestBase.cs @@ -5,15 +5,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; public abstract class ComplexPropertiesSetOperationsTestBase(TFixture fixture) : RelationshipsSetOperationsTestBase(fixture) - where TFixture : ComplexPropertiesFixtureBase, new() -{ - // TODO: the following is temporary until change tracking is implemented for complex JSON types (#35962) - private readonly TrackingRewriter _trackingRewriter = new(QueryTrackingBehavior.NoTracking); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _trackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } -} + where TFixture : ComplexPropertiesFixtureBase, new(); diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs index 1ed335c012e..e4618cdd783 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs @@ -74,14 +74,4 @@ await AssertQuery( ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection == nestedCollection), ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection.SequenceEqual(nestedCollection))); } - - // TODO: the following is temporary until change tracking is implemented for complex JSON types (#35962) - private readonly TrackingRewriter _trackingRewriter = new(QueryTrackingBehavior.NoTracking); - - protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) - { - var rewritten = _trackingRewriter.Visit(serverQueryExpression); - - return rewritten; - } }