-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Cosmos: Complex properties binding #37400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Cosmos: Complex properties binding #37400
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request implements binding logic for complex properties in the Cosmos DB provider, enabling proper materialization of complex types from JSON documents. The implementation introduces a custom CosmosStructuralTypeMaterializerSource that defers complex property handling to allow nested materialization expressions to be generated during query compilation.
Key changes:
- Introduces
CosmosStructuralTypeMaterializerSourceto control when complex properties are materialized during shaping - Implements nested complex property and collection binding in
CosmosShapedQueryCompilingExpressionVisitor - Enhances null-handling logic in
CosmosProjectionBindingExpressionVisitorfor nullable value types
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
src/EFCore.Cosmos/Query/Internal/CosmosStructuralTypeMaterializerSource.cs |
New class that overrides complex type materialization behavior to enable deferred binding |
src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs |
Adds logic to generate materialization expressions for complex properties and collections from JObject/JArray |
src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs |
Exposes methods and fields needed for complex property binding; adds handling for ComplexPropertyBindingExpression |
src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs |
Improves null-safety checks for nullable value types and refactors member expression updates |
src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs |
Registers the new CosmosStructuralTypeMaterializerSource service |
test/EFCore.Cosmos.FunctionalTests/CosmosComplexTypesTrackingTest.cs |
Uncomments assertions that now pass with complex property binding implemented |
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs |
New test class with Cosmos-specific overrides for complex property projection tests |
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesCosmosFixture.cs |
Test fixture configuration for complex properties tests in Cosmos |
...unctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs
Outdated
Show resolved
Hide resolved
...unctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
...unctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs
Show resolved
Hide resolved
src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
...unctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs
Show resolved
Hide resolved
...unctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
...unctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs
Outdated
Show resolved
Hide resolved
...unctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs
Outdated
Show resolved
Hide resolved
src/EFCore.Cosmos/Query/Internal/CosmosStructuralTypeMaterializerSource.cs
Outdated
Show resolved
Hide resolved
...hapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
roji
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @JoasE, thanks for submitting this and sorry I haven't gotten around to reviewing it earlier.
I admit that this part of the Cosmos query pipeline is generally unfamiliar to me, and also represents an older/legacy part of the EF codebase in general. I'll do my best to fully understand everything and provide meaningful feedback. As we add more test coverage I'm sure issues will be flushed out.
For now, here's a first round of review comments - most are nits but see some more important things as well.
| { | ||
| Expression updatedMemberExpression = memberExpression.Update( | ||
| expression != null ? MatchTypes(expression, memberExpression.Expression!.Type) : expression); | ||
| if (expression is null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please rename expression to innerExpression here, to make it clear what it's referring to? Otherwise the code below is somewhat confusing (I initially thought it's referring to the member expression)
| var expressionValue = Expression.Parameter(expression.Type); | ||
| var assignment = Expression.Assign(expressionValue, expression); | ||
|
|
||
| if (expression.Type.IsNullableType() == true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nit (also below):
| if (expression.Type.IsNullableType() == true | |
| if (expression.Type.IsNullableType() |
| var expressionValue = Expression.Parameter(expression.Type); | ||
| var assignment = Expression.Assign(expressionValue, expression); | ||
|
|
||
| if (expression.Type.IsNullableType() == true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand this correctly, when the inner expression is a Nullable<> and the outer isn't nullable, this adds "safe null" compensation logic, checking inner.HasValue() and then calling inner.Value.Foo only if true. Also, you're using a block with a variable and an assignment to make sure that inner only gets evaluated once (nice idea).
What I'm not clear on, is why exactly we need both this block and the one below (where we again check and compensate)...
Also, can you please add a short comment before the block explaining this (basically my explanation above, assuming it's correct), as it's really non-obvious?
|
|
||
| AssertSql( | ||
| """ | ||
| SELECT VALUE c |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be projecting out only c["RequiredAssociate"] rather than all of c, right? I'm assuming this is because query support isn't yet fully done (i.e. full translation capabilities), but will be done later?
Note that the same seems true for various other tests on this class.
| } | ||
| } | ||
|
|
||
| private int _currentComplexIndex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please move this to the top of the class, where we have fields.
| Constant(complexProperty.Name) | ||
| ) | ||
| ) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: we generally don't put closing parentheses on their own lines, but simply stack them on the last line of the expression. Can you please do a pass and ensure it's this way everywhere?
| Constant(complexProperty.Name) | |
| ) | |
| ) | |
| ); | |
| Constant(complexProperty.Name)))); |
| if (complexProperty.IsCollection) | ||
| { | ||
| expressions.Add(CreateComplexCollectionAssignmentBlock(member, complexProperty)); | ||
| } | ||
| else | ||
| { | ||
| expressions.Add(CreateComplexPropertyAssignmentBlock(member, complexProperty)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (complexProperty.IsCollection) | |
| { | |
| expressions.Add(CreateComplexCollectionAssignmentBlock(member, complexProperty)); | |
| } | |
| else | |
| { | |
| expressions.Add(CreateComplexPropertyAssignmentBlock(member, complexProperty)); | |
| } | |
| expressions.Add(complexProperty.IsCollection | |
| ? CreateComplexCollectionAssignmentBlock(member, complexProperty) | |
| : CreateComplexPropertyAssignmentBlock(member, complexProperty)); |
|
|
||
| private sealed class ComplexPropertyBindingExpression : Expression | ||
| { | ||
| public ComplexPropertyBindingExpression(IComplexProperty complexProperty, ParameterExpression jObjectParameter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use primary constructor
| private static readonly MethodInfo GetItemMethodInfo | ||
| = typeof(JObject).GetRuntimeProperties() | ||
| .Single(pi => pi.Name == "Item" && pi.GetIndexParameters()[0].ParameterType == typeof(string)) | ||
| public static readonly MethodInfo GetItemMethodInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually used anywhere not on a JObject, passing a string property name as an argument?
| case MethodCallExpression jObjectMethodCallExpression | ||
| when jObjectMethodCallExpression.Method.IsGenericMethod && jObjectMethodCallExpression.Method.GetGenericMethodDefinition() == ToObjectWithSerializerMethodInfo: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| case MethodCallExpression jObjectMethodCallExpression | |
| when jObjectMethodCallExpression.Method.IsGenericMethod && jObjectMethodCallExpression.Method.GetGenericMethodDefinition() == ToObjectWithSerializerMethodInfo: | |
| case MethodCallExpression { Method.IsGenericMethod: true } jObjectMethodCallExpression | |
| when jObjectMethodCallExpression.Method.GetGenericMethodDefinition() == ToObjectWithSerializerMethodInfo: |
Adds a CosmosStructuralTypeMaterializerSource and overrides AddStructuralTypeInitialization in CosmosShapedQueryCompilingExpressionVisitor to generate materialization expressions for complex properties
Leverages Nullable<>.HasValue for nullable value types in to support value types without equals operator
Part of: #31253