Skip to content

Commit 0cdfa6e

Browse files
authored
Cosmos: Complex properties Query (#37577)
Part of #31253
1 parent 31144c5 commit 0cdfa6e

File tree

31 files changed

+2248
-529
lines changed

31 files changed

+2248
-529
lines changed

src/EFCore.Cosmos/Extensions/Internal/CosmosShapedQueryExpressionExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ public static bool TryExtractArray(
167167
projectedStructuralTypeShaper = shaper;
168168
projection = shaper.ValueBufferExpression;
169169
if (projection is ProjectionBindingExpression { ProjectionMember: { } projectionMember }
170-
&& select.GetMappedProjection(projectionMember) is EntityProjectionExpression entityProjection)
170+
&& select.GetMappedProjection(projectionMember) is StructuralTypeProjectionExpression structuralTypeProjection)
171171
{
172-
projection = entityProjection.Object;
172+
projection = structuralTypeProjection.Object;
173173
}
174174
}
175175
else

src/EFCore.Cosmos/Query/Internal/CosmosAliasManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ protected override Expression VisitExtension(Expression node)
226226
ScalarReferenceExpression reference when aliasRewritingMap.TryGetValue(reference.Name, out var newAlias)
227227
=> new ScalarReferenceExpression(newAlias, reference.Type, reference.TypeMapping),
228228
ObjectReferenceExpression reference when aliasRewritingMap.TryGetValue(reference.Name, out var newAlias)
229-
=> new ObjectReferenceExpression(reference.EntityType, newAlias),
229+
=> new ObjectReferenceExpression(reference.StructuralType, newAlias),
230230

231231
_ => base.VisitExtension(node)
232232
};

src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
133133
var translation = _sqlTranslator.Translate(expression);
134134
if (translation == null)
135135
{
136+
_selectExpression.IndicateClientProjection();
136137
return base.Visit(expression);
137138
}
138139

@@ -214,11 +215,11 @@ protected override Expression VisitExtension(Expression extensionExpression)
214215

215216
if (_clientEval)
216217
{
217-
var entityProjection = (EntityProjectionExpression)projection;
218+
var structuralTypeProjection = (StructuralTypeProjectionExpression)projection;
218219

219220
return entityShaperExpression.Update(
220221
new ProjectionBindingExpression(
221-
_selectExpression, _selectExpression.AddToProjection(entityProjection), typeof(ValueBuffer)));
222+
_selectExpression, _selectExpression.AddToProjection(structuralTypeProjection), typeof(ValueBuffer)));
222223
}
223224

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

306-
var innerEntityProjection = shaperExpression.ValueBufferExpression switch
307+
var innerStructuralTypeProjection = shaperExpression.ValueBufferExpression switch
307308
{
308309
ProjectionBindingExpression innerProjectionBindingExpression
309-
=> (EntityProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,
310+
=> (StructuralTypeProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,
310311

311-
// Unwrap EntityProjectionExpression when the root entity is not projected
312+
// Unwrap StructuralTypeProjectionExpression when the root entity is not projected
312313
UnaryExpression unaryExpression
313-
=> (EntityProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,
314+
=> (StructuralTypeProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,
314315

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

318-
var navigationProjection = innerEntityProjection.BindMember(
319+
var navigationProjection = innerStructuralTypeProjection.BindMember(
319320
memberExpression.Member, innerExpression.Type, clientEval: true, out var propertyBase);
320321

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

327328
switch (navigationProjection)
328329
{
329-
case EntityProjectionExpression entityProjection:
330+
case StructuralTypeProjectionExpression structuralTypeProjection:
330331
return new StructuralTypeShaperExpression(
331332
navigation.TargetEntityType,
332-
Expression.Convert(Expression.Convert(entityProjection, typeof(object)), typeof(ValueBuffer)),
333+
Expression.Convert(Expression.Convert(structuralTypeProjection, typeof(object)), typeof(ValueBuffer)),
333334
nullable: true);
334335

335336
case ObjectArrayAccessExpression objectArrayProjectionExpression:
@@ -525,16 +526,16 @@ when _collectionShaperMapping.TryGetValue(parameterExpression, out var collectio
525526
return QueryCompilationContext.NotTranslatedExpression;
526527
}
527528

528-
var innerEntityProjection = shaperExpression.ValueBufferExpression switch
529+
var innerStructuralTypeProjection = shaperExpression.ValueBufferExpression switch
529530
{
530-
EntityProjectionExpression entityProjection
531-
=> entityProjection,
531+
StructuralTypeProjectionExpression structuralTypeProjection
532+
=> structuralTypeProjection,
532533

533534
ProjectionBindingExpression innerProjectionBindingExpression
534-
=> (EntityProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,
535+
=> (StructuralTypeProjectionExpression)_selectExpression.Projection[innerProjectionBindingExpression.Index!.Value].Expression,
535536

536537
UnaryExpression unaryExpression
537-
=> (EntityProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,
538+
=> (StructuralTypeProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand,
538539

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

549550
if (propertyBase is not INavigation projectedNavigation || !projectedNavigation.IsEmbedded())
@@ -555,7 +556,7 @@ UnaryExpression unaryExpression
555556
}
556557
else
557558
{
558-
navigationProjection = innerEntityProjection.BindNavigation(navigation, clientEval: true);
559+
navigationProjection = innerStructuralTypeProjection.BindNavigation(navigation, clientEval: true);
559560
}
560561

561562
switch (navigationProjection)

src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ public virtual CosmosSqlQuery GetSqlQuery(
6262
/// any release. You should only use it directly in your code with extreme caution and knowing that
6363
/// doing so can result in application failures when updating to a new Entity Framework Core release.
6464
/// </summary>
65-
protected override Expression VisitEntityProjection(EntityProjectionExpression entityProjectionExpression)
65+
protected override Expression VisitStructuralTypeProjection(StructuralTypeProjectionExpression structuralTypeProjectionExpression)
6666
{
67-
Visit(entityProjectionExpression.Object);
67+
Visit(structuralTypeProjectionExpression.Object);
6868

69-
return entityProjectionExpression;
69+
return structuralTypeProjectionExpression;
7070
}
7171

7272
/// <summary>

src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
252252
var alias = _aliasManager.GenerateSourceAlias(fromSql);
253253
var selectExpression = new SelectExpression(
254254
new SourceExpression(fromSql, alias),
255-
new EntityProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));
255+
new StructuralTypeProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));
256256
return CreateShapedQueryExpression(entityType, selectExpression) ?? QueryCompilationContext.NotTranslatedExpression;
257257

258258
default:
@@ -300,7 +300,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
300300
var alias = _aliasManager.GenerateSourceAlias("c");
301301
var selectExpression = new SelectExpression(
302302
new SourceExpression(new ObjectReferenceExpression(entityType, "root"), alias),
303-
new EntityProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));
303+
new StructuralTypeProjectionExpression(new ObjectReferenceExpression(entityType, alias), entityType));
304304

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

329329
var success = TryApplyPredicate(
@@ -340,9 +340,9 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
340340
return CreateShapedQueryExpression(entityType, selectExpression);
341341
}
342342

343-
private ShapedQueryExpression? CreateShapedQueryExpression(IEntityType entityType, SelectExpression queryExpression)
343+
private ShapedQueryExpression? CreateShapedQueryExpression(ITypeBase structuralType, SelectExpression queryExpression)
344344
{
345-
if (!entityType.IsOwned())
345+
if (structuralType is IEntityType entityType && !entityType.IsOwned())
346346
{
347347
var existingEntityType = _queryCompilationContext.RootEntityType;
348348
if (existingEntityType is not null && existingEntityType != entityType)
@@ -358,7 +358,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
358358
return new ShapedQueryExpression(
359359
queryExpression,
360360
new StructuralTypeShaperExpression(
361-
entityType,
361+
structuralType,
362362
new ProjectionBindingExpression(queryExpression, new ProjectionMember(), typeof(ValueBuffer)),
363363
nullable: false));
364364
}
@@ -532,6 +532,14 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
532532
return null;
533533
}
534534

535+
// We can not apply distinct because SQL DISTINCT operates on the full
536+
// structural type, but the shaper extracts only a subset of that data.
537+
// Cosmos: Projecting out nested documents retrieves the entire document #34067
538+
if (select.UsesClientProjection)
539+
{
540+
return null;
541+
}
542+
535543
select.ApplyDistinct();
536544

537545
return source;
@@ -607,7 +615,7 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
607615

608616
var translatedSelect =
609617
new SelectExpression(
610-
new EntityProjectionExpression(translation, (IEntityType)projectedStructuralTypeShaper.StructuralType));
618+
new StructuralTypeProjectionExpression(translation, projectedStructuralTypeShaper.StructuralType));
611619
return source.Update(
612620
translatedSelect,
613621
new StructuralTypeShaperExpression(
@@ -859,24 +867,19 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
859867
/// </summary>
860868
protected override ShapedQueryExpression? TranslateOfType(ShapedQueryExpression source, Type resultType)
861869
{
862-
if (source.ShaperExpression is not StructuralTypeShaperExpression entityShaperExpression)
870+
if (source.ShaperExpression is not StructuralTypeShaperExpression { StructuralType: IEntityType entityType } shaper)
863871
{
864872
return null;
865873
}
866874

867-
if (entityShaperExpression.StructuralType is not IEntityType entityType)
868-
{
869-
throw new UnreachableException("Complex types not supported in Cosmos");
870-
}
871-
872875
if (entityType.ClrType == resultType)
873876
{
874877
return source;
875878
}
876879

877880
var select = (SelectExpression)source.QueryExpression;
878881

879-
var parameterExpression = Expression.Parameter(entityShaperExpression.Type);
882+
var parameterExpression = Expression.Parameter(shaper.Type);
880883
var predicate = Expression.Lambda(Expression.TypeIs(parameterExpression, resultType), parameterExpression);
881884

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

893896
var derivedType = entityType.GetDerivedTypes().Single(et => et.ClrType == resultType);
894-
var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
897+
var projectionBindingExpression = (ProjectionBindingExpression)shaper.ValueBufferExpression;
895898

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

899-
var entityProjectionExpression = (EntityProjectionExpression)select.GetMappedProjection(projectionMember);
902+
var structuralTypeProjectionExpression = (StructuralTypeProjectionExpression)select.GetMappedProjection(projectionMember);
900903
select.ReplaceProjectionMapping(
901904
new Dictionary<ProjectionMember, Expression>
902905
{
903-
{ projectionMember, entityProjectionExpression.UpdateEntityType(derivedType) }
906+
{ projectionMember, structuralTypeProjectionExpression.UpdateEntityType(derivedType) }
904907
});
905908

906-
return source.UpdateShaperExpression(entityShaperExpression.WithType(derivedType));
909+
return source.UpdateShaperExpression(shaper.WithType(derivedType));
907910
}
908911

909912
/// <summary>
@@ -1131,9 +1134,9 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
11311134
var translatedSelect = SelectExpression.CreateForCollection(
11321135
slice,
11331136
alias,
1134-
new EntityProjectionExpression(
1135-
new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, alias),
1136-
(IEntityType)projectedStructuralTypeShaper.StructuralType));
1137+
new StructuralTypeProjectionExpression(
1138+
new ObjectReferenceExpression(projectedStructuralTypeShaper.StructuralType, alias),
1139+
projectedStructuralTypeShaper.StructuralType));
11371140
return source.Update(
11381141
translatedSelect,
11391142
new StructuralTypeShaperExpression(
@@ -1270,9 +1273,9 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
12701273
var translatedSelect = SelectExpression.CreateForCollection(
12711274
slice,
12721275
alias,
1273-
new EntityProjectionExpression(
1274-
new ObjectReferenceExpression((IEntityType)projectedStructuralTypeShaper.StructuralType, alias),
1275-
(IEntityType)projectedStructuralTypeShaper.StructuralType));
1276+
new StructuralTypeProjectionExpression(
1277+
new ObjectReferenceExpression(projectedStructuralTypeShaper.StructuralType, alias),
1278+
projectedStructuralTypeShaper.StructuralType));
12761279
return source.Update(
12771280
translatedSelect,
12781281
new StructuralTypeShaperExpression(
@@ -1361,7 +1364,7 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
13611364
/// </summary>
13621365
protected override ShapedQueryExpression? TranslateMemberAccess(Expression source, MemberIdentity member)
13631366
{
1364-
// Attempt to translate access into a primitive collection property
1367+
// Attempt to translate access into a primitive, complex or embedded owned navigation collection property
13651368
if (_sqlTranslator.TryBindMember(
13661369
_sqlTranslator.Visit(source),
13671370
member,
@@ -1378,20 +1381,19 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
13781381

13791382
switch (translatedExpression)
13801383
{
1381-
case StructuralTypeShaperExpression shaper when property is INavigation { IsCollection: true }:
1384+
case StructuralTypeShaperExpression shaper when property is INavigation { IsCollection: true }
1385+
or IComplexProperty { IsCollection: true }:
13821386
{
1383-
var targetEntityType = (IEntityType)shaper.StructuralType;
1384-
var projection = new EntityProjectionExpression(
1385-
new ObjectReferenceExpression(targetEntityType, sourceAlias), targetEntityType);
1387+
var targetStructuralType = shaper.StructuralType;
1388+
var projection = new StructuralTypeProjectionExpression(
1389+
new ObjectReferenceExpression(targetStructuralType, sourceAlias), targetStructuralType);
13861390
var select = SelectExpression.CreateForCollection(
13871391
shaper.ValueBufferExpression,
13881392
sourceAlias,
13891393
projection);
1390-
return CreateShapedQueryExpression(targetEntityType, select);
1394+
return CreateShapedQueryExpression(targetStructuralType, select);
13911395
}
13921396

1393-
// TODO: Collection of complex type (#31253)
1394-
13951397
// Note that non-collection navigations/complex types are handled in CosmosSqlTranslatingExpressionVisitor
13961398
// (no collection -> no queryable operators)
13971399

@@ -1666,7 +1668,7 @@ private bool TryPushdownIntoSubquery(SelectExpression select)
16661668
var translation = new ObjectFunctionExpression(functionName, [array1, array2], arrayType);
16671669
var alias = _aliasManager.GenerateSourceAlias(translation);
16681670
var select = SelectExpression.CreateForCollection(
1669-
translation, alias, new ObjectReferenceExpression((IEntityType)structuralType1, alias));
1671+
translation, alias, new ObjectReferenceExpression(structuralType1, alias));
16701672
return CreateShapedQueryExpression(select, structuralType1.ClrType);
16711673
}
16721674
}

0 commit comments

Comments
 (0)