Skip to content

Commit

Permalink
Additional refactoring of Null Semantics:
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
maumar committed Jan 8, 2020
1 parent b0da2e1 commit a5f3e26
Show file tree
Hide file tree
Showing 30 changed files with 2,294 additions and 484 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Query
{
public class FromSqlParameterApplyingExpressionVisitor : ExpressionVisitor
{
private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpressions
= new Dictionary<FromSqlExpression, Expression>(ReferenceEqualityComparer.Instance);

private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly ParameterNameGenerator _parameterNameGenerator;
private readonly IReadOnlyDictionary<string, object> _parametersValues;

public FromSqlParameterApplyingExpressionVisitor(
[NotNull] ISqlExpressionFactory sqlExpressionFactory,
[NotNull] ParameterNameGenerator parameterNameGenerator,
[NotNull] IReadOnlyDictionary<string, object> parametersValues)
{
Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory));
Check.NotNull(parameterNameGenerator, nameof(parameterNameGenerator));
Check.NotNull(parametersValues, nameof(parametersValues));

_sqlExpressionFactory = sqlExpressionFactory;
_parameterNameGenerator = parameterNameGenerator;
_parametersValues = parametersValues;
}

public override Expression Visit(Expression expression)
{
if (expression is FromSqlExpression fromSql)
{
if (!_visitedFromSqlExpressions.TryGetValue(fromSql, out var updatedFromSql))
{
switch (fromSql.Arguments)
{
case ParameterExpression parameterExpression:
var parameterValues = (object[])_parametersValues[parameterExpression.Name];

var subParameters = new List<IRelationalParameter>(parameterValues.Length);
// ReSharper disable once ForCanBeConvertedToForeach
for (var i = 0; i < parameterValues.Length; i++)
{
var parameterName = _parameterNameGenerator.GenerateNext();
if (parameterValues[i] is DbParameter dbParameter)
{
if (string.IsNullOrEmpty(dbParameter.ParameterName))
{
dbParameter.ParameterName = parameterName;
}
else
{
parameterName = dbParameter.ParameterName;
}

subParameters.Add(new RawRelationalParameter(parameterName, dbParameter));
}
else
{
subParameters.Add(
new TypeMappedRelationalParameter(
parameterName,
parameterName,
_sqlExpressionFactory.GetTypeMappingForValue(parameterValues[i]),
parameterValues[i]?.GetType().IsNullableType()));
}
}

updatedFromSql = new FromSqlExpression(
fromSql.Sql,
Expression.Constant(
new CompositeRelationalParameter(
parameterExpression.Name,
subParameters)),
fromSql.Alias);

_visitedFromSqlExpressions[fromSql] = updatedFromSql;
break;

case ConstantExpression constantExpression:
var existingValues = (object[])constantExpression.Value;
var constantValues = new object[existingValues.Length];
for (var i = 0; i < existingValues.Length; i++)
{
var value = existingValues[i];
if (value is DbParameter dbParameter)
{
var parameterName = _parameterNameGenerator.GenerateNext();
if (string.IsNullOrEmpty(dbParameter.ParameterName))
{
dbParameter.ParameterName = parameterName;
}
else
{
parameterName = dbParameter.ParameterName;
}

constantValues[i] = new RawRelationalParameter(parameterName, dbParameter);
}
else
{
constantValues[i] = _sqlExpressionFactory.Constant(
value, _sqlExpressionFactory.GetTypeMappingForValue(value));
}
}

updatedFromSql = new FromSqlExpression(
fromSql.Sql,
Expression.Constant(constantValues, typeof(object[])),
fromSql.Alias);

_visitedFromSqlExpressions[fromSql] = updatedFromSql;
break;
}
}

return updatedFromSql;
}

return base.Visit(expression);
}
}
}
Loading

0 comments on commit a5f3e26

Please sign in to comment.