From cb6bb7694b5df9700fafa70602efd6963baef347 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Thu, 14 Nov 2019 18:58:42 -0800 Subject: [PATCH] Fix to #18492 - Query: inject SearchConditionConvertingExpressionVisitor into ParameterValueBasedSelectExpressionOptimizer Using DI to create parameter value based postprocessor, which allows for provider-specific optimizations, including SearchConditionConvertingExpressionVisitor Resolves #18492 Resolved #18940 --- ...ntityFrameworkRelationalServicesBuilder.cs | 4 +- ...sedQueryTranslationPostprocessorFactory.cs | 23 +++++ .../Query/Internal/RelationalCommandCache.cs | 12 +-- ...eterBasedQueryTranslationPostprocessor.cs} | 78 +++------------- ...eryTranslationPostprocessorDependencies.cs | 93 +++++++++++++++++++ ...sedQueryTranslationPostprocessorFactory.cs | 18 ++++ ...qlExpressionOptimizingExpressionVisitor.cs | 10 +- ...alShapedQueryCompilingExpressionVisitor.cs | 2 +- ...yCompilingExpressionVisitorDependencies.cs | 32 ++++++- .../Query/SqlExpressions/SelectExpression.cs | 2 +- .../SqlServerServiceCollectionExtensions.cs | 1 + ...meterBasedQueryTranslationPostprocessor.cs | 31 +++++++ ...sedQueryTranslationPostprocessorFactory.cs | 21 +++++ .../ComplexNavigationsQuerySqlServerTest.cs | 2 +- ...indAggregateOperatorsQuerySqlServerTest.cs | 5 +- .../Query/NullSemanticsQuerySqlServerTest.cs | 4 +- .../QueryFilterFuncletizationSqlServerTest.cs | 2 +- 17 files changed, 247 insertions(+), 93 deletions(-) create mode 100644 src/EFCore.Relational/Query/IRelationalParameterBasedQueryTranslationPostprocessorFactory.cs rename src/EFCore.Relational/Query/Internal/{ParameterValueBasedSelectExpressionOptimizer.cs => RelationalParameterBasedQueryTranslationPostprocessor.cs} (81%) create mode 100644 src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs create mode 100644 src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs create mode 100644 src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessor.cs create mode 100644 src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessorFactory.cs diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 6327eaf66fb..6fdc56d9045 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -89,7 +89,7 @@ public static readonly IDictionary RelationalServi { typeof(IRelationalTypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, { typeof(IMethodCallTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, { typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, - + { typeof(IRelationalParameterBasedQueryTranslationPostprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) } }; /// @@ -166,6 +166,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(); ServiceCollectionMap.GetInfrastructure() .AddDependencySingleton() @@ -187,6 +188,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() + .AddDependencySingleton() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() diff --git a/src/EFCore.Relational/Query/IRelationalParameterBasedQueryTranslationPostprocessorFactory.cs b/src/EFCore.Relational/Query/IRelationalParameterBasedQueryTranslationPostprocessorFactory.cs new file mode 100644 index 00000000000..6c5f1a8d7bc --- /dev/null +++ b/src/EFCore.Relational/Query/IRelationalParameterBasedQueryTranslationPostprocessorFactory.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Query +{ + /// + /// + /// A factory for creating instances. + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public interface IRelationalParameterBasedQueryTranslationPostprocessorFactory + { + RelationalParameterBasedQueryTranslationPostprocessor Create(bool useRelationalNulls); + } +} diff --git a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs index af430104185..564daf5b287 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs @@ -24,24 +24,20 @@ private static readonly ConcurrentDictionary _syncObjects private readonly IMemoryCache _memoryCache; private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory; private readonly SelectExpression _selectExpression; - private readonly ParameterValueBasedSelectExpressionOptimizer _parameterValueBasedSelectExpressionOptimizer; + private readonly RelationalParameterBasedQueryTranslationPostprocessor _relationalParameterBasedQueryTranslationPostprocessor; public RelationalCommandCache( IMemoryCache memoryCache, ISqlExpressionFactory sqlExpressionFactory, - IParameterNameGeneratorFactory parameterNameGeneratorFactory, IQuerySqlGeneratorFactory querySqlGeneratorFactory, + IRelationalParameterBasedQueryTranslationPostprocessorFactory relationalParameterBasedQueryTranslationPostprocessorFactory, bool useRelationalNulls, SelectExpression selectExpression) { _memoryCache = memoryCache; _querySqlGeneratorFactory = querySqlGeneratorFactory; _selectExpression = selectExpression; - - _parameterValueBasedSelectExpressionOptimizer = new ParameterValueBasedSelectExpressionOptimizer( - sqlExpressionFactory, - parameterNameGeneratorFactory, - useRelationalNulls); + _relationalParameterBasedQueryTranslationPostprocessor = relationalParameterBasedQueryTranslationPostprocessorFactory.Create(useRelationalNulls); } public virtual IRelationalCommand GetRelationalCommand(IReadOnlyDictionary parameters) @@ -59,7 +55,7 @@ public virtual IRelationalCommand GetRelationalCommand(IReadOnlyDictionary - public class ParameterValueBasedSelectExpressionOptimizer + public class RelationalParameterBasedQueryTranslationPostprocessor { - private readonly ISqlExpressionFactory _sqlExpressionFactory; - private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; - private readonly bool _useRelationalNulls; - - public ParameterValueBasedSelectExpressionOptimizer( - ISqlExpressionFactory sqlExpressionFactory, - IParameterNameGeneratorFactory parameterNameGeneratorFactory, + public RelationalParameterBasedQueryTranslationPostprocessor( + RelationalParameterBasedQueryTranslationPostprocessorDependencies dependencies, bool useRelationalNulls) { - _sqlExpressionFactory = sqlExpressionFactory; - _parameterNameGeneratorFactory = parameterNameGeneratorFactory; - _useRelationalNulls = useRelationalNulls; + Dependencies = dependencies; + UseRelationalNulls = useRelationalNulls; } + protected virtual RelationalParameterBasedQueryTranslationPostprocessorDependencies Dependencies { get; } + + protected virtual bool UseRelationalNulls { get; } + public virtual (SelectExpression selectExpression, bool canCache) Optimize( SelectExpression selectExpression, IReadOnlyDictionary parametersValues) { var canCache = true; var inExpressionOptimized = new InExpressionValuesExpandingExpressionVisitor( - _sqlExpressionFactory, parametersValues).Visit(selectExpression); + Dependencies.SqlExpressionFactory, parametersValues).Visit(selectExpression); if (!ReferenceEquals(selectExpression, inExpressionOptimized)) { @@ -50,11 +47,11 @@ public virtual (SelectExpression selectExpression, bool canCache) Optimize( } var nullParametersOptimized = new ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor( - _sqlExpressionFactory, _useRelationalNulls, parametersValues).Visit(inExpressionOptimized); + Dependencies.SqlExpressionFactory, UseRelationalNulls, parametersValues).Visit(inExpressionOptimized); var fromSqlParameterOptimized = new FromSqlParameterApplyingExpressionVisitor( - _sqlExpressionFactory, - _parameterNameGeneratorFactory.Create(), + Dependencies.SqlExpressionFactory, + Dependencies.ParameterNameGeneratorFactory.Create(), parametersValues).Visit(nullParametersOptimized); if (!ReferenceEquals(nullParametersOptimized, fromSqlParameterOptimized)) @@ -78,53 +75,6 @@ public ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor( _parametersValues = parametersValues; } - protected override Expression VisitExtension(Expression extensionExpression) - { - // workaround for issue #18492 - var newExpression = base.VisitExtension(extensionExpression); - if (newExpression is SelectExpression newSelectExpression) - { - var changed = false; - var newPredicate = newSelectExpression.Predicate; - var newHaving = newSelectExpression.Having; - if (newSelectExpression.Predicate is SqlConstantExpression predicateConstantExpression - && predicateConstantExpression.Value is bool predicateBoolValue - && !predicateBoolValue) - { - changed = true; - newPredicate = SqlExpressionFactory.Equal( - predicateConstantExpression, - SqlExpressionFactory.Constant(true, predicateConstantExpression.TypeMapping)); - } - - if (newSelectExpression.Having is SqlConstantExpression havingConstantExpression - && havingConstantExpression.Value is bool havingBoolValue - && !havingBoolValue) - { - changed = true; - newHaving = SqlExpressionFactory.Equal( - havingConstantExpression, - SqlExpressionFactory.Constant(true, havingConstantExpression.TypeMapping)); - } - - return changed - ? newSelectExpression.Update( - newSelectExpression.Projection.ToList(), - newSelectExpression.Tables.ToList(), - newPredicate, - newSelectExpression.GroupBy.ToList(), - newHaving, - newSelectExpression.Orderings.ToList(), - newSelectExpression.Limit, - newSelectExpression.Offset, - newSelectExpression.IsDistinct, - newSelectExpression.Alias) - : newSelectExpression; - } - - return newExpression; - } - protected override Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression) { var result = base.VisitSqlUnaryExpression(sqlUnaryExpression); diff --git a/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs new file mode 100644 index 00000000000..e74536b6d6d --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorDependencies.cs @@ -0,0 +1,93 @@ +// 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 JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + /// + /// + /// Service dependencies parameter class for + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// Do not construct instances of this class directly from either provider or application code as the + /// constructor signature may change as new dependencies are added. Instead, use this type in + /// your constructor so that an instance will be created and injected automatically by the + /// dependency injection container. To create an instance with some dependent services replaced, + /// first resolve the object from the dependency injection container, then replace selected + /// services using the 'With...' methods. Do not call the constructor at any point in this process. + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public sealed class RelationalParameterBasedQueryTranslationPostprocessorDependencies + { + /// + /// + /// Creates the service dependencies parameter object for a . + /// + /// + /// Do not call this constructor directly from either provider or application code as it may change + /// as new dependencies are added. Instead, use this type in your constructor so that an instance + /// will be created and injected automatically by the dependency injection container. To create + /// an instance with some dependent services replaced, first resolve the object from the dependency + /// injection container, then replace selected services using the 'With...' methods. Do not call + /// the constructor at any point in this process. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + /// + [EntityFrameworkInternal] + public RelationalParameterBasedQueryTranslationPostprocessorDependencies( + [NotNull] ISqlExpressionFactory sqlExpressionFactory, + [NotNull] IParameterNameGeneratorFactory parameterNameGeneratorFactory) + { + Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + Check.NotNull(parameterNameGeneratorFactory, nameof(parameterNameGeneratorFactory)); + + SqlExpressionFactory = sqlExpressionFactory; + ParameterNameGeneratorFactory = parameterNameGeneratorFactory; + } + + /// + /// Sql expression factory. + /// + public ISqlExpressionFactory SqlExpressionFactory { get; } + + /// + /// Parameter name generator factory. + /// + public IParameterNameGeneratorFactory ParameterNameGeneratorFactory { get; } + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public RelationalParameterBasedQueryTranslationPostprocessorDependencies With(ISqlExpressionFactory sqlExpressionFactory) + => new RelationalParameterBasedQueryTranslationPostprocessorDependencies(sqlExpressionFactory, ParameterNameGeneratorFactory); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public RelationalParameterBasedQueryTranslationPostprocessorDependencies With(IParameterNameGeneratorFactory parameterNameGeneratorFactory) + => new RelationalParameterBasedQueryTranslationPostprocessorDependencies(SqlExpressionFactory, parameterNameGeneratorFactory); + } +} diff --git a/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs new file mode 100644 index 00000000000..2a2ba7a32ce --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedQueryTranslationPostprocessorFactory.cs @@ -0,0 +1,18 @@ +// 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. + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + public class RelationalParameterBasedQueryTranslationPostprocessorFactory : IRelationalParameterBasedQueryTranslationPostprocessorFactory + { + private readonly RelationalParameterBasedQueryTranslationPostprocessorDependencies _dependencies; + + public RelationalParameterBasedQueryTranslationPostprocessorFactory(RelationalParameterBasedQueryTranslationPostprocessorDependencies dependencies) + { + _dependencies = dependencies; + } + + public virtual RelationalParameterBasedQueryTranslationPostprocessor Create(bool useRelationalNulls) + => new RelationalParameterBasedQueryTranslationPostprocessor(_dependencies, useRelationalNulls); + } +} diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs index 389fa135338..5e72b69104c 100644 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs @@ -413,13 +413,9 @@ private SqlExpression SimplifyBoolConstantComparisonExpression( { if (leftBoolConstant != null && rightBoolConstant != null) { - // potential optimization: - // we can't do it on SqlServer because it reverts search conditions back to values - // and we run this visitor after search condition visitor - //return operatorType == ExpressionType.Equal - // ? SqlExpressionFactory.Constant((bool)leftBoolConstant.Value == (bool)rightBoolConstant.Value, typeMapping) - // : SqlExpressionFactory.Constant((bool)leftBoolConstant.Value != (bool)rightBoolConstant.Value, typeMapping); - return SqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping); + return operatorType == ExpressionType.Equal + ? SqlExpressionFactory.Constant((bool)leftBoolConstant.Value == (bool)rightBoolConstant.Value, typeMapping) + : SqlExpressionFactory.Constant((bool)leftBoolConstant.Value != (bool)rightBoolConstant.Value, typeMapping); } if (rightBoolConstant != null diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index 0ff1e0f4339..4275e9db773 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -75,8 +75,8 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s var relationalCommandCache = new RelationalCommandCache( Dependencies.MemoryCache, RelationalDependencies.SqlExpressionFactory, - RelationalDependencies.ParameterNameGeneratorFactory, RelationalDependencies.QuerySqlGeneratorFactory, + RelationalDependencies.RelationalParameterBasedQueryTranslationPostprocessorFactory, _useRelationalNulls, selectExpression); diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs index 28958a1d180..1ca17399f3a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs @@ -57,15 +57,18 @@ public sealed class RelationalShapedQueryCompilingExpressionVisitorDependencies public RelationalShapedQueryCompilingExpressionVisitorDependencies( [NotNull] IQuerySqlGeneratorFactory querySqlGeneratorFactory, [NotNull] ISqlExpressionFactory sqlExpressionFactory, - [NotNull] IParameterNameGeneratorFactory parameterNameGeneratorFactory) + [NotNull] IParameterNameGeneratorFactory parameterNameGeneratorFactory, + [NotNull] IRelationalParameterBasedQueryTranslationPostprocessorFactory relationalParameterBasedQueryTranslationPostprocessorFactory) { Check.NotNull(querySqlGeneratorFactory, nameof(querySqlGeneratorFactory)); Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); Check.NotNull(parameterNameGeneratorFactory, nameof(parameterNameGeneratorFactory)); + Check.NotNull(relationalParameterBasedQueryTranslationPostprocessorFactory, nameof(relationalParameterBasedQueryTranslationPostprocessorFactory)); QuerySqlGeneratorFactory = querySqlGeneratorFactory; SqlExpressionFactory = sqlExpressionFactory; ParameterNameGeneratorFactory = parameterNameGeneratorFactory; + RelationalParameterBasedQueryTranslationPostprocessorFactory = relationalParameterBasedQueryTranslationPostprocessorFactory; } /// @@ -83,6 +86,11 @@ public RelationalShapedQueryCompilingExpressionVisitorDependencies( /// public IParameterNameGeneratorFactory ParameterNameGeneratorFactory { get; } + /// + /// The query postprocessor based on parameter values. + /// + public IRelationalParameterBasedQueryTranslationPostprocessorFactory RelationalParameterBasedQueryTranslationPostprocessorFactory { get; } + /// /// Clones this dependency parameter object with one service replaced. /// @@ -93,7 +101,8 @@ public RelationalShapedQueryCompilingExpressionVisitorDependencies With( => new RelationalShapedQueryCompilingExpressionVisitorDependencies( querySqlGeneratorFactory, SqlExpressionFactory, - ParameterNameGeneratorFactory); + ParameterNameGeneratorFactory, + RelationalParameterBasedQueryTranslationPostprocessorFactory); /// /// Clones this dependency parameter object with one service replaced. @@ -104,7 +113,8 @@ public RelationalShapedQueryCompilingExpressionVisitorDependencies With([NotNull => new RelationalShapedQueryCompilingExpressionVisitorDependencies( QuerySqlGeneratorFactory, sqlExpressionFactory, - ParameterNameGeneratorFactory); + ParameterNameGeneratorFactory, + RelationalParameterBasedQueryTranslationPostprocessorFactory); /// /// Clones this dependency parameter object with one service replaced. @@ -116,6 +126,20 @@ public RelationalShapedQueryCompilingExpressionVisitorDependencies With( => new RelationalShapedQueryCompilingExpressionVisitorDependencies( QuerySqlGeneratorFactory, SqlExpressionFactory, - parameterNameGeneratorFactory); + parameterNameGeneratorFactory, + RelationalParameterBasedQueryTranslationPostprocessorFactory); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public RelationalShapedQueryCompilingExpressionVisitorDependencies With( + [NotNull] IRelationalParameterBasedQueryTranslationPostprocessorFactory relationalParameterBasedQueryTranslationPostprocessorFactory) + => new RelationalShapedQueryCompilingExpressionVisitorDependencies( + QuerySqlGeneratorFactory, + SqlExpressionFactory, + ParameterNameGeneratorFactory, + relationalParameterBasedQueryTranslationPostprocessorFactory); } } diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 215ada2b24e..ba0f818809b 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1661,7 +1661,7 @@ private bool Equals(SelectExpression selectExpression) } if (!(Having == null && selectExpression.Having == null - || Having != null && Predicate.Equals(selectExpression.Having))) + || Having != null && Having.Equals(selectExpression.Having))) { return false; } diff --git a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs index cadd8bcbbbf..45bf940851f 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs @@ -76,6 +76,7 @@ public static IServiceCollection AddEntityFrameworkSqlServer([NotNull] this ISer .TryAdd() .TryAdd() .TryAdd() + .TryAdd() .TryAddProviderSpecificServices( b => b .TryAddSingleton() diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessor.cs new file mode 100644 index 00000000000..236f469bdbd --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessor.cs @@ -0,0 +1,31 @@ +// 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.Collections.Generic; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal +{ + public class SqlServerParameterBasedQueryTranslationPostprocessor : RelationalParameterBasedQueryTranslationPostprocessor + { + public SqlServerParameterBasedQueryTranslationPostprocessor( + RelationalParameterBasedQueryTranslationPostprocessorDependencies dependencies, + bool useRelationalNulls) + : base(dependencies, useRelationalNulls) + { + } + + public override (SelectExpression selectExpression, bool canCache) Optimize( + SelectExpression selectExpression, + IReadOnlyDictionary parametersValues) + { + var (optimizedSelectExpression, canCache) = base.Optimize(selectExpression, parametersValues); + + var searchConditionOptimized = (SelectExpression)new SearchConditionConvertingExpressionVisitor(Dependencies.SqlExpressionFactory) + .Visit(optimizedSelectExpression); + + return (searchConditionOptimized, canCache); + } + } +} diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessorFactory.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessorFactory.cs new file mode 100644 index 00000000000..c25145dc7fd --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerParameterBasedQueryTranslationPostprocessorFactory.cs @@ -0,0 +1,21 @@ +// 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 Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.Internal; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal +{ + public class SqlServerParameterBasedQueryTranslationPostprocessorFactory : IRelationalParameterBasedQueryTranslationPostprocessorFactory + { + private readonly RelationalParameterBasedQueryTranslationPostprocessorDependencies _dependencies; + + public SqlServerParameterBasedQueryTranslationPostprocessorFactory(RelationalParameterBasedQueryTranslationPostprocessorDependencies dependencies) + { + _dependencies = dependencies; + } + + public virtual RelationalParameterBasedQueryTranslationPostprocessor Create(bool useRelationalNulls) + => new SqlServerParameterBasedQueryTranslationPostprocessor(_dependencies, useRelationalNulls); + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index a8c1cf62224..736380f1740 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -2875,7 +2875,7 @@ public override async Task Join_condition_optimizations_applied_correctly_when_a AssertSql( @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] FROM [LevelOne] AS [l] -INNER JOIN [LevelTwo] AS [l0] ON (([l].[OneToMany_Optional_Self_Inverse1Id] = [l0].[Level1_Optional_Id]) OR ([l].[OneToMany_Optional_Self_Inverse1Id] IS NULL AND [l0].[Level1_Optional_Id] IS NULL)) AND (CAST(1 AS bit) = CAST(1 AS bit))"); +INNER JOIN [LevelTwo] AS [l0] ON ([l].[OneToMany_Optional_Self_Inverse1Id] = [l0].[Level1_Optional_Id]) OR ([l].[OneToMany_Optional_Self_Inverse1Id] IS NULL AND [l0].[Level1_Optional_Id] IS NULL)"); } public override async Task Join_condition_optimizations_applied_correctly_when_anonymous_type_with_multiple_properties(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs index 492b324ab54..6fba71a1115 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs @@ -941,7 +941,7 @@ public override async Task Contains_with_local_collection_empty_closure(bool asy AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE CAST(1 AS bit) = CAST(0 AS bit)"); +WHERE CAST(0 AS bit) = CAST(1 AS bit)"); } public override async Task Contains_with_local_collection_empty_inline(bool async) @@ -950,8 +950,7 @@ public override async Task Contains_with_local_collection_empty_inline(bool asyn AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] -FROM [Customers] AS [c] -WHERE CAST(1 AS bit) = CAST(1 AS bit)"); +FROM [Customers] AS [c]"); } public override async Task Contains_top_level(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index ca9ff5b7aa3..4180492bea1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -1436,10 +1436,10 @@ public override void Null_semantics_join_with_composite_key() AssertSql( @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC], [e0].[Id], [e0].[BoolA], [e0].[BoolB], [e0].[BoolC], [e0].[IntA], [e0].[IntB], [e0].[IntC], [e0].[NullableBoolA], [e0].[NullableBoolB], [e0].[NullableBoolC], [e0].[NullableIntA], [e0].[NullableIntB], [e0].[NullableIntC], [e0].[NullableStringA], [e0].[NullableStringB], [e0].[NullableStringC], [e0].[StringA], [e0].[StringB], [e0].[StringC] FROM [Entities1] AS [e] -INNER JOIN [Entities2] AS [e0] ON ((([e].[NullableStringA] = [e0].[NullableStringB]) OR ([e].[NullableStringA] IS NULL AND [e0].[NullableStringB] IS NULL)) AND (CASE +INNER JOIN [Entities2] AS [e0] ON (([e].[NullableStringA] = [e0].[NullableStringB]) OR ([e].[NullableStringA] IS NULL AND [e0].[NullableStringB] IS NULL)) AND (CASE WHEN (([e].[NullableStringB] <> [e].[NullableStringC]) OR ([e].[NullableStringB] IS NULL OR [e].[NullableStringC] IS NULL)) AND ([e].[NullableStringB] IS NOT NULL OR [e].[NullableStringC] IS NOT NULL) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END = COALESCE([e0].[NullableBoolA], [e0].[BoolC]))) AND (CAST(1 AS bit) = CAST(1 AS bit))"); +END = COALESCE([e0].[NullableBoolA], [e0].[BoolC]))"); } public override void Null_semantics_contains() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs index 3d941398ec2..85e5315a9d6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs @@ -86,7 +86,7 @@ public override void DbContext_list_is_parameterized() AssertSql( @"SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l] -WHERE CAST(1 AS bit) = CAST(0 AS bit)", +WHERE CAST(0 AS bit) = CAST(1 AS bit)", // @"SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l]