From 540b4effeb563c3c9b69147bc6e7aade67f11f16 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 11 Feb 2020 18:06:48 +0100 Subject: [PATCH] Rewrite string equality with LIKE on SQL Server Since on SQL Server the equality operator ignores trailing whitespace, we can use LIKE when comparing to constants. Fixes #19402 Note: this is WIP, two tests are failing. --- ...lityBasedSqlProcessingExpressionVisitor.cs | 12 +- .../SqlServerServiceCollectionExtensions.cs | 1 + .../SqlServerQueryTranslationPostprocessor.cs | 29 +++++ ...verQueryTranslationPostprocessorFactory.cs | 47 +++++++ .../SqlServerStringMethodTranslator.cs | 2 +- ...ringEqualityConvertingExpressionVisitor.cs | 73 +++++++++++ .../Query/FunkyDataQueryTestBase.cs | 28 +++++ .../Query/NorthwindFunctionsQueryTestBase.cs | 30 +++++ .../FunkyDataModel/FunkyDataData.cs | 16 +++ .../ComplexNavigationsQuerySqlServerTest.cs | 2 +- .../Query/FunkyDataQuerySqlServerTest.cs | 115 ++++++++++++------ .../NorthwindFunctionsQuerySqlServerTest.cs | 58 +++++++-- ...thwindKeylessEntitiesQuerySqlServerTest.cs | 2 +- ...orthwindMiscellaneousQuerySqlServerTest.cs | 6 +- ...NorthwindQueryFiltersQuerySqlServerTest.cs | 32 ++--- .../Query/NullSemanticsQuerySqlServerTest.cs | 2 +- 16 files changed, 383 insertions(+), 72 deletions(-) create mode 100644 src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs create mode 100644 src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessorFactory.cs create mode 100644 src/EFCore.SqlServer/Query/Internal/StringEqualityConvertingExpressionVisitor.cs diff --git a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs index 0d7f97229a0..8ffcfcd33e4 100644 --- a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs @@ -147,7 +147,7 @@ protected override Expression VisitCase(CaseExpression caseExpression) // if there are no whenClauses left (e.g. their tests evaluated to false): // - if there is Else block, return it - // - if there is no Else block, return null + // - if there is no Else block, return null if (whenClauses.Count == 0) { return elseResult == null @@ -392,8 +392,16 @@ protected override Expression VisitLike(LikeExpression likeExpression) var (match, matchNullable) = VisitInternal(likeExpression.Match); var (pattern, patternNullable) = VisitInternal(likeExpression.Pattern); var (escapeChar, escapeCharNullable) = VisitInternal(likeExpression.EscapeChar); - _nullable = matchNullable || patternNullable || escapeCharNullable; + if (match is SqlConstantExpression matchConstant && matchConstant.Value is null + || pattern is SqlConstantExpression patternConstant && patternConstant.Value is null + || escapeChar is SqlConstantExpression escapeCharConstant && escapeCharConstant.Value is null) + { + _nullable = false; + return SqlExpressionFactory.Constant(false, likeExpression.TypeMapping); + } + + _nullable = matchNullable || patternNullable || escapeCharNullable; return likeExpression.Update(match, pattern, escapeChar); } diff --git a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs index 10760758cc8..2e30d8a4c00 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs @@ -75,6 +75,7 @@ public static IServiceCollection AddEntityFrameworkSqlServer([NotNull] this ISer .TryAdd() .TryAdd() .TryAdd() + .TryAdd() .TryAdd() .TryAddProviderSpecificServices( b => b diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs new file mode 100644 index 00000000000..d07348e9977 --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs @@ -0,0 +1,29 @@ +// 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.Linq.Expressions; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal +{ + public class SqlServerQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor + { + public SqlServerQueryTranslationPostprocessor( + [NotNull] QueryTranslationPostprocessorDependencies dependencies, + [NotNull] RelationalQueryTranslationPostprocessorDependencies relationalDependencies, + [NotNull] QueryCompilationContext queryCompilationContext) + : base(dependencies, relationalDependencies, queryCompilationContext) + { + } + + public override Expression Process(Expression query) + { + query = new StringEqualityConvertingExpressionVisitor(RelationalDependencies.SqlExpressionFactory).Visit(query); + + query = base.Process(query); + + return query; + } + } +} diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessorFactory.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessorFactory.cs new file mode 100644 index 00000000000..bcf96e23df9 --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessorFactory.cs @@ -0,0 +1,47 @@ +// 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.Query; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal +{ + /// + /// + /// 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. + /// + /// + /// 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 class SqlServerQueryTranslationPostprocessorFactory : IQueryTranslationPostprocessorFactory + { + private readonly QueryTranslationPostprocessorDependencies _dependencies; + private readonly RelationalQueryTranslationPostprocessorDependencies _relationalDependencies; + + public SqlServerQueryTranslationPostprocessorFactory( + [NotNull] QueryTranslationPostprocessorDependencies dependencies, + [NotNull] RelationalQueryTranslationPostprocessorDependencies relationalDependencies) + { + _dependencies = dependencies; + _relationalDependencies = relationalDependencies; + } + + public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext) + { + Check.NotNull(queryCompilationContext, nameof(queryCompilationContext)); + + return new SqlServerQueryTranslationPostprocessor( + _dependencies, + _relationalDependencies, + queryCompilationContext); + } + } +} diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs index 33c4da6d355..b10bd83330e 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs @@ -318,7 +318,7 @@ private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpress _sqlExpressionFactory.Constant(null, stringTypeMapping)); } - return constantString.Any(c => IsLikeWildChar(c)) + return constantString.Any(IsLikeWildChar) ? _sqlExpressionFactory.Like( instance, _sqlExpressionFactory.Constant( diff --git a/src/EFCore.SqlServer/Query/Internal/StringEqualityConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/StringEqualityConvertingExpressionVisitor.cs new file mode 100644 index 00000000000..a87576cf595 --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/StringEqualityConvertingExpressionVisitor.cs @@ -0,0 +1,73 @@ +// 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.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal +{ + /// + /// In SQL Server, string equality ignores trailing whitespace. This replaces equality with constant strings to + /// LIKE, which does an exact comparison and still utilizes indexes. + /// + public class StringEqualityConvertingExpressionVisitor : ExpressionVisitor + { + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + public StringEqualityConvertingExpressionVisitor(ISqlExpressionFactory sqlExpressionFactory) + => _sqlExpressionFactory = sqlExpressionFactory; + + protected override Expression VisitExtension(Expression extensionExpression) + { + Check.NotNull(extensionExpression, nameof(extensionExpression)); + + if (extensionExpression is ShapedQueryExpression shapedQueryExpression) + { + return shapedQueryExpression.Update( + Visit(shapedQueryExpression.QueryExpression), + shapedQueryExpression.ShaperExpression); + } + + if (extensionExpression is SqlBinaryExpression binaryExpression + && (binaryExpression.OperatorType == ExpressionType.Equal + || binaryExpression.OperatorType == ExpressionType.NotEqual) + && binaryExpression.Left.TypeMapping is StringTypeMapping + && binaryExpression.Right.TypeMapping is StringTypeMapping + // Specifically avoid rewriting if both sides are constant (e.g. N'' = N'') - this gets handled + // elsewhere and rewriting here interferes + && (!(binaryExpression.Left is SqlConstantExpression) + || !(binaryExpression.Right is SqlConstantExpression))) + { + var likeExpression = + TransformToLikeIfPossible(binaryExpression.Left, binaryExpression.Right) ?? + TransformToLikeIfPossible(binaryExpression.Right, binaryExpression.Left); + + if (likeExpression != null) + { + return binaryExpression.OperatorType == ExpressionType.Equal + ? likeExpression + : (SqlExpression)_sqlExpressionFactory.Not(likeExpression); + } + } + + return base.VisitExtension(extensionExpression); + } + + private LikeExpression TransformToLikeIfPossible(SqlExpression left, SqlExpression right) + => right is SqlConstantExpression constantExpression + && constantExpression.Value is string value + && ( + value.Length == 0 + || value.All(c => !IsLikeWildChar(c)) + && char.IsWhiteSpace(value[^1])) + ? _sqlExpressionFactory.Like(left, right) + : null; + + // See https://docs.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql + private bool IsLikeWildChar(char c) => c == '%' || c == '_' || c == '['; + } +} diff --git a/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs index bec299325bf..e3656e294c9 100644 --- a/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs @@ -574,6 +574,34 @@ public virtual Task String_ends_with_not_equals_nullable_column(bool async) }); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_equals_with_trailing_whitespace_constant(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.LastName == "WithoutTrailing ").Select(c => c.FirstName)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_equals_with_trailing_whitespace_column(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.FirstName == "WithTrailing").Select(c => c.FirstName)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_equals_with_like_wildcard(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.FirstName == "With%Wildcard").Select(c => c.FirstName)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_equals_with_trailing_whitespace_and_like_wildcard(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.FirstName == "WithTrailingAnd%Wildcard").Select(c => c.FirstName)); + protected FunkyDataContext CreateContext() => Fixture.CreateContext(); protected virtual void ClearLog() diff --git a/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs index 44c13919cfc..d8a70db6f0e 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs @@ -77,6 +77,16 @@ public virtual Task String_StartsWith_MethodCall(bool async) entryCount: 12); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_StartsWith_Parameter_with_whitespace(bool async) + { + var pattern = "hello"; + return AssertQuery( + async, + ss => ss.Set().Where(c => c.ContactName.StartsWith(pattern))); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task String_EndsWith_Literal(bool async) @@ -117,6 +127,16 @@ public virtual Task String_EndsWith_MethodCall(bool async) entryCount: 1); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_EndsWith_Parameter_with_whitespace(bool async) + { + var pattern = "hello"; + return AssertQuery( + async, + ss => ss.Set().Where(c => c.ContactName.EndsWith(pattern))); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task String_Contains_Literal(bool async) @@ -157,6 +177,16 @@ public virtual Task String_Contains_MethodCall(bool async) entryCount: 19); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_Contains_Parameter_with_whitespace(bool async) + { + var pattern = " "; + return AssertQuery( + async, + ss => ss.Set().Where(c => c.ContactName.Contains(pattern))); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task String_Compare_simple_zero(bool async) diff --git a/test/EFCore.Specification.Tests/TestModels/FunkyDataModel/FunkyDataData.cs b/test/EFCore.Specification.Tests/TestModels/FunkyDataModel/FunkyDataData.cs index 0bf22ce066d..890ccd62448 100644 --- a/test/EFCore.Specification.Tests/TestModels/FunkyDataModel/FunkyDataData.cs +++ b/test/EFCore.Specification.Tests/TestModels/FunkyDataModel/FunkyDataData.cs @@ -157,6 +157,22 @@ public static IReadOnlyList CreateFunkyCustomers() FirstName = "B[[a^r", LastName = "B[[", NullableBool = true + }, + new FunkyCustomer + { + Id = 20, + FirstName = "WithTrailing ", + LastName = "WithoutTrailing" + }, + new FunkyCustomer + { + Id = 21, + FirstName = "With%Wildcard" + }, + new FunkyCustomer + { + Id = 22, + FirstName = "WithTrailingAnd%Wildcard " } }; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 589f8cc15e1..54c3f45b6b6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -326,7 +326,7 @@ public override async Task Method_call_on_optional_navigation_translates_to_null @"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] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] -WHERE ([l0].[Name] = N'') OR ([l0].[Name] IS NOT NULL AND (LEFT([l0].[Name], LEN([l0].[Name])) = [l0].[Name]))"); +WHERE ([l0].[Name] LIKE N'') OR ([l0].[Name] IS NOT NULL AND (LEFT([l0].[Name], LEN([l0].[Name])) = [l0].[Name]))"); } public override async Task Optional_navigation_inside_method_call_translated_to_join_keeps_original_nullability(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs index efdaafc3de3..effa217d13a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs @@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore.TestModels.FunkyDataModel; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit.Abstractions; +using Xunit.Sdk; +using Assert = Xunit.Assert; namespace Microsoft.EntityFrameworkCore.Query { @@ -65,13 +67,13 @@ public override async Task String_contains_on_argument_with_wildcard_parameter(b SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm1_0 = N'') OR (CHARINDEX(@__prm1_0, [f].[FirstName]) > 0)", +WHERE (@__prm1_0 LIKE N'') OR (CHARINDEX(@__prm1_0, [f].[FirstName]) > 0)", // @"@__prm2_0='a_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm2_0 = N'') OR (CHARINDEX(@__prm2_0, [f].[FirstName]) > 0)", +WHERE (@__prm2_0 LIKE N'') OR (CHARINDEX(@__prm2_0, [f].[FirstName]) > 0)", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -81,25 +83,25 @@ WHERE CHARINDEX(NULL, [f].[FirstName]) > 0", SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm4_0 = N'') OR (CHARINDEX(@__prm4_0, [f].[FirstName]) > 0)", +WHERE (@__prm4_0 LIKE N'') OR (CHARINDEX(@__prm4_0, [f].[FirstName]) > 0)", // @"@__prm5_0='_Ba_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm5_0 = N'') OR (CHARINDEX(@__prm5_0, [f].[FirstName]) > 0)", +WHERE (@__prm5_0 LIKE N'') OR (CHARINDEX(@__prm5_0, [f].[FirstName]) > 0)", // @"@__prm6_0='%B%a%r' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE NOT ((@__prm6_0 = N'') OR (CHARINDEX(@__prm6_0, [f].[FirstName]) > 0))", +WHERE NOT ((@__prm6_0 LIKE N'') OR (CHARINDEX(@__prm6_0, [f].[FirstName]) > 0))", // @"@__prm7_0='' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE NOT ((@__prm7_0 = N'') OR (CHARINDEX(@__prm7_0, [f].[FirstName]) > 0))", +WHERE NOT ((@__prm7_0 LIKE N'') OR (CHARINDEX(@__prm7_0, [f].[FirstName]) > 0))", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -114,7 +116,7 @@ public override async Task String_contains_on_argument_with_wildcard_column(bool @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE ([f0].[LastName] = N'') OR (CHARINDEX([f0].[LastName], [f].[FirstName]) > 0)"); +WHERE ([f0].[LastName] LIKE N'') OR (CHARINDEX([f0].[LastName], [f].[FirstName]) > 0)"); } public override async Task String_contains_on_argument_with_wildcard_column_negated(bool async) @@ -125,7 +127,7 @@ public override async Task String_contains_on_argument_with_wildcard_column_nega @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE NOT ((([f0].[LastName] = N'') AND [f0].[LastName] IS NOT NULL) OR (CHARINDEX([f0].[LastName], [f].[FirstName]) > 0))"); +WHERE NOT (([f0].[LastName] LIKE N'') OR (CHARINDEX([f0].[LastName], [f].[FirstName]) > 0))"); } public override async Task String_starts_with_on_argument_with_wildcard_constant(bool async) @@ -174,13 +176,13 @@ public override async Task String_starts_with_on_argument_with_wildcard_paramete SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm1_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0))", +WHERE (@__prm1_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0))", // @"@__prm2_0='a_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm2_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0))", +WHERE (@__prm2_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0))", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -190,25 +192,25 @@ FROM [FunkyCustomers] AS [f] SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm4_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm4_0)) = @__prm4_0))", +WHERE (@__prm4_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm4_0)) = @__prm4_0))", // @"@__prm5_0='_Ba_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm5_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm5_0)) = @__prm5_0))", +WHERE (@__prm5_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm5_0)) = @__prm5_0))", // @"@__prm6_0='%B%a%r' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm6_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0))", +WHERE NOT (@__prm6_0 LIKE N'') AND ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0))", // @"@__prm7_0='' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm7_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0))", +WHERE NOT (@__prm7_0 LIKE N'') AND ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0))", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -236,23 +238,23 @@ WHERE [f].[FirstName] IS NOT NULL AND ([f].[FirstName] LIKE N'B\[\[a^%' ESCAPE N SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE (@__prm1_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0))", +WHERE (@__prm1_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0))", // @"@__prm2_0='B[' (Size = 4000) SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE (@__prm2_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0))", +WHERE (@__prm2_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0))", // @"@__prm3_0='B[[a^' (Size = 4000) SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE (@__prm3_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm3_0)) = @__prm3_0))", +WHERE (@__prm3_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm3_0)) = @__prm3_0))", // @"SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE ([f].[LastName] = N'') OR ([f].[FirstName] IS NOT NULL AND ([f].[LastName] IS NOT NULL AND (LEFT([f].[FirstName], LEN([f].[LastName])) = [f].[LastName])))"); +WHERE ([f].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f].[LastName] IS NOT NULL AND (LEFT([f].[FirstName], LEN([f].[LastName])) = [f].[LastName])))"); } public override async Task String_starts_with_on_argument_with_wildcard_column(bool async) @@ -263,7 +265,7 @@ public override async Task String_starts_with_on_argument_with_wildcard_column(b @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE ([f0].[LastName] = N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (LEFT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName])))"); +WHERE ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (LEFT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName])))"); } public override async Task String_starts_with_on_argument_with_wildcard_column_negated(bool async) @@ -274,7 +276,7 @@ public override async Task String_starts_with_on_argument_with_wildcard_column_n @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (LEFT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName])))"); +WHERE NOT ([f0].[LastName] LIKE N'') AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (LEFT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName])))"); } public override async Task String_ends_with_on_argument_with_wildcard_constant(bool async) @@ -323,13 +325,13 @@ public override async Task String_ends_with_on_argument_with_wildcard_parameter( SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm1_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0))", +WHERE (@__prm1_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0))", // @"@__prm2_0='a_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm2_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0))", +WHERE (@__prm2_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0))", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -339,25 +341,25 @@ FROM [FunkyCustomers] AS [f] SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm4_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm4_0)) = @__prm4_0))", +WHERE (@__prm4_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm4_0)) = @__prm4_0))", // @"@__prm5_0='_Ba_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm5_0 = N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm5_0)) = @__prm5_0))", +WHERE (@__prm5_0 LIKE N'') OR ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm5_0)) = @__prm5_0))", // @"@__prm6_0='%B%a%r' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm6_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0))", +WHERE NOT (@__prm6_0 LIKE N'') AND ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0))", // @"@__prm7_0='' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm7_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0))", +WHERE NOT (@__prm7_0 LIKE N'') AND ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0))", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -372,7 +374,7 @@ public override async Task String_ends_with_on_argument_with_wildcard_column(boo @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE ([f0].[LastName] = N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName])))"); +WHERE ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName])))"); } public override async Task String_ends_with_on_argument_with_wildcard_column_negated(bool async) @@ -383,7 +385,7 @@ public override async Task String_ends_with_on_argument_with_wildcard_column_neg @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName])))"); +WHERE NOT ([f0].[LastName] LIKE N'') AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName])))"); } public override async Task String_ends_with_inside_conditional(bool async) @@ -395,7 +397,7 @@ public override async Task String_ends_with_inside_conditional(bool async) FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN ([f0].[LastName] = N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) + WHEN ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit)"); } @@ -409,7 +411,7 @@ public override async Task String_ends_with_inside_conditional_negated(bool asyn FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName]))) THEN CAST(1 AS bit) + WHEN NOT ([f0].[LastName] LIKE N'') AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName]))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit)"); } @@ -422,10 +424,13 @@ public override async Task String_ends_with_equals_nullable_column(bool async) @"SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool], [f0].[Id], [f0].[FirstName], [f0].[LastName], [f0].[NullableBool] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE CASE - WHEN (([f0].[LastName] = N'') AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) +WHERE (CASE + WHEN ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END = [f].[NullableBool]) OR (CASE + WHEN ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END = [f].[NullableBool]"); +END IS NULL AND [f].[NullableBool] IS NULL)"); } public override async Task String_ends_with_not_equals_nullable_column(bool async) @@ -436,12 +441,50 @@ public override async Task String_ends_with_not_equals_nullable_column(bool asyn @"SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool], [f0].[Id], [f0].[FirstName], [f0].[LastName], [f0].[NullableBool] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE (CASE - WHEN (([f0].[LastName] = N'') AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) +WHERE ((CASE + WHEN ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END <> [f].[NullableBool]) OR (CASE + WHEN ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END IS NULL OR [f].[NullableBool] IS NULL)) AND (CASE + WHEN ([f0].[LastName] LIKE N'') OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END <> [f].[NullableBool]) OR [f].[NullableBool] IS NULL"); +END IS NOT NULL OR [f].[NullableBool] IS NOT NULL)"); + } + + public override async Task String_equals_with_trailing_whitespace_constant(bool async) + { + await base.String_equals_with_trailing_whitespace_constant(async); + + AssertSql( + @"SELECT [f].[FirstName] +FROM [FunkyCustomers] AS [f] +WHERE [f].[LastName] LIKE N'WithoutTrailing '"); } + // TODO: We can rewrite equality to LIKE even when the constant pattern has no trailing whitespace + // (just as long as it has no wildcard characters). This would change string equality in many places. + public override Task String_equals_with_trailing_whitespace_column(bool async) + => Assert.ThrowsAnyAsync(() => + base.String_equals_with_trailing_whitespace_and_like_wildcard(async)); + + public override async Task String_equals_with_like_wildcard(bool async) + { + await base.String_equals_with_like_wildcard(async); + + AssertSql( + @"SELECT [f].[FirstName] +FROM [FunkyCustomers] AS [f] +WHERE [f].[FirstName] = N'With%Wildcard'"); + } + + // In SQL Server we work around trailing whitespace issues by using LIKE instead of the equality operator, + // but we can't do that if there are LIKE wildcard chars present. + public override Task String_equals_with_trailing_whitespace_and_like_wildcard(bool async) + => Assert.ThrowsAnyAsync(() => + base.String_equals_with_trailing_whitespace_and_like_wildcard(async)); + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs index 47660310fe5..526738ba80f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs @@ -39,7 +39,7 @@ public override async Task String_StartsWith_Identity(bool async) 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 ([c].[ContactName] = N'') OR ([c].[ContactName] IS NOT NULL AND (LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); +WHERE ([c].[ContactName] LIKE N'') OR ([c].[ContactName] IS NOT NULL AND (LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); } public override async Task String_StartsWith_Column(bool async) @@ -49,7 +49,7 @@ public override async Task String_StartsWith_Column(bool async) 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 ([c].[ContactName] = N'') OR ([c].[ContactName] IS NOT NULL AND (LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); +WHERE ([c].[ContactName] LIKE N'') OR ([c].[ContactName] IS NOT NULL AND (LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); } public override async Task String_StartsWith_MethodCall(bool async) @@ -62,6 +62,18 @@ FROM [Customers] AS [c] WHERE [c].[ContactName] IS NOT NULL AND ([c].[ContactName] LIKE N'M%')"); } + public override async Task String_StartsWith_Parameter_with_whitespace(bool async) + { + await base.String_StartsWith_Parameter_with_whitespace(async); + + AssertSql( + @"@__pattern_0='hello' (Size = 4000) + +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 (@__pattern_0 LIKE N'') OR ([c].[ContactName] IS NOT NULL AND (LEFT([c].[ContactName], LEN(@__pattern_0)) = @__pattern_0))"); + } + public override async Task String_EndsWith_Literal(bool async) { await base.String_EndsWith_Literal(async); @@ -79,7 +91,7 @@ public override async Task String_EndsWith_Identity(bool async) 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 ([c].[ContactName] = N'') OR ([c].[ContactName] IS NOT NULL AND (RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); +WHERE ([c].[ContactName] LIKE N'') OR ([c].[ContactName] IS NOT NULL AND (RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); } public override async Task String_EndsWith_Column(bool async) @@ -89,7 +101,7 @@ public override async Task String_EndsWith_Column(bool async) 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 ([c].[ContactName] = N'') OR ([c].[ContactName] IS NOT NULL AND (RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); +WHERE ([c].[ContactName] LIKE N'') OR ([c].[ContactName] IS NOT NULL AND (RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]))"); } public override async Task String_EndsWith_MethodCall(bool async) @@ -102,6 +114,18 @@ FROM [Customers] AS [c] WHERE [c].[ContactName] IS NOT NULL AND ([c].[ContactName] LIKE N'%m')"); } + public override async Task String_EndsWith_Parameter_with_whitespace(bool async) + { + await base.String_EndsWith_Parameter_with_whitespace(async); + + AssertSql( + @"@__pattern_0='hello' (Size = 4000) + +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 (@__pattern_0 LIKE N'') OR ([c].[ContactName] IS NOT NULL AND (RIGHT([c].[ContactName], LEN(@__pattern_0)) = @__pattern_0))"); + } + public override async Task String_Contains_Literal(bool async) { await AssertQuery( @@ -123,7 +147,7 @@ public override async Task String_Contains_Identity(bool async) 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 ([c].[ContactName] = N'') OR (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0)"); +WHERE ([c].[ContactName] LIKE N'') OR (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0)"); } public override async Task String_Contains_Column(bool async) @@ -133,7 +157,7 @@ public override async Task String_Contains_Column(bool async) 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 ([c].[ContactName] = N'') OR (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0)"); +WHERE ([c].[ContactName] LIKE N'') OR (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0)"); } public override async Task String_Contains_MethodCall(bool async) @@ -152,6 +176,18 @@ FROM [Customers] AS [c] WHERE CHARINDEX(N'M', [c].[ContactName]) > 0"); } + public override async Task String_Contains_Parameter_with_whitespace(bool async) + { + await base.String_Contains_Parameter_with_whitespace(async); + + AssertSql( + @"@__pattern_0=' ' (Size = 4000) + +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 (@__pattern_0 LIKE N'') OR (CHARINDEX(@__pattern_0, [c].[ContactName]) > 0)"); + } + public override async Task String_Compare_simple_zero(bool async) { await base.String_Compare_simple_zero(async); @@ -1258,7 +1294,7 @@ public override async Task IsNullOrEmpty_in_predicate(bool async) 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 [c].[Region] IS NULL OR ([c].[Region] = N'')"); +WHERE [c].[Region] IS NULL OR ([c].[Region] LIKE N'')"); } public override void IsNullOrEmpty_in_projection() @@ -1267,7 +1303,7 @@ public override void IsNullOrEmpty_in_projection() AssertSql( @"SELECT [c].[CustomerID] AS [Id], CASE - WHEN [c].[Region] IS NULL OR (([c].[Region] = N'') AND [c].[Region] IS NOT NULL) THEN CAST(1 AS bit) + WHEN [c].[Region] IS NULL OR ([c].[Region] LIKE N'') THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [Value] FROM [Customers] AS [c]"); @@ -1279,7 +1315,7 @@ public override void IsNullOrEmpty_negated_in_projection() AssertSql( @"SELECT [c].[CustomerID] AS [Id], CASE - WHEN [c].[Region] IS NOT NULL AND (([c].[Region] <> N'') OR [c].[Region] IS NULL) THEN CAST(1 AS bit) + WHEN NOT ([c].[Region] IS NULL OR ([c].[Region] LIKE N'')) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [Value] FROM [Customers] AS [c]"); @@ -1292,7 +1328,7 @@ public override async Task IsNullOrWhiteSpace_in_predicate(bool async) 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 [c].[Region] IS NULL OR (LTRIM(RTRIM([c].[Region])) = N'')"); +WHERE [c].[Region] IS NULL OR (LTRIM(RTRIM([c].[Region])) LIKE N'')"); } public override async Task IsNullOrWhiteSpace_in_predicate_on_non_nullable_column(bool async) @@ -1302,7 +1338,7 @@ public override async Task IsNullOrWhiteSpace_in_predicate_on_non_nullable_colum 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 LTRIM(RTRIM([c].[CustomerID])) = N''"); +WHERE LTRIM(RTRIM([c].[CustomerID])) LIKE N''"); } public override async Task TrimStart_without_arguments_in_predicate(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs index 23e65a56f3a..a4fbe75125d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindKeylessEntitiesQuerySqlServerTest.cs @@ -67,7 +67,7 @@ SELECT COUNT(*) FROM [Orders] AS [o] WHERE [c].[CustomerID] = [o].[CustomerID]) AS [OrderCount], @__ef_filter___searchTerm_0 AS [SearchTerm] FROM [Customers] AS [c] -WHERE ((@__ef_filter___searchTerm_1 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter___searchTerm_1)) = @__ef_filter___searchTerm_1))) AND (( +WHERE ((@__ef_filter___searchTerm_1 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter___searchTerm_1)) = @__ef_filter___searchTerm_1))) AND (( SELECT COUNT(*) FROM [Orders] AS [o] WHERE [c].[CustomerID] = [o].[CustomerID]) > 0)"); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 1051fec3091..f4dc226cfbc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -1460,7 +1460,7 @@ public override async Task All_top_level_column(bool async) WHEN NOT EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE (([c].[ContactName] <> N'') OR [c].[ContactName] IS NULL) AND ([c].[ContactName] IS NULL OR (LEFT([c].[ContactName], LEN([c].[ContactName])) <> [c].[ContactName]))) THEN CAST(1 AS bit) + WHERE NOT (([c].[ContactName] LIKE N'') OR ([c].[ContactName] IS NOT NULL AND (LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName])))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END"); } @@ -2765,7 +2765,7 @@ public override async Task Environment_newline_is_funcletized(bool async) 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 (@__NewLine_0 = N'') OR (CHARINDEX(@__NewLine_0, [c].[CustomerID]) > 0)"); +WHERE (@__NewLine_0 LIKE N'') OR (CHARINDEX(@__NewLine_0, [c].[CustomerID]) > 0)"); } public override async Task String_concat_with_navigation1(bool async) @@ -4235,7 +4235,7 @@ public override async Task Comparing_to_fixed_string_parameter(bool async) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE (@__prefix_0 = N'') OR (LEFT([c].[CustomerID], LEN(@__prefix_0)) = @__prefix_0)"); +WHERE (@__prefix_0 LIKE N'') OR (LEFT([c].[CustomerID], LEN(@__prefix_0)) = @__prefix_0)"); } public override async Task Comparing_entities_using_Equals(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs index 68318f5d6f4..37cdc890975 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs @@ -26,7 +26,7 @@ public override void Count_query() SELECT COUNT(*) FROM [Customers] AS [c] -WHERE (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); } public override void Materialized_query() @@ -38,7 +38,7 @@ public override void Materialized_query() 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 (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); } public override void Find() @@ -51,7 +51,7 @@ public override void Find() SELECT TOP(1) [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 ((@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))) AND ([c].[CustomerID] = @__p_0)"); +WHERE ((@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))) AND ([c].[CustomerID] = @__p_0)"); } public override void Materialized_query_parameter() @@ -63,7 +63,7 @@ public override void Materialized_query_parameter() 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 (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); } public override void Materialized_query_parameter_new_context() @@ -75,13 +75,13 @@ public override void Materialized_query_parameter_new_context() 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 (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))", +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))", // @"@__ef_filter__TenantPrefix_0='T' (Size = 4000) 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 (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); } public override void Projection_query_parameter() @@ -93,7 +93,7 @@ public override void Projection_query_parameter() SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); } public override void Projection_query() @@ -105,7 +105,7 @@ public override void Projection_query() SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); } public override void Include_query() @@ -127,7 +127,7 @@ FROM [Customers] AS [c0] ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL ) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID] -WHERE (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) ORDER BY [c].[CustomerID], [t0].[OrderID]"); } @@ -154,7 +154,7 @@ FROM [Orders] AS [o] LEFT JOIN ( 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 (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) + WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL"); } @@ -198,7 +198,7 @@ FROM [Orders] AS [o] LEFT JOIN ( SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] FROM [Customers] AS [c0] - WHERE (@__ef_filter__TenantPrefix_0 = N'') OR ([c0].[CompanyName] IS NOT NULL AND (LEFT([c0].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) + WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c0].[CompanyName] IS NOT NULL AND (LEFT([c0].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL ) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID] @@ -227,7 +227,7 @@ public void FromSql_is_composed() FROM ( select * from Customers ) AS [c] -WHERE (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); +WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))"); } [ConditionalFact] @@ -250,7 +250,7 @@ public void FromSql_is_composed_when_filter_has_navigation() LEFT JOIN ( 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 (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) + WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL"); } @@ -265,14 +265,14 @@ public override void Compiled_query() 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 ((@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))) AND ([c].[CustomerID] = @__customerID)", +WHERE ((@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))) AND ([c].[CustomerID] = @__customerID)", // @"@__ef_filter__TenantPrefix_0='B' (Size = 4000) @__customerID='BLAUS' (Size = 5) (DbType = StringFixedLength) 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 ((@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))) AND ([c].[CustomerID] = @__customerID)"); +WHERE ((@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0))) AND ([c].[CustomerID] = @__customerID)"); } public override void Entity_Equality() @@ -287,7 +287,7 @@ FROM [Orders] AS [o] LEFT JOIN ( 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 (@__ef_filter__TenantPrefix_0 = N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) + WHERE (@__ef_filter__TenantPrefix_0 LIKE N'') OR ([c].[CompanyName] IS NOT NULL AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 8b3f97c6f02..22b28ef2388 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -925,7 +925,7 @@ public override async Task Where_equal_with_and_and_contains(bool async) AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE (([e].[NullableStringB] = N'') OR (CHARINDEX([e].[NullableStringB], [e].[NullableStringA]) > 0)) AND ([e].[BoolA] = CAST(1 AS bit))"); +WHERE (([e].[NullableStringB] LIKE N'') OR (CHARINDEX([e].[NullableStringB], [e].[NullableStringA]) > 0)) AND ([e].[BoolA] = CAST(1 AS bit))"); } public override async Task Null_comparison_in_selector_with_relational_nulls(bool async)