Skip to content

Commit 20edb63

Browse files
authored
Cosmos: Stop generating discriminator clause when not needed (#34145)
Closes #20268
1 parent ec42520 commit 20edb63

20 files changed

+334
-385
lines changed

src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ protected virtual void ValidateSharedContainerCompatibility(
110110
int? defaultTtl = null;
111111
ThroughputProperties? throughput = null;
112112
IEntityType? firstEntityType = null;
113+
bool? isDiscriminatorMappingComplete = null;
114+
113115
foreach (var entityType in mappedTypes)
114116
{
115117
Check.DebugAssert(entityType.IsDocumentRoot(), "Only document roots expected here.");
@@ -177,6 +179,19 @@ protected virtual void ValidateSharedContainerCompatibility(
177179
}
178180

179181
discriminatorValues[discriminatorValue] = entityType;
182+
183+
var currentIsDiscriminatorMappingComplete = entityType.GetIsDiscriminatorMappingComplete();
184+
if (isDiscriminatorMappingComplete == null)
185+
{
186+
isDiscriminatorMappingComplete = currentIsDiscriminatorMappingComplete;
187+
}
188+
else if (currentIsDiscriminatorMappingComplete != isDiscriminatorMappingComplete)
189+
{
190+
throw new InvalidOperationException(
191+
CosmosStrings.IsDiscriminatorMappingCompleteMismatch(
192+
isDiscriminatorMappingComplete, firstEntityType.DisplayName(), entityType.DisplayName(),
193+
currentIsDiscriminatorMappingComplete, container));
194+
}
180195
}
181196

182197
var currentAnalyticalTtl = entityType.GetAnalyticalStoreTimeToLive();

src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EFCore.Cosmos/Properties/CosmosStrings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@
165165
<data name="InvalidResourceId" xml:space="preserve">
166166
<value>Unable to generate a valid 'id' value to execute a 'ReadItem' query. This usually happens when the value provided for one of the properties is 'null' or an empty string. Please supply a value that's not 'null' or an empty string.</value>
167167
</data>
168+
<data name="IsDiscriminatorMappingCompleteMismatch" xml:space="preserve">
169+
<value>The IsDiscriminatorMappingComplete setting was configured to '{isDiscriminatorMappingComplete1}' on '{entityType1}', but on '{entityType2}' it was configured to '{isDiscriminatorMappingComplete2}'. All entity types mapped to the same container '{container}' must be configured with the same IsDiscriminatorMappingComplete value.</value>
170+
</data>
168171
<data name="JsonPropertyCollision" xml:space="preserve">
169172
<value>Both properties '{property1}' and '{property2}' on entity type '{entityType}' are mapped to '{storeName}'. Map one of the properties to a different JSON property.</value>
170173
</data>

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

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ public override Expression Translate(Expression expression)
234234
(property, parameter) => (property, parameter))
235235
.ToDictionary(tuple => tuple.property, tuple => tuple.parameter);
236236

237+
// TODO: Reimplement ReadItem properly: #34157
237238
_readItemInfo = new ReadItemInfo(entityType, propertyParameterList, clrType);
238239
}
239240
}
@@ -427,38 +428,34 @@ protected override ShapedQueryExpression CreateShapedQueryExpression(IEntityType
427428

428429
// Add discriminator predicate
429430
var concreteEntityTypes = entityType.GetConcreteDerivedTypesInclusive().ToList();
430-
if (concreteEntityTypes.Count == 1)
431+
if (concreteEntityTypes is [var singleEntityType]
432+
&& singleEntityType.GetIsDiscriminatorMappingComplete()
433+
&& entityType.GetContainer() is var container
434+
&& !entityType.Model.GetEntityTypes().Any(e => e.GetContainer() == container && e != singleEntityType))
431435
{
432-
var concreteEntityType = concreteEntityTypes[0];
433-
var discriminatorProperty = concreteEntityType.FindDiscriminatorProperty();
434-
if (discriminatorProperty != null)
436+
// There's a single entity type mapped to the container and the discriminator mapping is complete; we can skip the
437+
// discriminator predicate.
438+
}
439+
else
440+
{
441+
var discriminatorProperty = concreteEntityTypes[0].FindDiscriminatorProperty();
442+
Check.DebugAssert(
443+
discriminatorProperty is not null || concreteEntityTypes.Count == 1,
444+
"Missing discriminator property in hierarchy");
445+
if (discriminatorProperty is not null)
435446
{
436447
var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember()))
437448
.BindProperty(discriminatorProperty, clientEval: false);
438449

439450
var success = TryApplyPredicate(
440451
selectExpression,
441-
_sqlExpressionFactory.Equal(
452+
_sqlExpressionFactory.In(
442453
(SqlExpression)discriminatorColumn,
443-
_sqlExpressionFactory.Constant(concreteEntityType.GetDiscriminatorValue(), discriminatorColumn.Type)));
454+
concreteEntityTypes.Select(et => _sqlExpressionFactory.Constant(et.GetDiscriminatorValue(), discriminatorColumn.Type))
455+
.ToArray()));
444456
Check.DebugAssert(success, "Couldn't apply predicate when creating a new ShapedQueryExpression");
445457
}
446458
}
447-
else
448-
{
449-
var discriminatorProperty = concreteEntityTypes[0].FindDiscriminatorProperty();
450-
Check.DebugAssert(discriminatorProperty is not null, "Missing discriminator property in hierarchy");
451-
var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember()))
452-
.BindProperty(discriminatorProperty, clientEval: false);
453-
454-
var success = TryApplyPredicate(
455-
selectExpression,
456-
_sqlExpressionFactory.In(
457-
(SqlExpression)discriminatorColumn,
458-
concreteEntityTypes.Select(et => _sqlExpressionFactory.Constant(et.GetDiscriminatorValue(), discriminatorColumn.Type))
459-
.ToArray()));
460-
Check.DebugAssert(success, "Couldn't apply predicate when creating a new ShapedQueryExpression");
461-
}
462459

463460
return CreateShapedQueryExpression(entityType, selectExpression);
464461
}
@@ -834,7 +831,7 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
834831
return null;
835832
}
836833

837-
if (select is { Predicate: null, Orderings: [] })
834+
if (select is { Orderings: [], Predicate: null, ReadItemInfo: null })
838835
{
839836
_queryCompilationContext.Logger.FirstWithoutOrderByAndFilterWarning();
840837
}

src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public sealed class SelectExpression : Expression, IPrintableExpression
2929
/// any release. You should only use it directly in your code with extreme caution and knowing that
3030
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3131
/// </summary>
32+
// TODO: Reimplement ReadItem properly: #34157
3233
public ReadItemInfo? ReadItemInfo { get; init; }
3334

3435
/// <summary>

0 commit comments

Comments
 (0)