Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d352388
WIP: Add some tests
JoasE Jan 28, 2026
6490d7b
Copy over files: Use StructuralType where needed in query translation…
JoasE Jan 28, 2026
d9d91d2
Add some more tests
JoasE Jan 28, 2026
003383d
Rewrite some baselines
JoasE Jan 28, 2026
7f7b564
Fix async tests
JoasE Jan 28, 2026
1b4b5cd
WIP: distinct, contains and null
JoasE Jan 29, 2026
bbc3477
Fix test do not explicitly set int ids in base test but use value gen…
JoasE Jan 30, 2026
de6328f
Improve object retrieval for null comparison
JoasE Jan 30, 2026
607a921
Distinct detection
JoasE Jan 30, 2026
0457143
WIP some docs for question
JoasE Jan 31, 2026
5ac80e0
WIP: Match code structure of relational where possible
JoasE Feb 3, 2026
a682368
Merge branch 'main' of https://github.com/dotnet/efcore into feature/…
JoasE Feb 3, 2026
753f31e
Cleanup
JoasE Feb 3, 2026
88c84b6
Rename TypeBase to StructuralType
JoasE Feb 4, 2026
19c1de1
Cleanup and add todo
JoasE Feb 4, 2026
3396b9a
Merge branch 'main' of https://github.com/dotnet/efcore into feature/…
JoasE Feb 9, 2026
a5a8bd4
Implement test
JoasE Feb 9, 2026
3b21620
Remove old unused class
JoasE Feb 9, 2026
7127586
Remove skip on fixed tests
JoasE Feb 9, 2026
82e9b15
Rename test class
JoasE Feb 9, 2026
dd4b3f0
Fix null check structural comparison
JoasE Feb 9, 2026
8126941
Rename field
JoasE Feb 9, 2026
2a96a9b
WIP: Use direct comparison
JoasE Feb 9, 2026
dbd1d17
Use CollectionResultExpression for complex collections
JoasE Feb 10, 2026
ee66c0a
Regenerate test
JoasE Feb 10, 2026
c258b9c
Add test and fix
JoasE Feb 10, 2026
d248623
Add docs
JoasE Feb 10, 2026
b3d68eb
Use property clr type
JoasE Feb 10, 2026
3a19515
Remove bom
JoasE Feb 10, 2026
ddcba7e
Make internal
JoasE Feb 10, 2026
a89891e
Merge branch 'main' of https://github.com/dotnet/efcore into feature/…
JoasE Feb 22, 2026
8a94daa
Improve CollectionResultExpression
JoasE Mar 6, 2026
d7116d1
Add explanation for UsesClientProjection
JoasE Mar 6, 2026
0dcbf4e
Add IEntityType type check for BindNavigation
JoasE Mar 6, 2026
2ebd18c
Align CosmosSqlTranslatingExpressionVisitor.cs more
JoasE Mar 6, 2026
fb75af5
Variable renames and more matching to relational
JoasE Mar 6, 2026
5e2e70f
Merge branch 'main' of https://github.com/dotnet/efcore into feature/…
JoasE Mar 6, 2026
1fb5dbe
Rename variables
JoasE Mar 6, 2026
940dda7
fix typo
JoasE Mar 6, 2026
254631c
Remove unused
JoasE Mar 6, 2026
2af802a
Return a sql expression for VisitTypeBinary and fix in relational aswell
JoasE Mar 6, 2026
1e1b8e9
Fix unreachable exception
JoasE Mar 7, 2026
d01e57d
Add check for entity type in UpdateEntityType
JoasE Mar 7, 2026
a2f97d4
Align VisitMember
JoasE Mar 7, 2026
4108a59
Fix Convert
JoasE Mar 7, 2026
b30a197
Copy over UsesClientProjection in SelectExpression
JoasE Mar 7, 2026
ffaa948
Add small test
JoasE Mar 11, 2026
7b5606e
Add comments
JoasE Mar 11, 2026
b669615
Move back to StructuralTypeShaperExpression (remove CollectionResultE…
JoasE Mar 11, 2026
ebffe48
Use check.DebugAssert
JoasE Mar 11, 2026
d8f336d
Use shared private constructor Object_AccessExpression
JoasE Mar 12, 2026
7afa805
Invert if
JoasE Mar 12, 2026
22af6e1
whitespaces
JoasE Mar 12, 2026
f2f1e77
Remove async
JoasE Mar 12, 2026
24bf0d1
Clean tests
JoasE Mar 12, 2026
b353bac
Remove UseTestAutoIncrementIntIds
JoasE Mar 12, 2026
9f0c9d3
Clean using
JoasE Mar 12, 2026
5af0d59
Add comment for set operation
JoasE Mar 13, 2026
d682197
Use IPropertyBase for ctor and switch
JoasE Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ public static bool TryExtractArray(
projectedStructuralTypeShaper = shaper;
projection = shaper.ValueBufferExpression;
if (projection is ProjectionBindingExpression { ProjectionMember: { } projectionMember }
&& select.GetMappedProjection(projectionMember) is EntityProjectionExpression entityProjection)
&& select.GetMappedProjection(projectionMember) is StructuralTypeProjectionExpression structuralTypeProjection)
{
projection = entityProjection.Object;
projection = structuralTypeProjection.Object;
}
}
else
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Cosmos/Query/Internal/CosmosAliasManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ protected override Expression VisitExtension(Expression node)
ScalarReferenceExpression reference when aliasRewritingMap.TryGetValue(reference.Name, out var newAlias)
=> new ScalarReferenceExpression(newAlias, reference.Type, reference.TypeMapping),
ObjectReferenceExpression reference when aliasRewritingMap.TryGetValue(reference.Name, out var newAlias)
=> new ObjectReferenceExpression(reference.EntityType, newAlias),
=> new ObjectReferenceExpression(reference.StructuralType, newAlias),

_ => base.VisitExtension(node)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
var translation = _sqlTranslator.Translate(expression);
if (translation == null)
{
_selectExpression.IndicateClientProjection();
return base.Visit(expression);
}

Expand Down Expand Up @@ -214,11 +215,11 @@ protected override Expression VisitExtension(Expression extensionExpression)

if (_clientEval)
{
var entityProjection = (EntityProjectionExpression)projection;
var structuralTypeProjection = (StructuralTypeProjectionExpression)projection;

return entityShaperExpression.Update(
new ProjectionBindingExpression(
_selectExpression, _selectExpression.AddToProjection(entityProjection), typeof(ValueBuffer)));
_selectExpression, _selectExpression.AddToProjection(structuralTypeProjection), typeof(ValueBuffer)));
}

_projectionMapping[_projectionMembers.Peek()] = projection;
Expand Down Expand Up @@ -303,19 +304,19 @@ protected override Expression VisitMember(MemberExpression memberExpression)
return NullSafeUpdate(innerExpression);
}

var innerEntityProjection = shaperExpression.ValueBufferExpression switch
var innerStructuralTypeProjection = shaperExpression.ValueBufferExpression switch
{
ProjectionBindingExpression innerProjectionBindingExpression
=> (EntityProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,
=> (StructuralTypeProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,

// Unwrap EntityProjectionExpression when the root entity is not projected
// Unwrap StructuralTypeProjectionExpression when the root entity is not projected
UnaryExpression unaryExpression
=> (EntityProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,
=> (StructuralTypeProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,

_ => throw new InvalidOperationException(CoreStrings.TranslationFailed(memberExpression.Print()))
};

var navigationProjection = innerEntityProjection.BindMember(
var navigationProjection = innerStructuralTypeProjection.BindMember(
memberExpression.Member, innerExpression.Type, clientEval: true, out var propertyBase);

if (propertyBase is not INavigation navigation
Expand All @@ -326,10 +327,10 @@ UnaryExpression unaryExpression

switch (navigationProjection)
{
case EntityProjectionExpression entityProjection:
case StructuralTypeProjectionExpression structuralTypeProjection:
return new StructuralTypeShaperExpression(
navigation.TargetEntityType,
Expression.Convert(Expression.Convert(entityProjection, typeof(object)), typeof(ValueBuffer)),
Expression.Convert(Expression.Convert(structuralTypeProjection, typeof(object)), typeof(ValueBuffer)),
nullable: true);

case ObjectArrayAccessExpression objectArrayProjectionExpression:
Expand Down Expand Up @@ -525,16 +526,16 @@ when _collectionShaperMapping.TryGetValue(parameterExpression, out var collectio
return QueryCompilationContext.NotTranslatedExpression;
}

var innerEntityProjection = shaperExpression.ValueBufferExpression switch
var innerStructuralTypeProjection = shaperExpression.ValueBufferExpression switch
{
EntityProjectionExpression entityProjection
=> entityProjection,
StructuralTypeProjectionExpression structuralTypeProjection
=> structuralTypeProjection,

ProjectionBindingExpression innerProjectionBindingExpression
=> (EntityProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,
=> (StructuralTypeProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,

UnaryExpression unaryExpression
=> (EntityProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,
=> (StructuralTypeProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,

_ => throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print()))
};
Expand All @@ -543,7 +544,7 @@ UnaryExpression unaryExpression
var navigation = _includedNavigations.FirstOrDefault(n => n.Name == memberName);
if (navigation == null)
{
navigationProjection = innerEntityProjection.BindMember(
navigationProjection = innerStructuralTypeProjection.BindMember(
memberName, visitedSource.Type, clientEval: true, out var propertyBase);

if (propertyBase is not INavigation projectedNavigation || !projectedNavigation.IsEmbedded())
Expand All @@ -555,7 +556,7 @@ UnaryExpression unaryExpression
}
else
{
navigationProjection = innerEntityProjection.BindNavigation(navigation, clientEval: true);
navigationProjection = innerStructuralTypeProjection.BindNavigation(navigation, clientEval: true);
}

switch (navigationProjection)
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ public virtual CosmosSqlQuery GetSqlQuery(
/// 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>
protected override Expression VisitEntityProjection(EntityProjectionExpression entityProjectionExpression)
protected override Expression VisitStructuralTypeProjection(StructuralTypeProjectionExpression structuralTypeProjectionExpression)
{
Visit(entityProjectionExpression.Object);
Visit(structuralTypeProjectionExpression.Object);

return entityProjectionExpression;
return structuralTypeProjectionExpression;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
var alias = _aliasManager.GenerateSourceAlias(fromSql);
var selectExpression = new SelectExpression(
new SourceExpression(fromSql, alias),
new EntityProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));
new StructuralTypeProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));
return CreateShapedQueryExpression(entityType, selectExpression) ?? QueryCompilationContext.NotTranslatedExpression;

default:
Expand Down Expand Up @@ -300,7 +300,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
var alias = _aliasManager.GenerateSourceAlias("c");
var selectExpression = new SelectExpression(
new SourceExpression(new ObjectReferenceExpression(entityType, "root"), alias),
new EntityProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));
new StructuralTypeProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));

// Add discriminator predicate
var concreteEntityTypes = entityType.GetConcreteDerivedTypesInclusive().ToList();
Expand All @@ -323,7 +323,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
"Missing discriminator property in hierarchy");
if (discriminatorProperty is not null)
{
var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember()))
var discriminatorColumn = ((StructuralTypeProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember()))
.BindProperty(discriminatorProperty, clientEval: false);

var success = TryApplyPredicate(
Expand All @@ -340,9 +340,9 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
return CreateShapedQueryExpression(entityType, selectExpression);
}

private ShapedQueryExpression? CreateShapedQueryExpression(IEntityType entityType, SelectExpression queryExpression)
private ShapedQueryExpression? CreateShapedQueryExpression(ITypeBase structuralType, SelectExpression queryExpression)
{
if (!entityType.IsOwned())
if (structuralType is IEntityType entityType && !entityType.IsOwned())
{
var existingEntityType = _queryCompilationContext.RootEntityType;
if (existingEntityType is not null && existingEntityType != entityType)
Expand All @@ -358,7 +358,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
return new ShapedQueryExpression(
queryExpression,
new StructuralTypeShaperExpression(
entityType,
structuralType,
new ProjectionBindingExpression(queryExpression, new ProjectionMember(), typeof(ValueBuffer)),
nullable: false));
}
Expand Down Expand Up @@ -532,6 +532,14 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
return null;
}

// We can not apply distinct because SQL DISTINCT operates on the full
Comment thread
JoasE marked this conversation as resolved.
// structural type, but the shaper extracts only a subset of that data.
// Cosmos: Projecting out nested documents retrieves the entire document #34067
if (select.UsesClientProjection)
Comment thread
JoasE marked this conversation as resolved.
{
return null;
}

select.ApplyDistinct();

return source;
Expand Down Expand Up @@ -607,7 +615,7 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou

var translatedSelect =
new SelectExpression(
new EntityProjectionExpression(translation, (IEntityType)projectedStructuralTypeShaper.StructuralType));
new StructuralTypeProjectionExpression(translation, projectedStructuralTypeShaper.StructuralType));
return source.Update(
translatedSelect,
new StructuralTypeShaperExpression(
Expand Down Expand Up @@ -859,24 +867,19 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
/// </summary>
protected override ShapedQueryExpression? TranslateOfType(ShapedQueryExpression source, Type resultType)
{
if (source.ShaperExpression is not StructuralTypeShaperExpression entityShaperExpression)
if (source.ShaperExpression is not StructuralTypeShaperExpression { StructuralType: IEntityType entityType } shaper)
{
return null;
}

if (entityShaperExpression.StructuralType is not IEntityType entityType)
{
throw new UnreachableException("Complex types not supported in Cosmos");
}

if (entityType.ClrType == resultType)
{
return source;
}

var select = (SelectExpression)source.QueryExpression;

var parameterExpression = Expression.Parameter(entityShaperExpression.Type);
var parameterExpression = Expression.Parameter(shaper.Type);
var predicate = Expression.Lambda(Expression.TypeIs(parameterExpression, resultType), parameterExpression);

if (!TryApplyPredicate(source, predicate))
Expand All @@ -887,23 +890,23 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
var baseType = entityType.GetAllBaseTypes().SingleOrDefault(et => et.ClrType == resultType);
if (baseType != null)
{
return source.UpdateShaperExpression(entityShaperExpression.WithType(baseType));
return source.UpdateShaperExpression(shaper.WithType(baseType));
}

var derivedType = entityType.GetDerivedTypes().Single(et => et.ClrType == resultType);
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
var projectionBindingExpression = (ProjectionBindingExpression)shaper.ValueBufferExpression;

var projectionMember = projectionBindingExpression.ProjectionMember;
Check.DebugAssert(new ProjectionMember().Equals(projectionMember), "Invalid ProjectionMember when processing OfType");

var entityProjectionExpression = (EntityProjectionExpression)select.GetMappedProjection(projectionMember);
var structuralTypeProjectionExpression = (StructuralTypeProjectionExpression)select.GetMappedProjection(projectionMember);
select.ReplaceProjectionMapping(
new Dictionary<ProjectionMember, Expression>
{
{ projectionMember, entityProjectionExpression.UpdateEntityType(derivedType) }
{ projectionMember, structuralTypeProjectionExpression.UpdateEntityType(derivedType) }
});

return source.UpdateShaperExpression(entityShaperExpression.WithType(derivedType));
return source.UpdateShaperExpression(shaper.WithType(derivedType));
}

/// <summary>
Expand Down Expand Up @@ -1131,9 +1134,9 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
var translatedSelect = SelectExpression.CreateForCollection(
slice,
alias,
new EntityProjectionExpression(
new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, alias),
(IEntityType)projectedStructuralTypeShaper.StructuralType));
new StructuralTypeProjectionExpression(
new ObjectReferenceExpression(projectedStructuralTypeShaper.StructuralType, alias),
projectedStructuralTypeShaper.StructuralType));
return source.Update(
translatedSelect,
new StructuralTypeShaperExpression(
Expand Down Expand Up @@ -1270,9 +1273,9 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
var translatedSelect = SelectExpression.CreateForCollection(
slice,
alias,
new EntityProjectionExpression(
new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, alias),
(IEntityType)projectedStructuralTypeShaper.StructuralType));
new StructuralTypeProjectionExpression(
new ObjectReferenceExpression(projectedStructuralTypeShaper.StructuralType, alias),
projectedStructuralTypeShaper.StructuralType));
return source.Update(
translatedSelect,
new StructuralTypeShaperExpression(
Expand Down Expand Up @@ -1361,7 +1364,7 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
/// </summary>
protected override ShapedQueryExpression? TranslateMemberAccess(Expression source, MemberIdentity member)
{
// Attempt to translate access into a primitive collection property
// Attempt to translate access into a primitive, complex or embedded owned navigation collection property
if (_sqlTranslator.TryBindMember(
_sqlTranslator.Visit(source),
member,
Expand All @@ -1378,20 +1381,19 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s

switch (translatedExpression)
{
case StructuralTypeShaperExpression shaper when property is INavigation { IsCollection: true }:
case StructuralTypeShaperExpression shaper when property is INavigation { IsCollection: true }
or IComplexProperty { IsCollection: true }:
{
var targetEntityType = (IEntityType)shaper.StructuralType;
var projection = new EntityProjectionExpression(
new ObjectReferenceExpression(targetEntityType, sourceAlias), targetEntityType);
var targetStructuralType = shaper.StructuralType;
var projection = new StructuralTypeProjectionExpression(
new ObjectReferenceExpression(targetStructuralType, sourceAlias), targetStructuralType);
var select = SelectExpression.CreateForCollection(
shaper.ValueBufferExpression,
sourceAlias,
projection);
return CreateShapedQueryExpression(targetEntityType, select);
return CreateShapedQueryExpression(targetStructuralType, select);
}

// TODO: Collection of complex type (#31253)

// Note that non-collection navigations/complex types are handled in CosmosSqlTranslatingExpressionVisitor
// (no collection -> no queryable operators)

Expand Down Expand Up @@ -1666,7 +1668,7 @@ private bool TryPushdownIntoSubquery(SelectExpression select)
var translation = new ObjectFunctionExpression(functionName, [array1, array2], arrayType);
var alias = _aliasManager.GenerateSourceAlias(translation);
var select = SelectExpression.CreateForCollection(
translation, alias, new ObjectReferenceExpression((IEntityType)structuralType1, alias));
translation, alias, new ObjectReferenceExpression(structuralType1, alias));
return CreateShapedQueryExpression(select, structuralType1.ClrType);
}
}
Expand Down
Loading
Loading