Skip to content

Commit c591f17

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, - combining NullSemantics with SqlExpressionOptimizer (kept SqlExpressionOptimizer name) - SearchCondition now applies some relevant optimization itself, so that we don't need to run full optimizer afterwards, - merging InExpressionValuesExpandingExpressionVisitor into SqlExpressionOptimizer as well, 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 Resolves #19019
1 parent 7f086a8 commit c591f17

31 files changed

+2513
-1962
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Data.Common;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Linq.Expressions;
9+
using Microsoft.EntityFrameworkCore.Internal;
10+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
11+
using Microsoft.EntityFrameworkCore.Storage;
12+
using Microsoft.EntityFrameworkCore.Storage.Internal;
13+
using Microsoft.EntityFrameworkCore.Utilities;
14+
15+
namespace Microsoft.EntityFrameworkCore.Query
16+
{
17+
public class FromSqlParameterApplyingExpressionVisitor : ExpressionVisitor
18+
{
19+
private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpressions
20+
= new Dictionary<FromSqlExpression, Expression>(ReferenceEqualityComparer.Instance);
21+
22+
private readonly ISqlExpressionFactory _sqlExpressionFactory;
23+
private readonly ParameterNameGenerator _parameterNameGenerator;
24+
private readonly IReadOnlyDictionary<string, object> _parametersValues;
25+
26+
public FromSqlParameterApplyingExpressionVisitor(
27+
[NotNull] ISqlExpressionFactory sqlExpressionFactory,
28+
[NotNull] ParameterNameGenerator parameterNameGenerator,
29+
[NotNull] IReadOnlyDictionary<string, object> parametersValues)
30+
{
31+
Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory));
32+
Check.NotNull(parameterNameGenerator, nameof(parameterNameGenerator));
33+
Check.NotNull(parametersValues, nameof(parametersValues));
34+
35+
_sqlExpressionFactory = sqlExpressionFactory;
36+
_parameterNameGenerator = parameterNameGenerator;
37+
_parametersValues = parametersValues;
38+
}
39+
40+
public override Expression Visit(Expression expression)
41+
{
42+
if (expression is FromSqlExpression fromSql)
43+
{
44+
if (!_visitedFromSqlExpressions.TryGetValue(fromSql, out var updatedFromSql))
45+
{
46+
switch (fromSql.Arguments)
47+
{
48+
case ParameterExpression parameterExpression:
49+
var parameterValues = (object[])_parametersValues[parameterExpression.Name];
50+
51+
var subParameters = new List<IRelationalParameter>(parameterValues.Length);
52+
// ReSharper disable once ForCanBeConvertedToForeach
53+
for (var i = 0; i < parameterValues.Length; i++)
54+
{
55+
var parameterName = _parameterNameGenerator.GenerateNext();
56+
if (parameterValues[i] is DbParameter dbParameter)
57+
{
58+
if (string.IsNullOrEmpty(dbParameter.ParameterName))
59+
{
60+
dbParameter.ParameterName = parameterName;
61+
}
62+
else
63+
{
64+
parameterName = dbParameter.ParameterName;
65+
}
66+
67+
subParameters.Add(new RawRelationalParameter(parameterName, dbParameter));
68+
}
69+
else
70+
{
71+
subParameters.Add(
72+
new TypeMappedRelationalParameter(
73+
parameterName,
74+
parameterName,
75+
_sqlExpressionFactory.GetTypeMappingForValue(parameterValues[i]),
76+
parameterValues[i]?.GetType().IsNullableType()));
77+
}
78+
}
79+
80+
updatedFromSql = new FromSqlExpression(
81+
fromSql.Sql,
82+
Expression.Constant(
83+
new CompositeRelationalParameter(
84+
parameterExpression.Name,
85+
subParameters)),
86+
fromSql.Alias);
87+
88+
_visitedFromSqlExpressions[fromSql] = updatedFromSql;
89+
break;
90+
91+
case ConstantExpression constantExpression:
92+
var existingValues = (object[])constantExpression.Value;
93+
var constantValues = new object[existingValues.Length];
94+
for (var i = 0; i < existingValues.Length; i++)
95+
{
96+
var value = existingValues[i];
97+
if (value is DbParameter dbParameter)
98+
{
99+
var parameterName = _parameterNameGenerator.GenerateNext();
100+
if (string.IsNullOrEmpty(dbParameter.ParameterName))
101+
{
102+
dbParameter.ParameterName = parameterName;
103+
}
104+
else
105+
{
106+
parameterName = dbParameter.ParameterName;
107+
}
108+
109+
constantValues[i] = new RawRelationalParameter(parameterName, dbParameter);
110+
}
111+
else
112+
{
113+
constantValues[i] = _sqlExpressionFactory.Constant(
114+
value, _sqlExpressionFactory.GetTypeMappingForValue(value));
115+
}
116+
}
117+
118+
updatedFromSql = new FromSqlExpression(
119+
fromSql.Sql,
120+
Expression.Constant(constantValues, typeof(object[])),
121+
fromSql.Alias);
122+
123+
_visitedFromSqlExpressions[fromSql] = updatedFromSql;
124+
break;
125+
}
126+
}
127+
128+
return updatedFromSql;
129+
}
130+
131+
return base.Visit(expression);
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)