diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/Internal/StartsWithTranslator.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/Internal/StartsWithTranslator.cs index 243c48abc10..f25e20e58ed 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/Internal/StartsWithTranslator.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/Internal/StartsWithTranslator.cs @@ -30,6 +30,7 @@ public virtual Expression Translate(MethodCallExpression methodCallExpression) return ReferenceEquals(methodCallExpression.Method, _methodInfo) ? new LikeExpression( + // ReSharper disable once AssignNullToNotNullAttribute methodCallExpression.Object, Expression.Add(methodCallExpression.Arguments[0], Expression.Constant("%", typeof(string)), _concat)) : null; diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslator.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslator.cs index ec1af67b8d8..956da5a9f7b 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslator.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionTranslators/RelationalCompositeMethodCallTranslator.cs @@ -55,6 +55,6 @@ public virtual Expression Translate(MethodCallExpression methodCallExpression) /// /// The translators. protected virtual void AddTranslators([NotNull] IEnumerable translators) - => _methodCallTranslators.AddRange(translators); + => _methodCallTranslators.InsertRange(0, translators); } } diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/PredicateNegationExpressionOptimizer.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/PredicateNegationExpressionOptimizer.cs index 08a24632c45..57ef24e8f65 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/PredicateNegationExpressionOptimizer.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/PredicateNegationExpressionOptimizer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query.Expressions; using Remotion.Linq.Parsing; namespace Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal @@ -98,9 +99,11 @@ protected override Expression VisitUnary(UnaryExpression node) return Visit(innerUnary.Operand); } - var innerBinary = node.Operand as BinaryExpression; + var notNullableExpression = node.Operand as NotNullableExpression; + var innerBinary = (notNullableExpression?.Operand ?? node.Operand) as BinaryExpression; if (innerBinary != null) { + Expression result = null; if ((innerBinary.NodeType == ExpressionType.Equal) || (innerBinary.NodeType == ExpressionType.NotEqual)) { @@ -108,7 +111,7 @@ protected override Expression VisitUnary(UnaryExpression node) // if user opts-out of the null semantics, we should not apply this rule // !(a == b) -> a != b // !(a != b) -> a == b - return innerBinary.NodeType == ExpressionType.Equal + result = innerBinary.NodeType == ExpressionType.Equal ? Visit(Expression.NotEqual(innerBinary.Left, innerBinary.Right)) : Visit(Expression.Equal(innerBinary.Left, innerBinary.Right)); } @@ -116,7 +119,7 @@ protected override Expression VisitUnary(UnaryExpression node) if (innerBinary.NodeType == ExpressionType.AndAlso) { // !(a && b) -> !a || !b - return Visit( + result = Visit( Expression.MakeBinary( ExpressionType.OrElse, Expression.Not(innerBinary.Left), @@ -126,7 +129,7 @@ protected override Expression VisitUnary(UnaryExpression node) if (innerBinary.NodeType == ExpressionType.OrElse) { // !(a || b) -> !a && !b - return Visit( + result = Visit( Expression.MakeBinary( ExpressionType.AndAlso, Expression.Not(innerBinary.Left), @@ -136,12 +139,19 @@ protected override Expression VisitUnary(UnaryExpression node) if (_nodeTypeMapping.ContainsKey(innerBinary.NodeType)) { // e.g. !(a > b) -> a <= b - return Visit( + result = Visit( Expression.MakeBinary( _nodeTypeMapping[innerBinary.NodeType], innerBinary.Left, innerBinary.Right)); } + + if (result != null) + { + return notNullableExpression != null + ? new NotNullableExpression(result) + : result; + } } } diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/RelationalNullsExpandingVisitor.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/RelationalNullsExpandingVisitor.cs index 1c214fa66df..9fe589bab04 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/RelationalNullsExpandingVisitor.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/ExpressionVisitors/Internal/RelationalNullsExpandingVisitor.cs @@ -114,13 +114,9 @@ var rightOperand /// directly from your code. This API may change or be removed in future releases. /// protected override Expression VisitExtension(Expression node) - { - var notNullableExpression = node as NotNullableExpression; - - return notNullableExpression != null + => node is NotNullableExpression ? node : base.VisitExtension(node); - } private static Expression UnwrapConvertExpression(Expression expression, out Type conversionResultType) { diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs b/src/Microsoft.EntityFrameworkCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs index 45661c85725..1f2609e54c2 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs @@ -1632,21 +1632,27 @@ protected override Expression VisitBinary(BinaryExpression expression) } else { + Expression newLeft; + Expression newRight; if (expression.IsLogicalOperation()) { var parentIsSearchCondition = _isSearchCondition; _isSearchCondition = true; - var left = Visit(expression.Left); - var right = Visit(expression.Right); + newLeft = Visit(expression.Left); + newRight = Visit(expression.Right); _isSearchCondition = parentIsSearchCondition; - - return Expression.MakeBinary(expression.NodeType, left, right); + } + else + { + newLeft = Visit(expression.Left); + newRight = Visit(expression.Right); } - if (IsSearchCondition(expression)) + var newExpression = expression.Update(newLeft, expression.Conversion, newRight); + if (IsSearchCondition(newExpression)) { return Expression.Condition( - expression, + newExpression, Expression.Constant(true, typeof(bool)), Expression.Constant(false, typeof(bool))); } diff --git a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs index 8987a8ee66e..4b68667dabb 100644 --- a/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs +++ b/src/Microsoft.EntityFrameworkCore.Specification.Tests/QueryTestBase.cs @@ -426,6 +426,13 @@ public virtual void All_top_level() cs => cs.All(c => c.ContactName.StartsWith("A"))); } + [ConditionalFact] + public virtual void All_top_level_column() + { + AssertQuery( + cs => cs.All(c => c.ContactName.StartsWith(c.ContactName))); + } + [ConditionalFact] public virtual void All_top_level_subquery() { diff --git a/src/Microsoft.EntityFrameworkCore.SqlServer/Microsoft.EntityFrameworkCore.SqlServer.csproj b/src/Microsoft.EntityFrameworkCore.SqlServer/Microsoft.EntityFrameworkCore.SqlServer.csproj index e6e49a7d90f..6cec87b11a9 100644 --- a/src/Microsoft.EntityFrameworkCore.SqlServer/Microsoft.EntityFrameworkCore.SqlServer.csproj +++ b/src/Microsoft.EntityFrameworkCore.SqlServer/Microsoft.EntityFrameworkCore.SqlServer.csproj @@ -106,9 +106,11 @@ + + @@ -116,6 +118,7 @@ + @@ -192,4 +195,4 @@ --> - + \ No newline at end of file diff --git a/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerCompositeMethodCallTranslator.cs b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerCompositeMethodCallTranslator.cs index 486c2696078..0003848d259 100644 --- a/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerCompositeMethodCallTranslator.cs +++ b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerCompositeMethodCallTranslator.cs @@ -29,7 +29,10 @@ public class SqlServerCompositeMethodCallTranslator : RelationalCompositeMethodC new SqlServerStringTrimEndTranslator(), new SqlServerStringTrimStartTranslator(), new SqlServerStringTrimTranslator(), - new SqlServerConvertTranslator() + new SqlServerConvertTranslator(), + new SqlServerContainsCharIndexTranslator(), + new SqlServerEndsWithCharIndexTranslator(), + new SqlServerStartsWithCharIndexTranslator(), }; // ReSharper disable once SuggestBaseTypeForParameter diff --git a/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerContainsCharIndexTranslator.cs b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerContainsCharIndexTranslator.cs new file mode 100644 index 00000000000..4e7aaa1012c --- /dev/null +++ b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerContainsCharIndexTranslator.cs @@ -0,0 +1,43 @@ +// 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 System.Reflection; +using Microsoft.EntityFrameworkCore.Query.Expressions; + +namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class SqlServerContainsCharIndexTranslator : IMethodCallTranslator + { + private static readonly MethodInfo _methodInfo + = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) }); + + public virtual Expression Translate(MethodCallExpression methodCallExpression) + { + if (ReferenceEquals(methodCallExpression.Method, _methodInfo)) + { + var patternExpression = methodCallExpression.Arguments[0]; + var patternConstantExpression = patternExpression as ConstantExpression; + + var charIndexExpression = Expression.GreaterThan( + new SqlFunctionExpression("CHARINDEX", typeof(int), new[] { patternExpression, methodCallExpression.Object }), + Expression.Constant(0)); + + return + patternConstantExpression != null + ? (string)patternConstantExpression.Value == string.Empty + ? (Expression)Expression.Constant(true) + : charIndexExpression + : Expression.OrElse( + charIndexExpression, + Expression.Equal(patternExpression, Expression.Constant(string.Empty))); + } + + return null; + } + } +} diff --git a/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerEndsWithCharIndexTranslator.cs b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerEndsWithCharIndexTranslator.cs new file mode 100644 index 00000000000..df9d0de5daf --- /dev/null +++ b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerEndsWithCharIndexTranslator.cs @@ -0,0 +1,51 @@ +// 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 System.Reflection; +using Microsoft.EntityFrameworkCore.Query.Expressions; + +namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class SqlServerEndsWithCharIndexTranslator : IMethodCallTranslator + { + private static readonly MethodInfo _methodInfo + = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) }); + + public virtual Expression Translate(MethodCallExpression methodCallExpression) + { + if (ReferenceEquals(methodCallExpression.Method, _methodInfo)) + { + var patternExpression = methodCallExpression.Arguments[0]; + var patternConstantExpression = patternExpression as ConstantExpression; + + var endsWithExpression = Expression.Equal( + new SqlFunctionExpression( + "RIGHT", + // ReSharper disable once PossibleNullReferenceException + methodCallExpression.Object.Type, + new[] + { + methodCallExpression.Object, + new SqlFunctionExpression("LEN", typeof(int), new[] { patternExpression }) + }), + patternExpression); + + return new NotNullableExpression( + patternConstantExpression != null + ? (string)patternConstantExpression.Value == string.Empty + ? (Expression)Expression.Constant(true) + : endsWithExpression + : Expression.OrElse( + endsWithExpression, + Expression.Equal(patternExpression, Expression.Constant(string.Empty)))); + } + + return null; + } + } +} diff --git a/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerStartsWithCharIndexTranslator.cs b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerStartsWithCharIndexTranslator.cs new file mode 100644 index 00000000000..7d635c9349c --- /dev/null +++ b/src/Microsoft.EntityFrameworkCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerStartsWithCharIndexTranslator.cs @@ -0,0 +1,50 @@ +// 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 System.Reflection; +using Microsoft.EntityFrameworkCore.Query.Expressions; + +namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class SqlServerStartsWithCharIndexTranslator : IMethodCallTranslator + { + private static readonly MethodInfo _methodInfo + = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) }); + + private static readonly MethodInfo _concat + = typeof(string).GetRuntimeMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) }); + + public virtual Expression Translate(MethodCallExpression methodCallExpression) + { + if (ReferenceEquals(methodCallExpression.Method, _methodInfo)) + { + var patternExpression = methodCallExpression.Arguments[0]; + var patternConstantExpression = patternExpression as ConstantExpression; + + var startsWithExpression = Expression.AndAlso( + new LikeExpression( + // ReSharper disable once AssignNullToNotNullAttribute + methodCallExpression.Object, + Expression.Add(methodCallExpression.Arguments[0], Expression.Constant("%", typeof(string)), _concat)), + Expression.Equal( + new SqlFunctionExpression("CHARINDEX", typeof(int), new[] { patternExpression, methodCallExpression.Object }), + Expression.Constant(1))); + + return patternConstantExpression != null + ? (string)patternConstantExpression.Value == string.Empty + ? (Expression)Expression.Constant(true) + : startsWithExpression + : Expression.OrElse( + startsWithExpression, + Expression.Equal(patternExpression, Expression.Constant(string.Empty))); + } + + return null; + } + } +} diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs index beacf61f434..7748becaf80 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs @@ -288,7 +288,8 @@ public override void Navigation_inside_method_call_translated_to_join() @"SELECT [e1].[Id], [e1].[Name], [e1].[OneToMany_Optional_Self_InverseId], [e1].[OneToMany_Required_Self_InverseId], [e1].[OneToOne_Optional_SelfId] FROM [Level1] AS [e1] INNER JOIN [Level2] AS [e1.OneToOne_Required_FK] ON [e1].[Id] = [e1.OneToOne_Required_FK].[Level1_Required_Id] -WHERE [e1.OneToOne_Required_FK].[Name] LIKE N'L' + N'%'", Sql); +WHERE [e1.OneToOne_Required_FK].[Name] LIKE N'L' + N'%' AND (CHARINDEX(N'L', [e1.OneToOne_Required_FK].[Name]) = 1)", + Sql); } public override void Join_navigation_in_outer_selector_translated_to_extra_join() diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/FromSqlQuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/FromSqlQuerySqlServerTest.cs index f3dcf3525a0..b30e8ddd888 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/FromSqlQuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/FromSqlQuerySqlServerTest.cs @@ -48,7 +48,7 @@ public override void From_sql_queryable_composed() FROM ( SELECT * FROM ""Customers"" ) AS [c] -WHERE [c].[ContactName] LIKE (N'%' + N'z') + N'%'", +WHERE CHARINDEX(N'z', [c].[ContactName]) > 0", Sql); } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs index 6a69125817e..e8272fd5230 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs @@ -882,7 +882,7 @@ public override void Null_propagation_optimization2() Assert.Equal( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gear] AS [g] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND [g].[LeaderNickname] LIKE N'%' + N'us'", +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND RIGHT([g].[LeaderNickname], LEN(N'us')) = N'us'", Sql); } @@ -894,7 +894,7 @@ public override void Null_propagation_optimization3() Assert.Equal( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gear] AS [g] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND [g].[LeaderNickname] LIKE N'%' + N'us'", +WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND RIGHT([g].[LeaderNickname], LEN(N'us')) = N'us'", Sql); } @@ -1248,7 +1248,7 @@ public override void Non_unicode_string_literals_is_used_for_non_unicode_column_ Assert.Equal( @"SELECT [c].[Name], [c].[Location] FROM [City] AS [c] -WHERE [c].[Location] LIKE ('%' + 'Jacinto') + '%'", +WHERE CHARINDEX(N'Jacinto', [c].[Location]) > 0", Sql); } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs index 98ce31247c9..bdfbee0157e 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs @@ -274,7 +274,7 @@ public override void Include_collection_order_by_collection_column() Assert.Equal( @"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 [c].[CustomerID] LIKE N'W' + N'%' +WHERE [c].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c].[CustomerID]) = 1) ORDER BY ( SELECT TOP(1) [oo].[OrderDate] FROM [Orders] AS [oo] @@ -292,7 +292,7 @@ FROM [Orders] AS [oo] ORDER BY [oo].[OrderDate] DESC ) AS [c0_0], [c].[CustomerID] FROM [Customers] AS [c] - WHERE [c].[CustomerID] LIKE N'W' + N'%' + WHERE [c].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c].[CustomerID]) = 1) ORDER BY [c0_0] DESC, [c].[CustomerID] ) AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] ORDER BY [c0].[c0_0] DESC, [c0].[CustomerID]", @@ -1252,9 +1252,9 @@ public override void Then_include_collection_order_by_collection_column() base.Then_include_collection_order_by_collection_column(); Assert.Equal( - @"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] + @"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 [c].[CustomerID] LIKE N'W' + N'%' +WHERE [c].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c].[CustomerID]) = 1) ORDER BY ( SELECT TOP(1) [oo].[OrderDate] FROM [Orders] AS [oo] @@ -1272,7 +1272,7 @@ FROM [Orders] AS [oo] ORDER BY [oo].[OrderDate] DESC ) AS [c0_0], [c].[CustomerID] FROM [Customers] AS [c] - WHERE [c].[CustomerID] LIKE N'W' + N'%' + WHERE [c].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c].[CustomerID]) = 1) ORDER BY [c0_0] DESC, [c].[CustomerID] ) AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] ORDER BY [c0].[c0_0] DESC, [c0].[CustomerID], [o].[OrderID] @@ -1290,12 +1290,12 @@ FROM [Orders] AS [oo] ORDER BY [oo].[OrderDate] DESC ) AS [c0_0], [c].[CustomerID] FROM [Customers] AS [c] - WHERE [c].[CustomerID] LIKE N'W' + N'%' + WHERE [c].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c].[CustomerID]) = 1) ORDER BY [c0_0] DESC, [c].[CustomerID] ) AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] ) AS [o1] ON [o0].[OrderID] = [o1].[OrderID] ORDER BY [o1].[c0_0] DESC, [o1].[CustomerID], [o1].[OrderID]", - Sql); + Sql); } private const string FileLineEnding = @" diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/InheritanceSqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/InheritanceSqlServerTest.cs index ed1021b66f5..2c1ee04c44c 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/InheritanceSqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/InheritanceSqlServerTest.cs @@ -362,7 +362,7 @@ INSERT INTO [Animal] ([Species], [CountryId], [Discriminator], [Name], [EagleId] SELECT TOP(2) [k].[Species], [k].[CountryId], [k].[Discriminator], [k].[Name], [k].[EagleId], [k].[IsFlightless], [k].[FoundOn] FROM [Animal] AS [k] -WHERE ([k].[Discriminator] = N'Kiwi') AND [k].[Species] LIKE N'%' + N'owenii' +WHERE ([k].[Discriminator] = N'Kiwi') AND (RIGHT([k].[Species], LEN(N'owenii')) = N'owenii') @p1: Apteryx owenii (Nullable = false) (Size = 100) @p0: Aquila chrysaetos canadensis (Size = 100) @@ -374,7 +374,7 @@ FROM [Animal] AS [k] SELECT TOP(2) [k].[Species], [k].[CountryId], [k].[Discriminator], [k].[Name], [k].[EagleId], [k].[IsFlightless], [k].[FoundOn] FROM [Animal] AS [k] -WHERE ([k].[Discriminator] = N'Kiwi') AND [k].[Species] LIKE N'%' + N'owenii' +WHERE ([k].[Discriminator] = N'Kiwi') AND (RIGHT([k].[Species], LEN(N'owenii')) = N'owenii') @p0: Apteryx owenii (Nullable = false) (Size = 100) @@ -385,7 +385,7 @@ DELETE FROM [Animal] SELECT COUNT(*) FROM [Animal] AS [k] -WHERE ([k].[Discriminator] = N'Kiwi') AND [k].[Species] LIKE N'%' + N'owenii'", +WHERE ([k].[Discriminator] = N'Kiwi') AND (RIGHT([k].[Species], LEN(N'owenii')) = N'owenii')", Sql); } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs index 8d71123eed6..23f4bea7dfd 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs @@ -913,7 +913,7 @@ public override void Where_equal_with_and_and_contains() Assert.Equal( @"SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE [e].[NullableStringA] LIKE (N'%' + [e].[NullableStringB]) + N'%' AND ([e].[BoolA] = 1)", +WHERE ((CHARINDEX([e].[NullableStringB], [e].[NullableStringA]) > 0) OR ([e].[NullableStringB] = N'')) AND ([e].[BoolA] = 1)", Sql); } @@ -941,7 +941,7 @@ FROM [NullSemanticsEntity1] AS [e] WHERE CASE WHEN @__prm_0 = 0 THEN CAST(1 AS BIT) ELSE CASE - WHEN [e].[StringA] LIKE N'A' + N'%' + WHEN [e].[StringA] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [e].[StringA]) = 1) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END END = 1", @@ -965,7 +965,7 @@ THEN CASE THEN CASE WHEN [e].[BoolA] = 1 THEN CASE - WHEN [e].[StringA] LIKE N'A' + N'%' + WHEN [e].[StringA] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [e].[StringA]) = 1) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END ELSE CAST(0 AS BIT) END ELSE CAST(1 AS BIT) diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryBugsTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryBugsTest.cs index f5710396a54..4515a98fe14 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryBugsTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryBugsTest.cs @@ -1321,6 +1321,668 @@ public class Comment5456 public Post5456 Blog { get; set; } } + [Fact] + public virtual void Repro474_string_contains_on_argument_with_wildcard_constant() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result1 = ctx.Customers.Where(c => c.FirstName.Contains("%B")).Select(c => c.FirstName).ToList(); + var expected1 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.Contains("%B")); + Assert.True(expected1.Count() == result1.Count); + + var result2 = ctx.Customers.Where(c => c.FirstName.Contains("a_")).Select(c => c.FirstName).ToList(); + var expected2 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.Contains("a_")); + Assert.True(expected2.Count() == result2.Count); + + var result3 = ctx.Customers.Where(c => c.FirstName.Contains(null)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result3.Count); + + var result4 = ctx.Customers.Where(c => c.FirstName.Contains("")).Select(c => c.FirstName).ToList(); + Assert.True(ctx.Customers.Count() == result4.Count); + + var result5 = ctx.Customers.Where(c => c.FirstName.Contains("_Ba_")).Select(c => c.FirstName).ToList(); + var expected5 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.Contains("_Ba_")); + Assert.True(expected5.Count() == result5.Count); + + var result6 = ctx.Customers.Where(c => !c.FirstName.Contains("%B%a%r")).Select(c => c.FirstName).ToList(); + var expected6 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && !c.Contains("%B%a%r")); + Assert.True(expected6.Count() == result6.Count); + + var result7 = ctx.Customers.Where(c => !c.FirstName.Contains("")).Select(c => c.FirstName).ToList(); + Assert.True(0 == result7.Count); + + var result8 = ctx.Customers.Where(c => !c.FirstName.Contains(null)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result8.Count); + } + } + + [Fact] + public virtual void Repro474_string_contains_on_argument_with_wildcard_parameter() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var prm1 = "%B"; + var result1 = ctx.Customers.Where(c => c.FirstName.Contains(prm1)).Select(c => c.FirstName).ToList(); + var expected1 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.Contains(prm1)); + Assert.True(expected1.Count() == result1.Count); + + var prm2 = "a_"; + var result2 = ctx.Customers.Where(c => c.FirstName.Contains(prm2)).Select(c => c.FirstName).ToList(); + var expected2 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.Contains(prm2)); + Assert.True(expected2.Count() == result2.Count); + + var prm3 = (string)null; + var result3 = ctx.Customers.Where(c => c.FirstName.Contains(prm3)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result3.Count); + + var prm4 = ""; + var result4 = ctx.Customers.Where(c => c.FirstName.Contains(prm4)).Select(c => c.FirstName).ToList(); + Assert.True(ctx.Customers.Count() == result4.Count); + + var prm5 = "_Ba_"; + var result5 = ctx.Customers.Where(c => c.FirstName.Contains(prm5)).Select(c => c.FirstName).ToList(); + var expected5 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.Contains(prm5)); + Assert.True(expected5.Count() == result5.Count); + + var prm6 = "%B%a%r"; + var result6 = ctx.Customers.Where(c => !c.FirstName.Contains(prm6)).Select(c => c.FirstName).ToList(); + var expected6 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && !c.Contains(prm6)); + Assert.True(expected6.Count() == result6.Count); + + var prm7 = ""; + var result7 = ctx.Customers.Where(c => !c.FirstName.Contains(prm7)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result7.Count); + + var prm8 = (string)null; + var result8 = ctx.Customers.Where(c => !c.FirstName.Contains(prm8)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result8.Count); + } + } + + [Fact] + public virtual void Repro474_string_contains_on_argument_with_wildcard_column() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => r.fn.Contains(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => r.ln == "" || (r.fn != null && r.ln != null && r.fn.Contains(r.ln))) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_contains_on_argument_with_wildcard_column_negated() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => !r.fn.Contains(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => r.ln != "" && r.fn != null && r.ln != null && !r.fn.Contains(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_starts_with_on_argument_with_wildcard_constant() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result1 = ctx.Customers.Where(c => c.FirstName.StartsWith("%B")).Select(c => c.FirstName).ToList(); + var expected1 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith("%B")); + Assert.True(expected1.Count() == result1.Count); + + var result2 = ctx.Customers.Where(c => c.FirstName.StartsWith("a_")).Select(c => c.FirstName).ToList(); + var expected2 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith("a_")); + Assert.True(expected2.Count() == result2.Count); + + var result3 = ctx.Customers.Where(c => c.FirstName.StartsWith(null)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result3.Count); + + var result4 = ctx.Customers.Where(c => c.FirstName.StartsWith("")).Select(c => c.FirstName).ToList(); + Assert.True(ctx.Customers.Count() == result4.Count); + + var result5 = ctx.Customers.Where(c => c.FirstName.StartsWith("_Ba_")).Select(c => c.FirstName).ToList(); + var expected5 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith("_Ba_")); + Assert.True(expected5.Count() == result5.Count); + + var result6 = ctx.Customers.Where(c => !c.FirstName.StartsWith("%B%a%r")).Select(c => c.FirstName).ToList(); + var expected6 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && !c.StartsWith("%B%a%r")); + Assert.True(expected6.Count() == result6.Count); + + var result7 = ctx.Customers.Where(c => !c.FirstName.StartsWith("")).Select(c => c.FirstName).ToList(); + Assert.True(0 == result7.Count); + + var result8 = ctx.Customers.Where(c => !c.FirstName.StartsWith(null)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result8.Count); + } + } + + [Fact] + public virtual void Repro474_string_starts_with_on_argument_with_wildcard_parameter() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var prm1 = "%B"; + var result1 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm1)).Select(c => c.FirstName).ToList(); + var expected1 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith(prm1)); + Assert.True(expected1.Count() == result1.Count); + + var prm2 = "a_"; + var result2 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm2)).Select(c => c.FirstName).ToList(); + var expected2 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith(prm2)); + Assert.True(expected2.Count() == result2.Count); + + var prm3 = (string)null; + var result3 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm3)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result3.Count); + + var prm4 = ""; + var result4 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm4)).Select(c => c.FirstName).ToList(); + Assert.True(ctx.Customers.Count() == result4.Count); + + var prm5 = "_Ba_"; + var result5 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm5)).Select(c => c.FirstName).ToList(); + var expected5 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith(prm5)); + Assert.True(expected5.Count() == result5.Count); + + var prm6 = "%B%a%r"; + var result6 = ctx.Customers.Where(c => !c.FirstName.StartsWith(prm6)).Select(c => c.FirstName).ToList(); + var expected6 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && !c.StartsWith(prm6)); + Assert.True(expected6.Count() == result6.Count); + + var prm7 = ""; + var result7 = ctx.Customers.Where(c => !c.FirstName.StartsWith(prm7)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result7.Count); + + var prm8 = (string)null; + var result8 = ctx.Customers.Where(c => !c.FirstName.StartsWith(prm8)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result8.Count); + } + } + + [Fact] + public virtual void Repro474_string_starts_with_on_argument_with_wildcard_column() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => r.fn.StartsWith(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => r.ln == "" || (r.fn != null && r.ln != null && r.fn.StartsWith(r.ln))) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_starts_with_on_argument_with_wildcard_column_negated() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => !r.fn.StartsWith(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => r.ln != "" && r.fn != null && r.ln != null && !r.fn.StartsWith(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_ends_with_on_argument_with_wildcard_constant() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result1 = ctx.Customers.Where(c => c.FirstName.StartsWith("%B")).Select(c => c.FirstName).ToList(); + var expected1 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith("%B")); + Assert.True(expected1.Count() == result1.Count); + + var result2 = ctx.Customers.Where(c => c.FirstName.StartsWith("_r")).Select(c => c.FirstName).ToList(); + var expected2 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith("_r")); + Assert.True(expected2.Count() == result2.Count); + + var result3 = ctx.Customers.Where(c => c.FirstName.StartsWith(null)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result3.Count); + + var result4 = ctx.Customers.Where(c => c.FirstName.StartsWith("")).Select(c => c.FirstName).ToList(); + Assert.True(ctx.Customers.Count() == result4.Count); + + var result5 = ctx.Customers.Where(c => c.FirstName.StartsWith("a__r_")).Select(c => c.FirstName).ToList(); + var expected5 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith("a__r_")); + Assert.True(expected5.Count() == result5.Count); + + var result6 = ctx.Customers.Where(c => !c.FirstName.StartsWith("%B%a%r")).Select(c => c.FirstName).ToList(); + var expected6 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && !c.StartsWith("%B%a%r")); + Assert.True(expected6.Count() == result6.Count); + + var result7 = ctx.Customers.Where(c => !c.FirstName.StartsWith("")).Select(c => c.FirstName).ToList(); + Assert.True(0 == result7.Count); + + var result8 = ctx.Customers.Where(c => !c.FirstName.StartsWith(null)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result8.Count); + } + } + + [Fact] + public virtual void Repro474_string_ends_with_on_argument_with_wildcard_parameter() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var prm1 = "%B"; + var result1 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm1)).Select(c => c.FirstName).ToList(); + var expected1 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith(prm1)); + Assert.True(expected1.Count() == result1.Count); + + var prm2 = "_r"; + var result2 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm2)).Select(c => c.FirstName).ToList(); + var expected2 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith(prm2)); + Assert.True(expected2.Count() == result2.Count); + + var prm3 = (string)null; + var result3 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm3)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result3.Count); + + var prm4 = ""; + var result4 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm4)).Select(c => c.FirstName).ToList(); + Assert.True(ctx.Customers.Count() == result4.Count); + + var prm5 = "a__r_"; + var result5 = ctx.Customers.Where(c => c.FirstName.StartsWith(prm5)).Select(c => c.FirstName).ToList(); + var expected5 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && c.StartsWith(prm5)); + Assert.True(expected5.Count() == result5.Count); + + var prm6 = "%B%a%r"; + var result6 = ctx.Customers.Where(c => !c.FirstName.StartsWith(prm6)).Select(c => c.FirstName).ToList(); + var expected6 = ctx.Customers.Select(c => c.FirstName).ToList().Where(c => c != null && !c.StartsWith(prm6)); + Assert.True(expected6.Count() == result6.Count); + + var prm7 = ""; + var result7 = ctx.Customers.Where(c => !c.FirstName.StartsWith(prm7)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result7.Count); + + var prm8 = (string)null; + var result8 = ctx.Customers.Where(c => !c.FirstName.StartsWith(prm8)).Select(c => c.FirstName).ToList(); + Assert.True(0 == result8.Count); + } + } + + [Fact] + public virtual void Repro474_string_ends_with_on_argument_with_wildcard_column() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => r.fn.EndsWith(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => r.ln == "" || (r.fn != null && r.ln != null && r.fn.EndsWith(r.ln))) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_ends_with_on_argument_with_wildcard_column_negated() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => !r.fn.EndsWith(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => r.ln != "" && r.fn != null && r.ln != null && !r.fn.EndsWith(r.ln)) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_ends_with_inside_conditional() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => r.fn.EndsWith(r.ln) ? true : false) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => (r.ln == "" || (r.fn != null && r.ln != null && r.fn.EndsWith(r.ln))) ? true : false ) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_ends_with_inside_conditional_negated() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers.Select(c => c.FirstName) + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) + .Where(r => !r.fn.EndsWith(r.ln) ? true : false) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + var expected = ctx.Customers.Select(c => c.FirstName).ToList() + .SelectMany(c => ctx.Customers.Select(c2 => c2.LastName).ToList(), (fn, ln) => new { fn, ln }) + .Where(r => (r.ln != "" && r.fn != null && r.ln != null && !r.fn.EndsWith(r.ln)) ? true : false) + .ToList().OrderBy(r => r.fn).ThenBy(r => r.ln).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].fn == result[i].fn); + Assert.True(expected[i].ln == result[i].ln); + } + } + } + + [Fact] + public virtual void Repro474_string_ends_with_equals_nullable_column() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers + .SelectMany(c => ctx.Customers, (c1, c2) => new { c1, c2 }) + .Where(r => r.c1.FirstName.EndsWith(r.c2.LastName) == r.c1.NullableBool.Value) + .ToList().Select(r => new { r.c1.FirstName, r.c2.LastName, r.c1.NullableBool }).OrderBy(r => r.FirstName).ThenBy(r => r.LastName).ToList(); + + var expected = ctx.Customers.ToList() + .SelectMany(c => ctx.Customers.ToList(), (c1, c2) => new { c1, c2 }) + .Where(r => (r.c2.LastName != null && r.c1.FirstName != null && r.c1.NullableBool.HasValue && r.c1.FirstName.EndsWith(r.c2.LastName) == r.c1.NullableBool.Value) || (r.c2.LastName == null && r.c1.NullableBool == false)) + .ToList().Select(r => new { r.c1.FirstName, r.c2.LastName, r.c1.NullableBool }).OrderBy(r => r.FirstName).ThenBy(r => r.LastName).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].FirstName == result[i].FirstName); + Assert.True(expected[i].LastName == result[i].LastName); + } + } + } + + + [Fact] + public virtual void Repro474_string_ends_with_not_equals_nullable_column() + { + CreateDatabase474(); + + var loggingFactory = new TestSqlLoggerFactory(); + var serviceProvider = new ServiceCollection() + .AddEntityFrameworkSqlServer() + .AddSingleton(loggingFactory) + .BuildServiceProvider(); + + using (var ctx = new MyContext474(serviceProvider)) + { + var result = ctx.Customers + .SelectMany(c => ctx.Customers, (c1, c2) => new { c1, c2 }) + .Where(r => r.c1.FirstName.EndsWith(r.c2.LastName) != r.c1.NullableBool.Value) + .ToList().Select(r => new { r.c1.FirstName, r.c2.LastName, r.c1.NullableBool }).OrderBy(r => r.FirstName).ThenBy(r => r.LastName).ToList(); + + var expected = ctx.Customers.ToList() + .SelectMany(c => ctx.Customers.ToList(), (c1, c2) => new { c1, c2 }) + .Where(r => + (r.c2.LastName != null && r.c1.FirstName != null && r.c1.NullableBool.HasValue && r.c1.FirstName.EndsWith(r.c2.LastName) != r.c1.NullableBool.Value) + || r.c1.NullableBool == null + || (r.c2.LastName == null && r.c1.NullableBool == true)) + .ToList().Select(r => new { r.c1.FirstName, r.c2.LastName, r.c1.NullableBool }).OrderBy(r => r.FirstName).ThenBy(r => r.LastName).ToList(); + + Assert.Equal(result.Count, expected.Count); + for (int i = 0; i < result.Count; i++) + { + Assert.True(expected[i].FirstName == result[i].FirstName); + Assert.True(expected[i].LastName == result[i].LastName); + } + } + } + + private void CreateDatabase474() + { + CreateTestStore( + "Repro474", + _fixture.ServiceProvider, + (sp, co) => new MyContext474(sp), + context => + { + var c11 = new Customer474 { FirstName = "%Bar", LastName = "%B", NullableBool = true }; + var c12 = new Customer474 { FirstName = "Ba%r", LastName = "a%", NullableBool = true }; + var c13 = new Customer474 { FirstName = "Bar%", LastName = "%B%", NullableBool = true }; + var c14 = new Customer474 { FirstName = "%Ba%r%", LastName = null, NullableBool = false }; + var c15 = new Customer474 { FirstName = "B%a%%r%", LastName = "r%", NullableBool = false }; + var c16 = new Customer474 { FirstName = null, LastName = "%B%a%r" }; + var c17 = new Customer474 { FirstName = "%B%a%r", LastName = "" }; + var c18 = new Customer474 { FirstName = "", LastName = "%%r%" }; + + var c21 = new Customer474 { FirstName = "_Bar", LastName = "_B", NullableBool = false }; + var c22 = new Customer474 { FirstName = "Ba_r", LastName = "a_", NullableBool = false }; + var c23 = new Customer474 { FirstName = "Bar_", LastName = "_B_", NullableBool = false }; + var c24 = new Customer474 { FirstName = "_Ba_r_", LastName = null, NullableBool = true }; + var c25 = new Customer474 { FirstName = "B_a__r_", LastName = "r_", NullableBool = true }; + var c26 = new Customer474 { FirstName = null, LastName = "_B_a_r" }; + var c27 = new Customer474 { FirstName = "_B_a_r", LastName = "" }; + var c28 = new Customer474 { FirstName = "", LastName = "__r_" }; + + context.Customers.AddRange(c11, c12, c13, c14, c15, c16, c17, c18, c21, c22, c23, c24, c25, c26, c27, c28); + + context.SaveChanges(); + }); + } + + public class MyContext474 : DbContext + { + private readonly IServiceProvider _serviceProvider; + + public MyContext474(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public DbSet Customers { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(SqlServerTestStore.CreateConnectionString("Repro474")); + } + + public class Customer474 + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + + public bool? NullableBool { get; set; } + } + private static void CreateTestStore( string databaseName, IServiceProvider serviceProvider, diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs index e4a1176923c..2662e3249f8 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs @@ -405,7 +405,7 @@ public override void Select_collection_navigation_simple() Assert.Equal( @"SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE [c].[CustomerID] LIKE N'A' + N'%' +WHERE [c].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [c].[CustomerID]) = 1) ORDER BY [c].[CustomerID] @_outer_CustomerID: ALFKI (Size = 450) @@ -538,7 +538,7 @@ SELECT CASE WHEN NOT EXISTS ( SELECT 1 FROM [Orders] AS [o0] - WHERE (([c].[CustomerID] = [o0].[CustomerID]) AND [o0].[CustomerID] IS NOT NULL) AND NOT (([o0].[CustomerID] = N'ALFKI') AND [o0].[CustomerID] IS NOT NULL)) + WHERE (([c].[CustomerID] = [o0].[CustomerID]) AND [o0].[CustomerID] IS NOT NULL) AND (([o0].[CustomerID] <> N'ALFKI') OR [o0].[CustomerID] IS NULL)) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END ) @@ -579,7 +579,7 @@ FROM [Customers] AS [c] WHERE NOT EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE (([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL) AND NOT (([o].[CustomerID] = N'ALFKI') AND [o].[CustomerID] IS NOT NULL))", + WHERE (([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL) AND (([o].[CustomerID] <> N'ALFKI') OR [o].[CustomerID] IS NULL))", Sql); } @@ -713,7 +713,7 @@ FROM [Order Details] AS [o3] WHERE [o].[OrderID] = [o3].[OrderID] ) FROM [Orders] AS [o] -WHERE [o].[CustomerID] LIKE N'A' + N'%'", +WHERE [o].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [o].[CustomerID]) = 1)", Sql); } @@ -776,7 +776,7 @@ public override void Collection_select_nav_prop_first_or_default_then_nav_prop() Assert.StartsWith( @"SELECT [e].[CustomerID] FROM [Customers] AS [e] -WHERE [e].[CustomerID] LIKE N'A' + N'%' +WHERE [e].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [e].[CustomerID]) = 1) ORDER BY [e].[CustomerID] @_outer_CustomerID: ALFKI (Size = 450) @@ -804,7 +804,7 @@ public override void Collection_select_nav_prop_first_or_default_then_nav_prop_n Assert.StartsWith( @"SELECT 1 FROM [Customers] AS [e] -WHERE [e].[CustomerID] LIKE N'A' + N'%' +WHERE [e].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [e].[CustomerID]) = 1) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o.Customer].[CustomerID], [o.Customer].[Address], [o.Customer].[City], [o.Customer].[CompanyName], [o.Customer].[ContactName], [o.Customer].[ContactTitle], [o.Customer].[Country], [o.Customer].[Fax], [o.Customer].[Phone], [o.Customer].[PostalCode], [o.Customer].[Region] FROM [Orders] AS [o] @@ -827,7 +827,7 @@ public override void Collection_select_nav_prop_single_or_default_then_nav_prop_ Assert.StartsWith( @"SELECT 1 FROM [Customers] AS [e] -WHERE [e].[CustomerID] LIKE N'A' + N'%' +WHERE [e].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [e].[CustomerID]) = 1) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o.Customer].[CustomerID], [o.Customer].[Address], [o.Customer].[City], [o.Customer].[CompanyName], [o.Customer].[ContactName], [o.Customer].[ContactTitle], [o.Customer].[Country], [o.Customer].[Fax], [o.Customer].[Phone], [o.Customer].[PostalCode], [o.Customer].[Region] FROM [Orders] AS [o] @@ -850,7 +850,7 @@ public override void Collection_select_nav_prop_first_or_default_then_nav_prop_n Assert.StartsWith( @"SELECT 1 FROM [Customers] AS [e] -WHERE [e].[CustomerID] LIKE N'A' + N'%' +WHERE [e].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [e].[CustomerID]) = 1) SELECT [oo].[OrderID], [oo].[CustomerID], [oo].[EmployeeID], [oo].[OrderDate], [oo.Customer].[CustomerID], [oo.Customer].[Address], [oo.Customer].[City], [oo.Customer].[CompanyName], [oo.Customer].[ContactName], [oo.Customer].[ContactTitle], [oo.Customer].[Country], [oo.Customer].[Fax], [oo.Customer].[Phone], [oo.Customer].[PostalCode], [oo.Customer].[Region] FROM [Orders] AS [oo] @@ -873,7 +873,7 @@ public override void Collection_select_nav_prop_first_or_default_then_nav_prop_n Assert.StartsWith( @"SELECT 1 FROM [Customers] AS [e] -WHERE [e].[CustomerID] LIKE N'A' + N'%' +WHERE [e].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [e].[CustomerID]) = 1) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o.Customer].[CustomerID], [o.Customer].[Address], [o.Customer].[City], [o.Customer].[CompanyName], [o.Customer].[ContactName], [o.Customer].[ContactTitle], [o.Customer].[Country], [o.Customer].[Fax], [o.Customer].[Phone], [o.Customer].[PostalCode], [o.Customer].[Region] FROM [Orders] AS [o] @@ -1094,7 +1094,7 @@ public override void Project_single_entity_value_subquery_works() Assert.Equal( @"SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE [c].[CustomerID] LIKE N'A' + N'%' +WHERE [c].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [c].[CustomerID]) = 1) ORDER BY [c].[CustomerID] @_outer_CustomerID: ALFKI (Size = 450) diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs index 729d6afe478..b9686a79f89 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs @@ -1619,7 +1619,7 @@ public override void Any_predicate() WHEN EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE [c].[ContactName] LIKE N'A' + N'%') + WHERE [c].[ContactName] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [c].[ContactName]) = 1)) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END", Sql); @@ -1635,7 +1635,7 @@ FROM [Customers] AS [c] WHERE NOT EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] LIKE N'A' + N'%')", + WHERE [o].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [o].[CustomerID]) = 1))", Sql); } @@ -1649,7 +1649,7 @@ FROM [Customers] AS [c] WHERE (([c].[City] <> N'London') OR [c].[City] IS NULL) AND NOT EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] LIKE N'A' + N'%')", + WHERE [o].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [o].[CustomerID]) = 1))", Sql); } @@ -1663,7 +1663,7 @@ FROM [Customers] AS [c] WHERE NOT EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] LIKE N'A' + N'%') AND (([c].[City] <> N'London') OR [c].[City] IS NULL)", + WHERE [o].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [o].[CustomerID]) = 1)) AND (([c].[City] <> N'London') OR [c].[City] IS NULL)", Sql); } @@ -1677,7 +1677,7 @@ FROM [Customers] AS [c] WHERE EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] LIKE N'A' + N'%')", + WHERE [o].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [o].[CustomerID]) = 1))", Sql); } @@ -1691,7 +1691,7 @@ FROM [Customers] AS [c] WHERE (([c].[City] <> N'London') OR [c].[City] IS NULL) AND EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] LIKE N'A' + N'%')", + WHERE [o].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [o].[CustomerID]) = 1))", Sql); } @@ -1705,7 +1705,7 @@ FROM [Customers] AS [c] WHERE EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] LIKE N'A' + N'%') AND (([c].[City] <> N'London') OR [c].[City] IS NULL)", + WHERE [o].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [o].[CustomerID]) = 1)) AND (([c].[City] <> N'London') OR [c].[City] IS NULL)", Sql); } @@ -1732,7 +1732,22 @@ public override void All_top_level() WHEN NOT EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE NOT ([c].[ContactName] LIKE N'A' + N'%')) + WHERE NOT ([c].[ContactName] LIKE N'A' + N'%') OR (CHARINDEX(N'A', [c].[ContactName]) <> 1)) + THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) +END", + Sql); + } + + public override void All_top_level_column() + { + base.All_top_level_column(); + + Assert.Equal( + @"SELECT CASE + WHEN NOT EXISTS ( + SELECT 1 + FROM [Customers] AS [c] + WHERE (NOT ([c].[ContactName] LIKE [c].[ContactName] + N'%') OR (CHARINDEX([c].[ContactName], [c].[ContactName]) <> 1)) AND (([c].[ContactName] <> N'') OR [c].[ContactName] IS NULL)) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END", Sql); @@ -3955,7 +3970,7 @@ public override void Where_comparison_to_nullable_bool() Assert.Equal( @"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].[CustomerID] LIKE N'%' + N'KI'", +WHERE RIGHT([c].[CustomerID], LEN(N'KI')) = N'KI'", Sql); } @@ -4090,7 +4105,7 @@ public override void String_StartsWith_Literal() Assert.Equal( @"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] LIKE N'M' + N'%'", +WHERE [c].[ContactName] LIKE N'M' + N'%' AND (CHARINDEX(N'M', [c].[ContactName]) = 1)", Sql); } @@ -4101,7 +4116,7 @@ public override void String_StartsWith_Identity() Assert.Equal( @"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] LIKE [c].[ContactName] + N'%'", +WHERE ([c].[ContactName] LIKE [c].[ContactName] + N'%' AND (CHARINDEX([c].[ContactName], [c].[ContactName]) = 1)) OR ([c].[ContactName] = N'')", Sql); } @@ -4112,7 +4127,7 @@ public override void String_StartsWith_Column() Assert.Equal( @"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] LIKE [c].[ContactName] + N'%'", +WHERE ([c].[ContactName] LIKE [c].[ContactName] + N'%' AND (CHARINDEX([c].[ContactName], [c].[ContactName]) = 1)) OR ([c].[ContactName] = N'')", Sql); } @@ -4125,7 +4140,7 @@ public override void String_StartsWith_MethodCall() 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] LIKE @__LocalMethod1_0 + N'%'", +WHERE ([c].[ContactName] LIKE @__LocalMethod1_0 + N'%' AND (CHARINDEX(@__LocalMethod1_0, [c].[ContactName]) = 1)) OR (@__LocalMethod1_0 = N'')", Sql); } @@ -4136,7 +4151,7 @@ public override void String_EndsWith_Literal() Assert.Equal( @"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] LIKE N'%' + N'b'", +WHERE RIGHT([c].[ContactName], LEN(N'b')) = N'b'", Sql); } @@ -4147,7 +4162,7 @@ public override void String_EndsWith_Identity() Assert.Equal( @"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] LIKE N'%' + [c].[ContactName]", +WHERE (RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]) OR ([c].[ContactName] = N'')", Sql); } @@ -4158,7 +4173,7 @@ public override void String_EndsWith_Column() Assert.Equal( @"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] LIKE N'%' + [c].[ContactName]", +WHERE (RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]) OR ([c].[ContactName] = N'')", Sql); } @@ -4171,7 +4186,7 @@ public override void String_EndsWith_MethodCall() 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] LIKE N'%' + @__LocalMethod2_0", +WHERE (RIGHT([c].[ContactName], LEN(@__LocalMethod2_0)) = @__LocalMethod2_0) OR (@__LocalMethod2_0 = N'')", Sql); } @@ -4185,7 +4200,7 @@ public override void String_Contains_Literal() Assert.Equal( @"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] LIKE (N'%' + N'M') + N'%'", +WHERE CHARINDEX(N'M', [c].[ContactName]) > 0", Sql); } @@ -4196,7 +4211,7 @@ public override void String_Contains_Identity() Assert.Equal( @"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] LIKE (N'%' + [c].[ContactName]) + N'%'", +WHERE (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0) OR ([c].[ContactName] = N'')", Sql); } @@ -4207,7 +4222,7 @@ public override void String_Contains_Column() Assert.Equal( @"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] LIKE (N'%' + [c].[ContactName]) + N'%'", +WHERE (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0) OR ([c].[ContactName] = N'')", Sql); } @@ -4223,7 +4238,7 @@ public override void String_Contains_MethodCall() 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] LIKE (N'%' + @__LocalMethod1_0) + N'%'", +WHERE (CHARINDEX(@__LocalMethod1_0, [c].[ContactName]) > 0) OR (@__LocalMethod1_0 = N'')", Sql); } @@ -5675,11 +5690,11 @@ public override void Environment_newline_is_funcletized() Assert.Equal( @"@__NewLine_0: - (Size = 450) + (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 [c].[CustomerID] LIKE (N'%' + @__NewLine_0) + N'%'", +WHERE (CHARINDEX(@__NewLine_0, [c].[CustomerID]) > 0) OR (@__NewLine_0 = N'')", Sql); } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs index d3cec4fab37..eb0b33f0a92 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs @@ -208,7 +208,7 @@ public override void String_Contains_Literal() Assert.Equal( @"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] LIKE (N'%' + N'M') + N'%'", +WHERE CHARINDEX(N'M', [c].[ContactName]) > 0", Sql); } @@ -224,7 +224,7 @@ public override void String_Contains_MethodCall() 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] LIKE (N'%' + @__LocalMethod1_0) + N'%'", +WHERE (CHARINDEX(@__LocalMethod1_0, [c].[ContactName]) > 0) OR (@__LocalMethod1_0 = N'')", Sql); }