Skip to content

Cosmos: Complex properties Query#37577

Open
JoasE wants to merge 41 commits intodotnet:mainfrom
JoasE:feature/complex-properties-query
Open

Cosmos: Complex properties Query#37577
JoasE wants to merge 41 commits intodotnet:mainfrom
JoasE:feature/complex-properties-query

Conversation

@JoasE
Copy link
Contributor

@JoasE JoasE commented Jan 28, 2026

Adds Cosmos Db query pipeline support for complex properties
Part of: #31253
Replace IEntityType and IProperty with ITypeBase and IPropertyBase in appropriate places. Along with a couple of class and variable renames
Adds ComplexProperty binding for StructuralTypeProjectionExpression
Do not translate distinct in TranslateDistinct with projections of child documents, related: #34067
Translate Nullable<>.HasValue to != null and Nullable<>.Value to direct property access
Move structural equality to partial class
Translate complex equality by direct comparison
Use CollectionResultExpression for complex property collections
Add assosiations, query and adhoc tests
list.Contains(complexType) not supported as per: 36468
TypeIs not supported, as per: 31250

  • I've read the guidelines for contributing and seen the walkthrough
  • I've posted a comment on an issue with a detailed description of how I am planning to contribute and got approval from a member of the team
  • The code builds and tests pass locally (also verified by our automated build checks)
  • Commit messages follow this format:
        Summary of the changes
        - Detail 1
        - Detail 2

        Fixes #bugnumber
  • Tests for the changes have been added (for bug fixes / features)
  • Code follows the same patterns and style as existing code in this repo

JoasE

This comment was marked as resolved.

@JoasE JoasE force-pushed the feature/complex-properties-query branch from c4dea47 to 3b21620 Compare February 9, 2026 09:47
@JoasE JoasE marked this pull request as ready for review February 9, 2026 10:03
@JoasE JoasE requested a review from a team as a code owner February 9, 2026 10:03
Copilot AI review requested due to automatic review settings February 9, 2026 10:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Cosmos DB query pipeline support for complex/structural types (complex properties), expanding translation capabilities and adding Cosmos-specific test coverage to validate expected SQL and failure behaviors.

Changes:

  • Generalizes Cosmos projection/binding from entity-only to structural types via StructuralTypeProjectionExpression, adding complex-property binding support.
  • Introduces structural equality translation for complex types (including parameter extraction support) and adds a Distinct translation guard when client projection is involved.
  • Adds/updates Cosmos functional tests for complex type queries and complex-property association scenarios (projection, equality, set operations, collections, primitive collections), plus a few ad-hoc helpers/fixtures.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs Adjusts ad-hoc complex-type test seeding and exposes context for provider-specific derivations.
test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs Updates Distinct-related baselines and replaces CI skip attribute.
test/EFCore.Cosmos.FunctionalTests/Query/ComplexTypeQueryCosmosTest.cs New Cosmos-specific complex type query test baselines and translation-failure expectations.
test/EFCore.Cosmos.FunctionalTests/Query/Associations/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs Adds a Distinct-over-owned-associate translation-failure test.
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesStructuralEqualityCosmosTest.cs New Cosmos tests validating complex/associate structural equality translation behavior.
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesSetOperationsCosmosTest.cs New Cosmos tests for set operations over complex-property association scenarios.
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesProjectionCosmosTest.cs Enables previously-skipped projection tests and updates baselines accordingly.
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesPrimitiveCollectionCosmosTest.cs New Cosmos tests for primitive-collection operations under complex properties.
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesMiscellaneousCosmosTest.cs New Cosmos tests for miscellaneous complex-property predicate/value-type translations.
test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesCollectionCosmosTest.cs New Cosmos tests for complex-property collection operators (Count/Where/Distinct/Index/etc.).
test/EFCore.Cosmos.FunctionalTests/Query/AdHocCosmosTestHelpers.cs Adds a helper to configure deterministic int key generation for Cosmos ad-hoc tests.
test/EFCore.Cosmos.FunctionalTests/Query/AdHocComplexTypeQueryCosmosTest.cs New Cosmos ad-hoc complex-type test with Cosmos SQL baselines and Cosmos-specific initialization.
test/EFCore.Cosmos.FunctionalTests/CosmosComplexTypesTrackingTest.cs Changes one tracking test to skip sync rather than silently completing, and runs async path.
src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs Updates visitor dispatch to the new structural projection expression type.
src/EFCore.Cosmos/Query/Internal/Expressions/StructuralTypeProjectionExpression.cs Renames/generalizes entity projection to structural projection and adds complex-property binding.
src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs Tracks whether client projection is used (for later translation decisions like Distinct).
src/EFCore.Cosmos/Query/Internal/Expressions/ObjectReferenceExpression.cs Generalizes object references from IEntityType to ITypeBase.
src/EFCore.Cosmos/Query/Internal/Expressions/ObjectArrayAccessExpression.cs Extends array-access expressions to support complex-property collections.
src/EFCore.Cosmos/Query/Internal/Expressions/ObjectAccessExpression.cs Extends object-access expressions to support complex properties and structural types.
src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs Refactors to partial class and routes equality rewriting to structural-type-aware logic.
src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.StructuralEquality.cs New structural equality implementation for entity/complex comparisons and parameter extraction.
src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs Updates projection binding removal to work with structural projections and new access expressions.
src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs Enables complex-property collection translation paths and prevents Distinct in client-projection scenarios.
src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs Updates SQL generator to use the new structural projection expression.
src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs Marks selects that fall back to client projection, impacting later translation decisions.
src/EFCore.Cosmos/Query/Internal/CosmosAliasManager.cs Updates alias rewriting for structural object references.
src/EFCore.Cosmos/Extensions/Internal/CosmosShapedQueryExpressionExtensions.cs Updates projection extraction logic for the renamed structural projection expression.
Comments suppressed due to low confidence (3)

src/EFCore.Cosmos/Query/Internal/Expressions/StructuralTypeProjectionExpression.cs:186

  • BindComplexProperty throws UnableToBindMemberToEntityProjection with memberType set to "navigation". This looks like a copy/paste error and will produce a misleading error message for complex properties; use a distinct memberType (e.g. "complexProperty") like the relational structural projection does.
    src/EFCore.Cosmos/Query/Internal/Expressions/StructuralTypeProjectionExpression.cs:287
  • UpdateEntityType should only be callable when StructuralType is an IEntityType. As written, it uses derivedType.GetAllBaseTypes().Contains(StructuralType) which mixes ITypeBase with the entity-type hierarchy and can misbehave (or fail to compile, depending on the GetAllBaseTypes() return type). Align this with the relational implementation by first asserting/casting StructuralType to IEntityType and then performing the base-type check against that entity type.
    src/EFCore.Cosmos/Query/Internal/Expressions/StructuralTypeProjectionExpression.cs:337
  • ToString() still returns the prefix "EntityProjectionExpression" even though the type was renamed to StructuralTypeProjectionExpression, which makes debugging/logging confusing. Update the string to reflect the new type name.

Comment on lines 288 to 292
var accessExpression = objectArrayAccess.InnerProjection.Object;
_projectionBindings[accessExpression] = jObjectParameter;
_ownerMappings[accessExpression] =
(objectArrayAccess.Navigation.DeclaringEntityType, objectArrayAccess.Object);
((IEntityType)objectArrayAccess.PropertyBase.DeclaringType, objectArrayAccess.Object);
_ordinalParameterBindings[accessExpression] = Add(
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

objectArrayAccess.PropertyBase can now be an IComplexProperty (complex collection). The cast to (IEntityType)objectArrayAccess.PropertyBase.DeclaringType will throw if the declaring type is a complex type. If _ownerMappings is only needed for owned navigations, guard this assignment to the navigation case (or change the mapping to store ITypeBase).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be as complex types are always handled by CosmosShapedQueryCompilingExpressionVisitor.AddStructuralTypeInitialization Should I add a comment here to clarify?

Comment on lines 127 to 131
accessExpression = objectAccessExpression.Object;
storeNames.Add(objectAccessExpression.PropertyName);
_ownerMappings[objectAccessExpression]
= (objectAccessExpression.Navigation.DeclaringEntityType, accessExpression);
= ((IEntityType)objectAccessExpression.PropertyBase.DeclaringType, accessExpression);
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ownerMappings assumes the access chain represents owned navigations and casts objectAccessExpression.PropertyBase.DeclaringType to IEntityType. With the new PropertyBase support for complex properties, ObjectAccessExpression can now represent an IComplexProperty, whose DeclaringType may be an IComplexType—this cast will throw at runtime. Consider only populating _ownerMappings when PropertyBase is an INavigation (owned entity navigation), and skip/handle complex-property accesses separately.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be as complex types are always handled by CosmosShapedQueryCompilingExpressionVisitor.AddStructuralTypeInitialization Should I add a comment here to clarify?

@roji
Copy link
Member

roji commented Feb 9, 2026

@JoasE thanks for this. I'll hold off reviewing until you resolve the Copilot comments above, just to save on the effort (we can do this in future PRs as well). Feel free to ping me once everything's ready for a human review.

Copy link

Copilot AI left a 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 32 out of 32 changed files in this pull request and generated 4 comments.

Copy link

Copilot AI left a 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 32 out of 32 changed files in this pull request and generated no new comments.

@JoasE JoasE marked this pull request as ready for review February 11, 2026 09:54
@JoasE JoasE requested a review from roji February 11, 2026 09:54
@JoasE
Copy link
Contributor Author

JoasE commented Feb 22, 2026

@roji btw, this should be ready now, incase you missed it!

Copy link
Member

@roji roji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoasE finally got around to looking at this, thanks for your patience. I reviewed up to a point, but noticed that there are some significant, unexplained discrepancies between the Cosmos implementation and the relational one (see comments). We can of course do things differently in Cosmos where there's a real difference/motivation, but we should otherwise strive to have the implementations more or less aligned, at least in principle - this would both make it easier to work across the providers, and possibly to extract out some common functionality in the future.

So I'll hold off completing the review for now. I'm happy to discuss point-by-point, but overall can you please go over and compare your implementation to the corresponding relational one, and either (a) align it where it makes sense, or (b) provide a justification for the discrepancy where it doesn't?

/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ShapedQueryExpression? Subquery { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks quite different from the relational CollectionResultExpression, which does not have Subquery vs. Parameter (those exist on StructuralTypeReferenceExpression, which is a very different thing). I'm not completely sure what design you're going for here, but it's important to try to align across the providers, at least unless there's a good reason not to. In relational, CollectionResultExpression is the shaper-side expression for a collection of structural types, so it should be oblivious to whether the source of the collection is a subquery or not.

/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public bool UsesClientProjection { get; private set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a completely new concept, and I'm not sure how it relates to implementing complex types... Once again, we don't have anything like this in relational - you'll have to explain this and why it's necessary on Cosmos.

if (!_complexPropertyExpressionsMap.TryGetValue(complexProperty, out var expression))
{
// TODO: Unify ObjectAccessExpression and ObjectArrayAccessExpression
expression = complexProperty.IsCollection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This differs significantly from the relational implementation - the latter assumes that _complexPropertyExpressionsMap contains the complexProperty (i.e. is pre-populated), and throws otherwise, whereas here you lazily create and populate it if it's not in the dictionary.

@JoasE
Copy link
Contributor Author

JoasE commented Mar 5, 2026

@roji Thanks for looking at this!
I apologize this isn’t up to the expected standard. I'll do another pass and provide explanations for discrepancies so we can better go over them, but I unfortunately might need some time to do that (possibly no sooner than this or next Monday)

This might be a bit premature but felt like explaining
I did try to stick to how things are done in relational where I felt it was possible, but also tried to match how the Cosmos DB provider is currently doing things. My goal was to prevent having to refactor or touch code unrelated to complex properties, possibly leading to a ripple effect. Maybe that already explains some of the things you saw, but it is definitely possible I went overboard in some cases. In retrospect, it may also be that trying to avoid the ripple effect wasn’t the right trade-off to make in the first place.

If you need anything else from me, let me know!

I will also try to do a better job explaining / leaving comments where I make certain choices in the future. If they are too bulky I can always remove them later.

Copilot AI review requested due to automatic review settings March 6, 2026 17:01
Copy link

Copilot AI left a 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 32 out of 32 changed files in this pull request and generated 3 comments.


You can also share your feedback on Copilot code review. Take the survey.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants