Skip to content

Commit c2b14b8

Browse files
committed
Additional refactoring of Null Semantics:
- moving NullSemantics visitor after 2nd level cache - we need to know the parameter values to properly handle IN expressions wrt null semantics, - NullSemantics visitor needs to go before SqlExpressionOptimizer and SearchCondition, so those two are also moved after 2nd level cache, - moving optimizations that depend on knowing the nullability to NullSemantics visitor - optimizer now only contains optimizations that also work in 3-value logic, or when we know nulls can't happen, - merging InExpressionValuesExpandingExpressionVisitor int NullSemantics visitor, so that we don't apply the rewrite for UseRelationalNulls, - preventing NulSemantics from performing double visitation when computing non-nullable columns. Resolves #11464 Resolves #15722 Resolves #18338 Resolves #18597 Resolves #18689
1 parent af8ff54 commit c2b14b8

22 files changed

+2013
-1498
lines changed

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

-941
This file was deleted.

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

+1,474
Large diffs are not rendered by default.

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

+20-253
Large diffs are not rendered by default.

src/EFCore.Relational/Query/RelationalParameterBasedQueryTranslationPostprocessor.cs

+8-164
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections;
65
using System.Collections.Generic;
76
using System.Data.Common;
87
using System.Linq.Expressions;
@@ -46,17 +45,19 @@ public virtual (SelectExpression selectExpression, bool canCache) Optimize(
4645
Check.NotNull(parametersValues, nameof(parametersValues));
4746

4847
var canCache = true;
48+
var nullabilityHandlingExpressionVisitor = new NullabilityHandlingExpressionVisitor(
49+
UseRelationalNulls,
50+
Dependencies.SqlExpressionFactory,
51+
parametersValues);
4952

50-
var inExpressionOptimized = new InExpressionValuesExpandingExpressionVisitor(
51-
Dependencies.SqlExpressionFactory, parametersValues).Visit(selectExpression);
52-
53-
if (!ReferenceEquals(selectExpression, inExpressionOptimized))
53+
var nullabilityHandled = nullabilityHandlingExpressionVisitor.Visit(selectExpression);
54+
if (!nullabilityHandlingExpressionVisitor.CanCache)
5455
{
5556
canCache = false;
5657
}
5758

58-
var nullParametersOptimized = new ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor(
59-
Dependencies.SqlExpressionFactory, UseRelationalNulls, parametersValues).Visit(inExpressionOptimized);
59+
var nullParametersOptimized = new SqlExpressionOptimizingExpressionVisitor(
60+
Dependencies.SqlExpressionFactory, UseRelationalNulls, parametersValues).Visit(nullabilityHandled);
6061

6162
var fromSqlParameterOptimized = new FromSqlParameterApplyingExpressionVisitor(
6263
Dependencies.SqlExpressionFactory,
@@ -71,163 +72,6 @@ public virtual (SelectExpression selectExpression, bool canCache) Optimize(
7172
return (selectExpression: (SelectExpression)fromSqlParameterOptimized, canCache);
7273
}
7374

74-
private sealed class ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor : SqlExpressionOptimizingExpressionVisitor
75-
{
76-
private readonly IReadOnlyDictionary<string, object> _parametersValues;
77-
78-
public ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor(
79-
ISqlExpressionFactory sqlExpressionFactory,
80-
bool useRelationalNulls,
81-
IReadOnlyDictionary<string, object> parametersValues)
82-
: base(sqlExpressionFactory, useRelationalNulls)
83-
{
84-
_parametersValues = parametersValues;
85-
}
86-
87-
protected override Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression)
88-
{
89-
var result = base.VisitSqlUnaryExpression(sqlUnaryExpression);
90-
if (result is SqlUnaryExpression newUnaryExpression
91-
&& newUnaryExpression.Operand is SqlParameterExpression parameterOperand)
92-
{
93-
var parameterValue = _parametersValues[parameterOperand.Name];
94-
if (sqlUnaryExpression.OperatorType == ExpressionType.Equal)
95-
{
96-
return SqlExpressionFactory.Constant(parameterValue == null, sqlUnaryExpression.TypeMapping);
97-
}
98-
99-
if (sqlUnaryExpression.OperatorType == ExpressionType.NotEqual)
100-
{
101-
return SqlExpressionFactory.Constant(parameterValue != null, sqlUnaryExpression.TypeMapping);
102-
}
103-
}
104-
105-
return result;
106-
}
107-
108-
protected override Expression VisitSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpression)
109-
{
110-
var result = base.VisitSqlBinaryExpression(sqlBinaryExpression);
111-
if (result is SqlBinaryExpression sqlBinaryResult)
112-
{
113-
var leftNullParameter = sqlBinaryResult.Left is SqlParameterExpression leftParameter
114-
&& _parametersValues[leftParameter.Name] == null;
115-
116-
var rightNullParameter = sqlBinaryResult.Right is SqlParameterExpression rightParameter
117-
&& _parametersValues[rightParameter.Name] == null;
118-
119-
if ((sqlBinaryResult.OperatorType == ExpressionType.Equal || sqlBinaryResult.OperatorType == ExpressionType.NotEqual)
120-
&& (leftNullParameter || rightNullParameter))
121-
{
122-
return SimplifyNullComparisonExpression(
123-
sqlBinaryResult.OperatorType,
124-
sqlBinaryResult.Left,
125-
sqlBinaryResult.Right,
126-
leftNullParameter,
127-
rightNullParameter,
128-
sqlBinaryResult.TypeMapping);
129-
}
130-
}
131-
132-
return result;
133-
}
134-
}
135-
136-
private sealed class InExpressionValuesExpandingExpressionVisitor : ExpressionVisitor
137-
{
138-
private readonly ISqlExpressionFactory _sqlExpressionFactory;
139-
private readonly IReadOnlyDictionary<string, object> _parametersValues;
140-
141-
public InExpressionValuesExpandingExpressionVisitor(
142-
ISqlExpressionFactory sqlExpressionFactory, IReadOnlyDictionary<string, object> parametersValues)
143-
{
144-
_sqlExpressionFactory = sqlExpressionFactory;
145-
_parametersValues = parametersValues;
146-
}
147-
148-
public override Expression Visit(Expression expression)
149-
{
150-
if (expression is InExpression inExpression
151-
&& inExpression.Values != null)
152-
{
153-
var inValues = new List<object>();
154-
var hasNullValue = false;
155-
RelationalTypeMapping typeMapping = null;
156-
157-
switch (inExpression.Values)
158-
{
159-
case SqlConstantExpression sqlConstant:
160-
{
161-
typeMapping = sqlConstant.TypeMapping;
162-
var values = (IEnumerable)sqlConstant.Value;
163-
foreach (var value in values)
164-
{
165-
if (value == null)
166-
{
167-
hasNullValue = true;
168-
continue;
169-
}
170-
171-
inValues.Add(value);
172-
}
173-
174-
break;
175-
}
176-
177-
case SqlParameterExpression sqlParameter:
178-
{
179-
typeMapping = sqlParameter.TypeMapping;
180-
var values = (IEnumerable)_parametersValues[sqlParameter.Name];
181-
foreach (var value in values)
182-
{
183-
if (value == null)
184-
{
185-
hasNullValue = true;
186-
continue;
187-
}
188-
189-
inValues.Add(value);
190-
}
191-
192-
break;
193-
}
194-
}
195-
196-
var updatedInExpression = inValues.Count > 0
197-
? _sqlExpressionFactory.In(
198-
(SqlExpression)Visit(inExpression.Item),
199-
_sqlExpressionFactory.Constant(inValues, typeMapping),
200-
inExpression.IsNegated)
201-
: null;
202-
203-
var nullCheckExpression = hasNullValue
204-
? inExpression.IsNegated
205-
? _sqlExpressionFactory.IsNotNull(inExpression.Item)
206-
: _sqlExpressionFactory.IsNull(inExpression.Item)
207-
: null;
208-
209-
if (updatedInExpression != null
210-
&& nullCheckExpression != null)
211-
{
212-
return inExpression.IsNegated
213-
? _sqlExpressionFactory.AndAlso(updatedInExpression, nullCheckExpression)
214-
: _sqlExpressionFactory.OrElse(updatedInExpression, nullCheckExpression);
215-
}
216-
217-
if (updatedInExpression == null
218-
&& nullCheckExpression == null)
219-
{
220-
return _sqlExpressionFactory.Equal(
221-
_sqlExpressionFactory.Constant(true), _sqlExpressionFactory.Constant(inExpression.IsNegated));
222-
}
223-
224-
return (SqlExpression)updatedInExpression ?? nullCheckExpression;
225-
}
226-
227-
return base.Visit(expression);
228-
}
229-
}
230-
23175
private sealed class FromSqlParameterApplyingExpressionVisitor : ExpressionVisitor
23276
{
23377
private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpressions

src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs

+1-15
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ namespace Microsoft.EntityFrameworkCore.Query
1111
{
1212
public class RelationalQueryTranslationPostprocessor : QueryTranslationPostprocessor
1313
{
14-
private readonly SqlExpressionOptimizingExpressionVisitor _sqlExpressionOptimizingExpressionVisitor;
15-
1614
public RelationalQueryTranslationPostprocessor(
1715
[NotNull] QueryTranslationPostprocessorDependencies dependencies,
1816
[NotNull] RelationalQueryTranslationPostprocessorDependencies relationalDependencies,
@@ -25,8 +23,6 @@ public RelationalQueryTranslationPostprocessor(
2523
RelationalDependencies = relationalDependencies;
2624
UseRelationalNulls = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).UseRelationalNulls;
2725
SqlExpressionFactory = relationalDependencies.SqlExpressionFactory;
28-
_sqlExpressionOptimizingExpressionVisitor
29-
= new SqlExpressionOptimizingExpressionVisitor(SqlExpressionFactory, UseRelationalNulls);
3026
}
3127

3228
protected virtual RelationalQueryTranslationPostprocessorDependencies RelationalDependencies { get; }
@@ -42,22 +38,12 @@ public override Expression Process(Expression query)
4238
query = new CollectionJoinApplyingExpressionVisitor().Visit(query);
4339
query = new TableAliasUniquifyingExpressionVisitor().Visit(query);
4440
query = new CaseWhenFlatteningExpressionVisitor(SqlExpressionFactory).Visit(query);
45-
46-
if (!UseRelationalNulls)
47-
{
48-
query = new NullSemanticsRewritingExpressionVisitor(SqlExpressionFactory).Visit(query);
49-
}
50-
5141
query = OptimizeSqlExpression(query);
5242

5343
return query;
5444
}
5545

5646
protected virtual Expression OptimizeSqlExpression([NotNull] Expression query)
57-
{
58-
Check.NotNull(query, nameof(query));
59-
60-
return _sqlExpressionOptimizingExpressionVisitor.Visit(query);
61-
}
47+
=> query;
6248
}
6349
}

src/EFCore.Relational/Query/SqlExpressions/InExpression.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
5454
{
5555
Check.NotNull(visitor, nameof(visitor));
5656

57-
var newItem = (SqlExpression)visitor.Visit(Item);
57+
var item = (SqlExpression)visitor.Visit(Item);
5858
var subquery = (SelectExpression)visitor.Visit(Subquery);
5959
var values = (SqlExpression)visitor.Visit(Values);
6060

61-
return Update(newItem, values, subquery);
61+
return Update(item, values, subquery);
6262
}
6363

6464
public virtual InExpression Negate() => new InExpression(Item, !IsNegated, Values, Subquery, TypeMapping);

src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessor.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using JetBrains.Annotations;
66
using Microsoft.EntityFrameworkCore.Query;
7+
using Microsoft.EntityFrameworkCore.Query.Internal;
78
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
89
using Microsoft.EntityFrameworkCore.Utilities;
910

@@ -30,7 +31,10 @@ public override (SelectExpression selectExpression, bool canCache) Optimize(
3031
var searchConditionOptimized = (SelectExpression)new SearchConditionConvertingExpressionVisitor(Dependencies.SqlExpressionFactory)
3132
.Visit(optimizedSelectExpression);
3233

33-
return (searchConditionOptimized, canCache);
34+
var optimized = (SelectExpression)new SqlExpressionOptimizingExpressionVisitor(
35+
Dependencies.SqlExpressionFactory, UseRelationalNulls, parametersValues).Visit(searchConditionOptimized);
36+
37+
return (optimized, canCache);
3438
}
3539
}
3640
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Linq.Expressions;
54
using JetBrains.Annotations;
65
using Microsoft.EntityFrameworkCore.Query;
76

@@ -16,13 +15,5 @@ public SqlServerQueryTranslationPostprocessor(
1615
: base(dependencies, relationalDependencies, queryCompilationContext)
1716
{
1817
}
19-
20-
public override Expression Process(Expression query)
21-
{
22-
query = base.Process(query);
23-
query = new SearchConditionConvertingExpressionVisitor(SqlExpressionFactory).Visit(query);
24-
25-
return query;
26-
}
2718
}
2819
}

0 commit comments

Comments
 (0)