Skip to content

Commit 38fbaf9

Browse files
authored
Fall out from #36502. (#36550)
1 parent 7d8701c commit 38fbaf9

16 files changed

+212
-511
lines changed

src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,13 @@ protected override Expression VisitExtension(Expression extensionExpression)
371371

372372
_projectionMapping[_projectionMembers.Peek()] = projection;
373373

374-
return shaper.Update(
375-
new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer)));
374+
return shaper
375+
.Update(new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer)))
376+
#pragma warning disable EF1001
377+
// This is to handle have correct type for the shaper expression. It is later fixed in MatchTypes.
378+
// This mirrors for structural types what we do for scalars.
379+
.MakeClrTypeNullable();
380+
#pragma warning restore EF1001
376381
}
377382

378383
case IncludeExpression includeExpression:
@@ -658,7 +663,14 @@ private static Expression MatchTypes(Expression expression, Type targetType)
658663
targetType.MakeNullable() == expression.Type,
659664
$"expression has type {expression.Type.Name}, but must be nullable over {targetType.Name}");
660665

661-
expression = Expression.Convert(expression, targetType);
666+
return expression switch
667+
{
668+
#pragma warning disable EF1001
669+
RelationalStructuralTypeShaperExpression structuralShaper => structuralShaper.MakeClrTypeNonNullable(),
670+
#pragma warning restore EF1001
671+
672+
_ => Expression.Convert(expression, targetType),
673+
};
662674
}
663675

664676
return expression;

src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,12 @@ bool TryRewriteComplexTypeEquality(bool collection, [NotNullWhen(true)] out SqlE
333333
// into complex properties to generate a flattened list of comparisons.
334334
// The moment we reach a a complex property that's mapped to JSON, we stop and generate a single comparison
335335
// for the whole complex type.
336-
bool TryGenerateComparisons(
337-
IComplexType type,
338-
Expression left,
339-
Expression right,
340-
[NotNullWhen(true)] ref SqlExpression? comparisons)
336+
bool TryGenerateComparisons(IComplexType complexType, Expression left, Expression right, [NotNullWhen(true)] ref SqlExpression? comparisons)
337+
=> TryGenerateComparisonsRec(complexType, left, right, ref comparisons, out _);
338+
bool TryGenerateComparisonsRec(IComplexType type, Expression left, Expression right, [NotNullWhen(true)] ref SqlExpression? comparisons, out bool exitImmediately)
341339
{
340+
exitImmediately = false;
341+
342342
if (type.IsMappedToJson())
343343
{
344344
var leftScalar = Process(left);
@@ -418,6 +418,17 @@ SqlParameterExpression parameter
418418
{
419419
var comparison = _sqlExpressionFactory.MakeBinary(nodeType, leftTranslation, rightTranslation, boolTypeMapping)!;
420420

421+
// If we have a required property and one of the sides is a constant null,
422+
// we can use just that property and skip comparing the rest of properties.
423+
if (!property.IsNullable
424+
&& (leftTranslation is SqlConstantExpression { Value: null }
425+
|| rightTranslation is SqlConstantExpression { Value: null }))
426+
{
427+
comparisons = comparison;
428+
exitImmediately = true;
429+
return true;
430+
}
431+
421432
comparisons = comparisons is null
422433
? comparison
423434
: nodeType == ExpressionType.Equal
@@ -447,10 +458,15 @@ SqlParameterExpression parameter
447458

448459
if (nestedLeft is null
449460
|| nestedRight is null
450-
|| !TryGenerateComparisons(complexProperty.ComplexType, nestedLeft, nestedRight, ref comparisons))
461+
|| !TryGenerateComparisonsRec(complexProperty.ComplexType, nestedLeft, nestedRight, ref comparisons, out exitImmediately))
451462
{
452463
return false;
453464
}
465+
466+
if (exitImmediately)
467+
{
468+
return true;
469+
}
454470
}
455471

456472
return comparisons is not null;

src/EFCore.Relational/Query/RelationalStructuralTypeShaperExpression.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class RelationalStructuralTypeShaperExpression : StructuralTypeShaperExpr
2424
/// <param name="valueBufferExpression">An expression of ValueBuffer to get values for properties of the entity.</param>
2525
/// <param name="nullable">A bool value indicating whether this entity instance can be null.</param>
2626
public RelationalStructuralTypeShaperExpression(ITypeBase structuralType, Expression valueBufferExpression, bool nullable)
27-
: base(structuralType, valueBufferExpression, nullable, materializationCondition: null)
27+
: base(structuralType, valueBufferExpression, nullable)
2828
{
2929
}
3030

@@ -38,12 +38,14 @@ public RelationalStructuralTypeShaperExpression(ITypeBase structuralType, Expres
3838
/// An expression of <see cref="Func{T,TResult}" /> to determine which entity type to
3939
/// materialize.
4040
/// </param>
41+
/// <param name="clrType">CLR type for this expression as returned from <see cref="Type"/>.</param>
4142
protected RelationalStructuralTypeShaperExpression(
4243
ITypeBase type,
4344
Expression valueBufferExpression,
4445
bool nullable,
45-
LambdaExpression? materializationCondition)
46-
: base(type, valueBufferExpression, nullable, materializationCondition)
46+
LambdaExpression? materializationCondition,
47+
Type clrType)
48+
: base(type, valueBufferExpression, nullable, materializationCondition, clrType)
4749
{
4850
}
4951

@@ -149,7 +151,7 @@ protected override LambdaExpression GenerateMaterializationCondition(ITypeBase t
149151
/// <inheritdoc />
150152
public override StructuralTypeShaperExpression WithType(ITypeBase type)
151153
=> type != StructuralType
152-
? new RelationalStructuralTypeShaperExpression(type, ValueBufferExpression, IsNullable)
154+
? new RelationalStructuralTypeShaperExpression(type, ValueBufferExpression, IsNullable, materializationCondition: null, type.ClrType)
153155
: this;
154156

155157
/// <inheritdoc />
@@ -175,12 +177,24 @@ public override StructuralTypeShaperExpression MakeNullable(bool nullable = true
175177
}
176178

177179
// Marking nullable requires re-computation of Discriminator condition
178-
return new RelationalStructuralTypeShaperExpression(StructuralType, newValueBufferExpression, true);
180+
return new RelationalStructuralTypeShaperExpression(StructuralType, newValueBufferExpression, true, materializationCondition: null, Type);
179181
}
180182

181183
/// <inheritdoc />
182184
public override StructuralTypeShaperExpression Update(Expression valueBufferExpression)
183185
=> valueBufferExpression != ValueBufferExpression
184-
? new RelationalStructuralTypeShaperExpression(StructuralType, valueBufferExpression, IsNullable, MaterializationCondition)
186+
? new RelationalStructuralTypeShaperExpression(StructuralType, valueBufferExpression, IsNullable, MaterializationCondition, Type)
187+
: this;
188+
189+
/// <inheritdoc />
190+
public override StructuralTypeShaperExpression MakeClrTypeNullable()
191+
=> Type != Type.MakeNullable()
192+
? new RelationalStructuralTypeShaperExpression(StructuralType, ValueBufferExpression, IsNullable, MaterializationCondition, Type.MakeNullable())
193+
: this;
194+
195+
/// <inheritdoc />
196+
public override StructuralTypeShaperExpression MakeClrTypeNonNullable()
197+
=> Type != Type.UnwrapNullableType()
198+
? new RelationalStructuralTypeShaperExpression(StructuralType, ValueBufferExpression, IsNullable, MaterializationCondition, Type.UnwrapNullableType())
185199
: this;
186200
}

src/EFCore/Query/EntityMaterializerSourceParameters.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ namespace Microsoft.EntityFrameworkCore.Query;
88
/// </summary>
99
/// <param name="StructuralType">The entity or complex type being materialized.</param>
1010
/// <param name="InstanceName">The name of the instance being materialized.</param>
11-
/// <param name="AllowNullable">Whether nullable result is allowed.</param>
11+
/// <param name="ClrType">CLR type of the result.</param>
1212
/// <param name="QueryTrackingBehavior">
1313
/// The query tracking behavior, or <see langword="null" /> if this materialization is not from a query.
1414
/// </param>
1515
public readonly record struct StructuralTypeMaterializerSourceParameters(
1616
ITypeBase StructuralType,
1717
string InstanceName,
18-
bool? AllowNullable,
18+
Type ClrType,
1919
QueryTrackingBehavior? QueryTrackingBehavior);
2020

2121
/// <summary>

0 commit comments

Comments
 (0)