diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 8304f1cc1c6..edaee096156 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -7,6 +7,8 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -20,7 +22,33 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal { public class InMemoryExpressionTranslatingExpressionVisitor : ExpressionVisitor { - private const string CompiledQueryParameterPrefix = "__"; + private const string _compiledQueryParameterPrefix = "__"; + + private static readonly MethodInfo _likeMethodInfo + = typeof(DbFunctionsExtensions).GetRuntimeMethod( + nameof(DbFunctionsExtensions.Like), + new[] { typeof(DbFunctions), typeof(string), typeof(string) }); + + private static readonly MethodInfo _likeMethodInfoWithEscape + = typeof(DbFunctionsExtensions).GetRuntimeMethod( + nameof(DbFunctionsExtensions.Like), + new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(string) }); + + private static readonly MethodInfo _inMemoryLikeMethodInfo + = typeof(InMemoryExpressionTranslatingExpressionVisitor) + .GetTypeInfo().GetDeclaredMethod(nameof(InMemoryLike)); + + // Regex special chars defined here: + // https://msdn.microsoft.com/en-us/library/4edbef7e(v=vs.110).aspx + private static readonly char[] _regexSpecialChars + = { '.', '$', '^', '{', '[', '(', '|', ')', '*', '+', '?', '\\' }; + + private static readonly string _defaultEscapeRegexCharsPattern + = BuildEscapeRegexCharsPattern(_regexSpecialChars); + + private static readonly TimeSpan _regexTimeout = TimeSpan.FromMilliseconds(value: 1000.0); + private static string BuildEscapeRegexCharsPattern(IEnumerable regexSpecialChars) + => string.Join("|", regexSpecialChars.Select(c => @"\" + c)); private readonly QueryableMethodTranslatingExpressionVisitor _queryableMethodTranslatingExpressionVisitor; private readonly EntityProjectionFindingExpressionVisitor _entityProjectionFindingExpressionVisitor; @@ -536,6 +564,27 @@ MethodInfo GetMethod() replacedReadExpression)); } + if (methodCallExpression.Method == _likeMethodInfo + || methodCallExpression.Method == _likeMethodInfoWithEscape) + { + // EF.Functions.Like + var visitedArguments = new Expression[3]; + visitedArguments[2] = Expression.Constant(null, typeof(string)); + // Skip first DbFunctions argument + for (var i = 1; i < methodCallExpression.Arguments.Count; i++) + { + var argument = Visit(methodCallExpression.Arguments[i]); + if (TranslationFailed(methodCallExpression.Arguments[i], argument)) + { + return null; + } + + visitedArguments[i - 1] = argument; + } + + return Expression.Call(_inMemoryLikeMethodInfo, visitedArguments); + } + // MethodCall translators var @object = Visit(methodCallExpression.Object); if (TranslationFailed(methodCallExpression.Object, @object)) @@ -740,7 +789,7 @@ protected override Expression VisitParameter(ParameterExpression parameterExpres { Check.NotNull(parameterExpression, nameof(parameterExpression)); - if (parameterExpression.Name.StartsWith(CompiledQueryParameterPrefix, StringComparison.Ordinal)) + if (parameterExpression.Name.StartsWith(_compiledQueryParameterPrefix, StringComparison.Ordinal)) { return Expression.Call( _getParameterValueMethodInfo.MakeGenericMethod(parameterExpression.Type), @@ -809,5 +858,86 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) [DebuggerStepThrough] private bool TranslationFailed(Expression original, Expression translation) => original != null && (translation == null || translation is EntityProjectionExpression); + + private static bool InMemoryLike(string matchExpression, string pattern, string escapeCharacter) + { + //TODO: this fixes https://github.com/aspnet/EntityFramework/issues/8656 by insisting that + // the "escape character" is a string but just using the first character of that string, + // but we may later want to allow the complete string as the "escape character" + // in which case we need to change the way we construct the regex below. + var singleEscapeCharacter = + (escapeCharacter == null || escapeCharacter.Length == 0) + ? (char?)null + : escapeCharacter.First(); + + if (matchExpression == null + || pattern == null) + { + return false; + } + + if (matchExpression.Equals(pattern, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (matchExpression.Length == 0 + || pattern.Length == 0) + { + return false; + } + + var escapeRegexCharsPattern + = singleEscapeCharacter == null + ? _defaultEscapeRegexCharsPattern + : BuildEscapeRegexCharsPattern(_regexSpecialChars.Where(c => c != singleEscapeCharacter)); + + var regexPattern + = Regex.Replace( + pattern, + escapeRegexCharsPattern, + c => @"\" + c, + default, + _regexTimeout); + + var stringBuilder = new StringBuilder(); + + for (var i = 0; i < regexPattern.Length; i++) + { + var c = regexPattern[i]; + var escaped = i > 0 && regexPattern[i - 1] == singleEscapeCharacter; + + switch (c) + { + case '_': + { + stringBuilder.Append(escaped ? '_' : '.'); + break; + } + case '%': + { + stringBuilder.Append(escaped ? "%" : ".*"); + break; + } + default: + { + if (c != singleEscapeCharacter) + { + stringBuilder.Append(c); + } + + break; + } + } + } + + regexPattern = stringBuilder.ToString(); + + return Regex.IsMatch( + matchExpression, + @"\A" + regexPattern + @"\s*\z", + RegexOptions.IgnoreCase | RegexOptions.Singleline, + _regexTimeout); + } } } diff --git a/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs index 7f0204fabb5..36cd2e77901 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.SqlServer.Internal; // ReSharper disable once CheckNamespace @@ -33,7 +34,7 @@ public static bool FreeText( [NotNull] string propertyReference, [NotNull] string freeText, int languageTerm) - => FreeTextCore(propertyReference, freeText, languageTerm); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FreeText))); /// /// @@ -51,12 +52,7 @@ public static bool FreeText( [CanBeNull] this DbFunctions _, [NotNull] string propertyReference, [NotNull] string freeText) - => FreeTextCore(propertyReference, freeText, null); - - private static bool FreeTextCore(string propertyName, string freeText, int? languageTerm) - { - throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(FreeText))); - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FreeText))); /// /// @@ -76,7 +72,7 @@ public static bool Contains( [NotNull] string propertyReference, [NotNull] string searchCondition, int languageTerm) - => ContainsCore(propertyReference, searchCondition, languageTerm); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains))); /// /// @@ -94,12 +90,7 @@ public static bool Contains( [CanBeNull] this DbFunctions _, [NotNull] string propertyReference, [NotNull] string searchCondition) - => ContainsCore(propertyReference, searchCondition, null); - - private static bool ContainsCore(string propertyName, string searchCondition, int? languageTerm) - { - throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(Contains))); - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains))); /// /// Counts the number of year boundaries crossed between the startDate and endDate. @@ -113,7 +104,7 @@ public static int DateDiffYear( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - => endDate.Year - startDate.Year; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffYear))); /// /// Counts the number of year boundaries crossed between the startDate and endDate. @@ -127,9 +118,7 @@ public static int DateDiffYear( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffYear(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffYear))); /// /// Counts the number of year boundaries crossed between the startDate and endDate. @@ -143,7 +132,7 @@ public static int DateDiffYear( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffYear(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffYear))); /// /// Counts the number of year boundaries crossed between the startDate and endDate. @@ -157,9 +146,7 @@ public static int DateDiffYear( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffYear(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffYear))); /// /// Counts the number of month boundaries crossed between the startDate and endDate. @@ -173,7 +160,7 @@ public static int DateDiffMonth( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - => 12 * (endDate.Year - startDate.Year) + endDate.Month - startDate.Month; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMonth))); /// /// Counts the number of month boundaries crossed between the startDate and endDate. @@ -187,9 +174,7 @@ public static int DateDiffMonth( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMonth(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMonth))); /// /// Counts the number of month boundaries crossed between the startDate and endDate. @@ -203,7 +188,7 @@ public static int DateDiffMonth( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffMonth(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMonth))); /// /// Counts the number of month boundaries crossed between the startDate and endDate. @@ -217,9 +202,7 @@ public static int DateDiffMonth( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMonth(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMonth))); /// /// Counts the number of day boundaries crossed between the startDate and endDate. @@ -233,7 +216,7 @@ public static int DateDiffDay( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - => (endDate.Date - startDate.Date).Days; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffDay))); /// /// Counts the number of day boundaries crossed between the startDate and endDate. @@ -247,9 +230,7 @@ public static int DateDiffDay( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffDay(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffDay))); /// /// Counts the number of day boundaries crossed between the startDate and endDate. @@ -263,7 +244,7 @@ public static int DateDiffDay( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffDay(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffDay))); /// /// Counts the number of day boundaries crossed between the startDate and endDate. @@ -277,9 +258,7 @@ public static int DateDiffDay( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffDay(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffDay))); /// /// Counts the number of hour boundaries crossed between the startDate and endDate. @@ -293,12 +272,7 @@ public static int DateDiffHour( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - { - checked - { - return DateDiffDay(_, startDate, endDate) * 24 + endDate.Hour - startDate.Hour; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffHour))); /// /// Counts the number of hour boundaries crossed between the startDate and endDate. @@ -312,9 +286,7 @@ public static int DateDiffHour( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffHour(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffHour))); /// /// Counts the number of hour boundaries crossed between the startDate and endDate. @@ -328,7 +300,7 @@ public static int DateDiffHour( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffHour(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffHour))); /// /// Counts the number of hour boundaries crossed between the startDate and endDate. @@ -342,9 +314,7 @@ public static int DateDiffHour( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffHour(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffHour))); /// /// Counts the number of hour boundaries crossed between the startTimeSpan and endTimeSpan. @@ -358,12 +328,7 @@ public static int DateDiffHour( [CanBeNull] this DbFunctions _, TimeSpan startTimeSpan, TimeSpan endTimeSpan) - { - checked - { - return endTimeSpan.Hours - startTimeSpan.Hours; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffHour))); /// /// Counts the number of hour boundaries crossed between the startTimeSpan and endTimeSpan. @@ -377,9 +342,7 @@ public static int DateDiffHour( [CanBeNull] this DbFunctions _, TimeSpan? startTimeSpan, TimeSpan? endTimeSpan) - => (startTimeSpan.HasValue && endTimeSpan.HasValue) - ? (int?)DateDiffHour(_, startTimeSpan.Value, endTimeSpan.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffHour))); /// /// Counts the number of minute boundaries crossed between the startDate and endDate. @@ -393,12 +356,7 @@ public static int DateDiffMinute( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - { - checked - { - return DateDiffHour(_, startDate, endDate) * 60 + endDate.Minute - startDate.Minute; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMinute))); /// /// Counts the number of minute boundaries crossed between the startDate and endDate. @@ -412,9 +370,7 @@ public static int DateDiffMinute( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMinute(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMinute))); /// /// Counts the number of minute boundaries crossed between the startDate and endDate. @@ -428,7 +384,7 @@ public static int DateDiffMinute( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffMinute(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMinute))); /// /// Counts the number of minute boundaries crossed between the startDate and endDate. @@ -442,9 +398,7 @@ public static int DateDiffMinute( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMinute(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMinute))); /// /// Counts the number of minute boundaries crossed between the startTimeSpan and endTimeSpan. @@ -458,12 +412,7 @@ public static int DateDiffMinute( [CanBeNull] this DbFunctions _, TimeSpan startTimeSpan, TimeSpan endTimeSpan) - { - checked - { - return DateDiffHour(_, startTimeSpan, endTimeSpan) * 60 + endTimeSpan.Minutes - startTimeSpan.Minutes; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMinute))); /// /// Counts the number of minute boundaries crossed between the startTimeSpan and endTimeSpan. @@ -477,9 +426,7 @@ public static int DateDiffMinute( [CanBeNull] this DbFunctions _, TimeSpan? startTimeSpan, TimeSpan? endTimeSpan) - => (startTimeSpan.HasValue && endTimeSpan.HasValue) - ? (int?)DateDiffMinute(_, startTimeSpan.Value, endTimeSpan.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMinute))); /// /// Counts the number of second boundaries crossed between the startDate and endDate. @@ -493,12 +440,7 @@ public static int DateDiffSecond( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - { - checked - { - return DateDiffMinute(_, startDate, endDate) * 60 + endDate.Second - startDate.Second; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffSecond))); /// /// Counts the number of second boundaries crossed between the startDate and endDate. @@ -512,9 +454,7 @@ public static int DateDiffSecond( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffSecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffSecond))); /// /// Counts the number of second boundaries crossed between the startDate and endDate. @@ -528,7 +468,7 @@ public static int DateDiffSecond( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffSecond(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffSecond))); /// /// Counts the number of second boundaries crossed between the startDate and endDate. @@ -542,9 +482,7 @@ public static int DateDiffSecond( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffSecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffSecond))); /// /// Counts the number of second boundaries crossed between the startTimeSpan and endTimeSpan. @@ -558,12 +496,7 @@ public static int DateDiffSecond( [CanBeNull] this DbFunctions _, TimeSpan startTimeSpan, TimeSpan endTimeSpan) - { - checked - { - return DateDiffMinute(_, startTimeSpan, endTimeSpan) * 60 + endTimeSpan.Seconds - startTimeSpan.Seconds; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffSecond))); /// /// Counts the number of second boundaries crossed between the startTimeSpan and endTimeSpan. @@ -577,9 +510,7 @@ public static int DateDiffSecond( [CanBeNull] this DbFunctions _, TimeSpan? startTimeSpan, TimeSpan? endTimeSpan) - => (startTimeSpan.HasValue && endTimeSpan.HasValue) - ? (int?)DateDiffSecond(_, startTimeSpan.Value, endTimeSpan.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffSecond))); /// /// Counts the number of millisecond boundaries crossed between the startDate and endDate. @@ -593,12 +524,7 @@ public static int DateDiffMillisecond( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - { - checked - { - return DateDiffSecond(_, startDate, endDate) * 1000 + endDate.Millisecond - startDate.Millisecond; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMillisecond))); /// /// Counts the number of millisecond boundaries crossed between the startDate and endDate. @@ -612,9 +538,7 @@ public static int DateDiffMillisecond( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMillisecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMillisecond))); /// /// Counts the number of millisecond boundaries crossed between the startDate and endDate. @@ -628,7 +552,7 @@ public static int DateDiffMillisecond( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffMillisecond(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMillisecond))); /// /// Counts the number of millisecond boundaries crossed between the startDate and endDate. @@ -642,9 +566,7 @@ public static int DateDiffMillisecond( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMillisecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMillisecond))); /// /// Counts the number of millisecond boundaries crossed between the startTimeSpan and endTimeSpan. @@ -658,12 +580,7 @@ public static int DateDiffMillisecond( [CanBeNull] this DbFunctions _, TimeSpan startTimeSpan, TimeSpan endTimeSpan) - { - checked - { - return DateDiffSecond(_, startTimeSpan, endTimeSpan) * 1000 + endTimeSpan.Milliseconds - startTimeSpan.Milliseconds; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMillisecond))); /// /// Counts the number of millisecond boundaries crossed between the startTimeSpan and endTimeSpan. @@ -677,9 +594,7 @@ public static int DateDiffMillisecond( [CanBeNull] this DbFunctions _, TimeSpan? startTimeSpan, TimeSpan? endTimeSpan) - => (startTimeSpan.HasValue && endTimeSpan.HasValue) - ? (int?)DateDiffMillisecond(_, startTimeSpan.Value, endTimeSpan.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMillisecond))); /// /// Counts the number of microsecond boundaries crossed between the startDate and endDate. @@ -693,12 +608,7 @@ public static int DateDiffMicrosecond( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - { - checked - { - return (int)((endDate.Ticks - startDate.Ticks) / 10); - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMicrosecond))); /// /// Counts the number of microsecond boundaries crossed between the startDate and endDate. @@ -712,9 +622,7 @@ public static int DateDiffMicrosecond( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMicrosecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMicrosecond))); /// /// Counts the number of microsecond boundaries crossed between the startDate and endDate. @@ -728,7 +636,7 @@ public static int DateDiffMicrosecond( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffMicrosecond(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMicrosecond))); /// /// Counts the number of microsecond boundaries crossed between the startDate and endDate. @@ -742,9 +650,7 @@ public static int DateDiffMicrosecond( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffMicrosecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMicrosecond))); /// /// Counts the number of microsecond boundaries crossed between the startTimeSpan and endTimeSpan. @@ -758,12 +664,7 @@ public static int DateDiffMicrosecond( [CanBeNull] this DbFunctions _, TimeSpan startTimeSpan, TimeSpan endTimeSpan) - { - checked - { - return (int)((endTimeSpan.Ticks - startTimeSpan.Ticks) / 10); - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMicrosecond))); /// /// Counts the number of microsecond boundaries crossed between the startTimeSpan and endTimeSpan. @@ -777,9 +678,7 @@ public static int DateDiffMicrosecond( [CanBeNull] this DbFunctions _, TimeSpan? startTimeSpan, TimeSpan? endTimeSpan) - => (startTimeSpan.HasValue && endTimeSpan.HasValue) - ? (int?)DateDiffMicrosecond(_, startTimeSpan.Value, endTimeSpan.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffMicrosecond))); /// /// Counts the number of nanosecond boundaries crossed between the startDate and endDate. @@ -793,12 +692,7 @@ public static int DateDiffNanosecond( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - { - checked - { - return (int)((endDate.Ticks - startDate.Ticks) * 100); - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffNanosecond))); /// /// Counts the number of nanosecond boundaries crossed between the startDate and endDate. @@ -812,9 +706,7 @@ public static int DateDiffNanosecond( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffNanosecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffNanosecond))); /// /// Counts the number of nanosecond boundaries crossed between the startDate and endDate. @@ -828,7 +720,7 @@ public static int DateDiffNanosecond( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffNanosecond(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffNanosecond))); /// /// Counts the number of nanosecond boundaries crossed between the startDate and endDate. @@ -842,9 +734,7 @@ public static int DateDiffNanosecond( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffNanosecond(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffNanosecond))); /// /// Counts the number of nanosecond boundaries crossed between the startTimeSpan and endTimeSpan. @@ -858,12 +748,7 @@ public static int DateDiffNanosecond( [CanBeNull] this DbFunctions _, TimeSpan startTimeSpan, TimeSpan endTimeSpan) - { - checked - { - return (int)((endTimeSpan.Ticks - startTimeSpan.Ticks) * 100); - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffNanosecond))); /// /// Counts the number of nanosecond boundaries crossed between the startTimeSpan and endTimeSpan. @@ -877,9 +762,7 @@ public static int DateDiffNanosecond( [CanBeNull] this DbFunctions _, TimeSpan? startTimeSpan, TimeSpan? endTimeSpan) - => (startTimeSpan.HasValue && endTimeSpan.HasValue) - ? (int?)DateDiffNanosecond(_, startTimeSpan.Value, endTimeSpan.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffNanosecond))); /// /// Counts the number of week boundaries crossed between the startDate and endDate. @@ -893,38 +776,7 @@ public static int DateDiffWeek( [CanBeNull] this DbFunctions _, DateTime startDate, DateTime endDate) - { - checked - { - var days = (endDate.Date - startDate.Date).Days; - var weeks = (int)days / 7; - var remainingDays = days % 7; - - if (remainingDays > 0) - { - var calendar = CultureInfo.InvariantCulture.Calendar; - - var first = calendar - .GetWeekOfYear( - startDate, - CalendarWeekRule.FirstFullWeek, - DayOfWeek.Sunday); - - var second = calendar - .GetWeekOfYear( - startDate.AddDays(remainingDays), - CalendarWeekRule.FirstFullWeek, - DayOfWeek.Sunday); - - if (first != second) - { - weeks++; - } - } - - return weeks; - } - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffWeek))); /// /// Counts the number of week boundaries crossed between the startDate and endDate. @@ -938,9 +790,7 @@ public static int DateDiffWeek( [CanBeNull] this DbFunctions _, DateTime? startDate, DateTime? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffWeek(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffWeek))); /// /// Counts the number of week boundaries crossed between the startDate and endDate. @@ -954,7 +804,7 @@ public static int DateDiffWeek( [CanBeNull] this DbFunctions _, DateTimeOffset startDate, DateTimeOffset endDate) - => DateDiffWeek(_, startDate.UtcDateTime, endDate.UtcDateTime); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffWeek))); /// /// Counts the number of week boundaries crossed between the startDate and endDate. @@ -968,9 +818,7 @@ public static int DateDiffWeek( [CanBeNull] this DbFunctions _, DateTimeOffset? startDate, DateTimeOffset? endDate) - => (startDate.HasValue && endDate.HasValue) - ? (int?)DateDiffWeek(_, startDate.Value, endDate.Value) - : null; + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateDiffWeek))); /// /// Validate if the given string is a valid date. @@ -982,7 +830,7 @@ public static int DateDiffWeek( public static bool IsDate( [CanBeNull] this DbFunctions _, [NotNull] string expression) - => throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(IsDate))); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(IsDate))); /// /// Initializes a new instance of the structure to the specified year, month, day, hour, minute, second, and millisecond. @@ -1006,7 +854,7 @@ public static DateTime DateTimeFromParts( int minute, int second, int millisecond) - => throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(DateTimeFromParts))); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateTimeFromParts))); /// /// Initializes a new instance of the structure to the specified year, month, day. @@ -1022,7 +870,7 @@ public static DateTime DateFromParts( int year, int month, int day) - => throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(DateFromParts))); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateFromParts))); /// /// Initializes a new instance of the structure to the specified year, month, day, hour, minute, second, fractions, and precision. @@ -1048,7 +896,7 @@ public static DateTime DateTime2FromParts( int second, int fractions, int precision) - => throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(DateTime2FromParts))); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateTime2FromParts))); /// /// Initializes a new instance of the structure to the specified year, month, day, hour, minute, second, fractions, hourOffset, minuteOffset and precision. @@ -1078,7 +926,7 @@ public static DateTimeOffset DateTimeOffsetFromParts( int hourOffset, int minuteOffset, int precision) - => throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(DateTimeOffsetFromParts))); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DateTimeOffsetFromParts))); /// /// Initializes a new instance of the structure to the specified year, month, day, hour and minute. @@ -1098,7 +946,7 @@ public static DateTime SmallDateTimeFromParts( int day, int hour, int minute) - => throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(SmallDateTimeFromParts))); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SmallDateTimeFromParts))); /// /// Initializes a new instance of the structure to the specified hour, minute, second, fractions, and precision. @@ -1118,6 +966,6 @@ public static TimeSpan TimeFromParts( int second, int fractions, int precision) - => throw new InvalidOperationException(SqlServerStrings.FunctionOnClient(nameof(TimeFromParts))); + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(TimeFromParts))); } } diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index 1e07641ef3e..ad9ed077c40 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -161,14 +161,6 @@ public static string DuplicateKeyMismatchedClustering([CanBeNull] object key1, [ GetString("DuplicateKeyMismatchedClustering", nameof(key1), nameof(entityType1), nameof(key2), nameof(entityType2), nameof(table), nameof(keyName)), key1, entityType1, key2, entityType2, table, keyName); - /// - /// The '{methodName}' method is not supported because the query has switched to client-evaluation. Inspect the log to determine which query expressions are triggering client-evaluation. - /// - public static string FunctionOnClient([CanBeNull] object methodName) - => string.Format( - GetString("FunctionOnClient", nameof(methodName)), - methodName); - /// /// Unknown operator type encountered in SqlUnaryExpression. /// diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index 77dcd8c8a20..0c3e94b4a20 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -238,9 +238,6 @@ The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}' but with different clustering. - - The '{methodName}' method is not supported because the query has switched to client-evaluation. Inspect the log to determine which query expressions are triggering client-evaluation. - Both the SqlServerValueGenerationStrategy {generationStrategy} and {otherGenerationStrategy} have been set on property '{propertyName}' on entity type '{entityName}'. Usually this is a mistake. Only use these at the same time if you are sure you understand the consequences. Warning SqlServerEventId.ConflictingValueGenerationStrategiesWarning string string string string diff --git a/src/EFCore/DbFunctionsExtensions.cs b/src/EFCore/DbFunctionsExtensions.cs index f8181f7f564..ff2fc6685b0 100644 --- a/src/EFCore/DbFunctionsExtensions.cs +++ b/src/EFCore/DbFunctionsExtensions.cs @@ -2,11 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace Microsoft.EntityFrameworkCore { @@ -66,101 +63,7 @@ public static bool Like( [CanBeNull] string escapeCharacter) => LikeCore(matchExpression, pattern, escapeCharacter); - // Regex special chars defined here: - // https://msdn.microsoft.com/en-us/library/4edbef7e(v=vs.110).aspx - - private static readonly char[] _regexSpecialChars - = { '.', '$', '^', '{', '[', '(', '|', ')', '*', '+', '?', '\\' }; - - private static readonly string _defaultEscapeRegexCharsPattern - = BuildEscapeRegexCharsPattern(_regexSpecialChars); - - private static readonly TimeSpan _regexTimeout = TimeSpan.FromMilliseconds(value: 1000.0); - - private static string BuildEscapeRegexCharsPattern(IEnumerable regexSpecialChars) - { - return string.Join("|", regexSpecialChars.Select(c => @"\" + c)); - } - private static bool LikeCore(string matchExpression, string pattern, string escapeCharacter) - { - //TODO: this fixes https://github.com/aspnet/EntityFramework/issues/8656 by insisting that - // the "escape character" is a string but just using the first character of that string, - // but we may later want to allow the complete string as the "escape character" - // in which case we need to change the way we construct the regex below. - var singleEscapeCharacter = - (escapeCharacter == null || escapeCharacter.Length == 0) - ? (char?)null - : escapeCharacter.First(); - - if (matchExpression == null - || pattern == null) - { - return false; - } - - if (matchExpression.Equals(pattern, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - if (matchExpression.Length == 0 - || pattern.Length == 0) - { - return false; - } - - var escapeRegexCharsPattern - = singleEscapeCharacter == null - ? _defaultEscapeRegexCharsPattern - : BuildEscapeRegexCharsPattern(_regexSpecialChars.Where(c => c != singleEscapeCharacter)); - - var regexPattern - = Regex.Replace( - pattern, - escapeRegexCharsPattern, - c => @"\" + c, - default, - _regexTimeout); - - var stringBuilder = new StringBuilder(); - - for (var i = 0; i < regexPattern.Length; i++) - { - var c = regexPattern[i]; - var escaped = i > 0 && regexPattern[i - 1] == singleEscapeCharacter; - - switch (c) - { - case '_': - { - stringBuilder.Append(escaped ? '_' : '.'); - break; - } - case '%': - { - stringBuilder.Append(escaped ? "%" : ".*"); - break; - } - default: - { - if (c != singleEscapeCharacter) - { - stringBuilder.Append(c); - } - - break; - } - } - } - - regexPattern = stringBuilder.ToString(); - - return Regex.IsMatch( - matchExpression, - @"\A" + regexPattern + @"\s*\z", - RegexOptions.IgnoreCase | RegexOptions.Singleline, - _regexTimeout); - } + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like))); } } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 55be5adb0d0..6402d3956f7 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2570,6 +2570,14 @@ public static string CanOnlyConfigureExistingNavigations([CanBeNull] object navi GetString("CanOnlyConfigureExistingNavigations", nameof(navigationName), nameof(entityType)), navigationName, entityType); + /// + /// The '{methodName}' method is not supported because the query has switched to client-evaluation. Inspect the log to determine which query expressions are triggering client-evaluation. + /// + public static string FunctionOnClient([CanBeNull] object methodName) + => string.Format( + GetString("FunctionOnClient", nameof(methodName)), + methodName); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 6bfb6746a83..9701fad6b53 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -1300,7 +1300,7 @@ Invalid expression type stored in NavigationMap. - The Include path '{navigationName}->{inverseNavigationName}' results in a cycle. Cycles are not allowed in no-tracking queries. Either use a tracking query or remove the cycle. + The Include path '{navigationName}->{inverseNavigationName}' results in a cycle. Cycles are not allowed in no-tracking queries. Either use a tracking query or remove the cycle. Unhandled method '{methodName}'. @@ -1346,7 +1346,7 @@ Cannot apply DefaultIfEmpty after a client-evaluated projection. - + Conflicting attributes have been applied: the 'Key' attribute on property '{property}' and the 'Keyless' attribute on its entity '{entity}'. Note that the entity will have no key unless you use fluent API to override this. Warning CoreEventId.ConflictingKeylessAndKeyAttributesWarning string string @@ -1360,4 +1360,7 @@ There is no navigation property with name '{navigationName}' on entity type '{entityType}'. Please add the navigation to the entity type before configuring it. + + The '{methodName}' method is not supported because the query has switched to client-evaluation. Inspect the log to determine which query expressions are triggering client-evaluation. + \ No newline at end of file diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index bb67f0f5f0d..c4408de473d 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -299,14 +299,21 @@ public virtual Task GroupBy_with_group_key_access_thru_nested_navigation(bool as [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task GroupBy_with_grouping_key_using_Like(bool async) + public virtual async Task GroupBy_with_grouping_key_using_Like(bool async) { - return AssertQuery( - async, - ss => ss.Set() - .GroupBy(o => EF.Functions.Like(o.CustomerID, "A%")) - .Select(g => new { g.Key, Count = g.Count() }), - elementSorter: e => e.Key); + using var context = CreateContext(); + + var query = context.Set() + .GroupBy(o => EF.Functions.Like(o.CustomerID, "A%")) + .Select(g => new { g.Key, Count = g.Count() }); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Equal(2, result.Count); + Assert.Equal(800, result.Single(t => !t.Key).Count); + Assert.Equal(30, result.Single(t => t.Key).Count); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index ac640fb6ba5..0a46cab8c89 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -1988,23 +1988,26 @@ public virtual Task Filter_non_nullable_value_after_FirstOrDefault_on_empty_coll [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Like_with_non_string_column_using_ToString(bool async) + public virtual async Task Like_with_non_string_column_using_ToString(bool async) { - return AssertQuery( - async, - ss => ss.Set().Where(o => EF.Functions.Like(o.OrderID.ToString(), "%20%")), - entryCount: 8); + using var context = CreateContext(); + + var query = context.Set().Where(o => EF.Functions.Like(o.OrderID.ToString(), "%20%")); + var result = async ? await query.ToListAsync() : query.ToList(); + + Assert.Equal(new[] { 10320, 10420, 10520, 10620, 10720, 10820, 10920, 11020 }, result.Select(e => e.OrderID)); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Like_with_non_string_column_using_double_cast(bool async) + public virtual async Task Like_with_non_string_column_using_double_cast(bool async) { - return AssertQuery( - async, - ss => ss.Set().Where(o => EF.Functions.Like((string)(object)o.OrderID, "%20%")), - ss => ss.Set().Where(o => EF.Functions.Like(o.OrderID.ToString(), "%20%")), - entryCount: 8); + using var context = CreateContext(); + + var query = context.Set().Where(o => EF.Functions.Like((string)(object)o.OrderID, "%20%")); + var result = async ? await query.ToListAsync() : query.ToList(); + + Assert.Equal(new[] { 10320, 10420, 10520, 10620, 10720, 10820, 10920, 11020 }, result.Select(e => e.OrderID)); } [ConditionalTheory] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs index 865e5f3cb7a..6510225af4f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -240,12 +241,12 @@ public void Contains_should_throw_on_client_eval() { var exNoLang = Assert.Throws(() => EF.Functions.Contains("teststring", "teststring")); Assert.Equal( - SqlServerStrings.FunctionOnClient(nameof(SqlServerDbFunctionsExtensions.Contains)), + CoreStrings.FunctionOnClient(nameof(SqlServerDbFunctionsExtensions.Contains)), exNoLang.Message); var exLang = Assert.Throws(() => EF.Functions.Contains("teststring", "teststring", 1033)); Assert.Equal( - SqlServerStrings.FunctionOnClient(nameof(SqlServerDbFunctionsExtensions.Contains)), + CoreStrings.FunctionOnClient(nameof(SqlServerDbFunctionsExtensions.Contains)), exLang.Message); } @@ -547,79 +548,16 @@ public virtual void DateDiff_Week_parameters_null() var count = context.Orders .Count(c => EF.Functions.DateDiffWeek( null, - new DateTimeOffset(1998, 5, 6, 0, 0, 0, TimeSpan.Zero)) == 5); + c.OrderDate) == 5); Assert.Equal(0, count); AssertSql( - @"@__p_0='False' - -SELECT COUNT(*) -FROM [Orders] AS [o] -WHERE @__p_0 = CAST(1 AS bit)"); - } - - [ConditionalFact] - public virtual void DateDiff_Week_server_vs_client_eval_datetime() - { - using var context = CreateContext(); - var endDate = new DateTime(1998, 5, 6, 0, 0, 0); - - var orders = context.Orders - .OrderBy(p => p.OrderID) - .Take(200) - .Select(c => new - { - Weeks = EF.Functions.DateDiffWeek(c.OrderDate, endDate), - c.OrderDate - }); - - foreach (var order in orders) - { - var weeks = EF.Functions.DateDiffWeek(order.OrderDate, endDate); - - Assert.Equal(weeks, order.Weeks); - } - - AssertSql( - @"@__p_0='200' -@__endDate_2='1998-05-06T00:00:00' (Nullable = true) (DbType = DateTime) - -SELECT TOP(@__p_0) DATEDIFF(WEEK, [o].[OrderDate], @__endDate_2) AS [Weeks], [o].[OrderDate] + @"SELECT COUNT(*) FROM [Orders] AS [o] -ORDER BY [o].[OrderID]"); +WHERE DATEDIFF(WEEK, NULL, [o].[OrderDate]) = 5"); } - [ConditionalFact] - public virtual void DateDiff_Week_server_vs_client_eval_datetimeoffset() - { - using var context = CreateContext(); - var endDate = new DateTimeOffset(1998, 5, 6, 0, 0, 0, TimeSpan.Zero); - - var orders = context.Orders - .OrderBy(p => p.OrderID) - .Take(200) - .Select(c => new - { - Weeks = EF.Functions.DateDiffWeek(c.OrderDate, endDate), - c.OrderDate - }); - - foreach (var order in orders) - { - var weeks = EF.Functions.DateDiffWeek(order.OrderDate, endDate); - - Assert.Equal(weeks, order.Weeks); - } - - AssertSql( - @"@__p_0='200' -@__endDate_2='1998-05-06T00:00:00.0000000+00:00' (Nullable = true) - -SELECT TOP(@__p_0) DATEDIFF(WEEK, CAST([o].[OrderDate] AS datetimeoffset), @__endDate_2) AS [Weeks], [o].[OrderDate] -FROM [Orders] AS [o] -ORDER BY [o].[OrderID]"); - } [ConditionalFact] public virtual void IsDate_not_valid() { @@ -676,7 +614,7 @@ public void IsDate_should_throw_on_client_eval() var exIsDate = Assert.Throws(() => EF.Functions.IsDate("#ISDATE#")); Assert.Equal( - SqlServerStrings.FunctionOnClient(nameof(SqlServerDbFunctionsExtensions.IsDate)), + CoreStrings.FunctionOnClient(nameof(SqlServerDbFunctionsExtensions.IsDate)), exIsDate.Message); } diff --git a/test/EFCore.Tests/DbFunctionsTest.cs b/test/EFCore.Tests/DbFunctionsTest.cs index f6516580182..688bce0b682 100644 --- a/test/EFCore.Tests/DbFunctionsTest.cs +++ b/test/EFCore.Tests/DbFunctionsTest.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using Microsoft.EntityFrameworkCore.Diagnostics; using Xunit; // ReSharper disable ArgumentsStyleStringLiteral @@ -10,131 +12,13 @@ namespace Microsoft.EntityFrameworkCore { public class DbFunctionsTest { - private readonly DbFunctions _functions = new DbFunctions(); - - [ConditionalFact] - public void Like_when_null_inputs() - { - Assert.False(_functions.Like(null, "abc")); - Assert.False(_functions.Like("abc", null)); - Assert.False(_functions.Like(null, null)); - } - - [ConditionalFact] - public void Like_when_empty_inputs() - { - Assert.True(_functions.Like("", "")); - Assert.False(_functions.Like("abc", "")); - Assert.False(_functions.Like("", "ABC")); - } - - [ConditionalFact] - public void Like_when_no_wildcards() - { - Assert.True(_functions.Like("abc", "abc")); - Assert.True(_functions.Like("abc", "ABC")); - Assert.True(_functions.Like("ABC", "abc")); - - Assert.False(_functions.Like("ABC", "ab")); - Assert.False(_functions.Like("ab", "abc")); - } - - [ConditionalFact] - public void Like_when_wildcards() - { - Assert.True(_functions.Like("abc", "%")); - Assert.True(_functions.Like("abc", "%_")); - Assert.True(_functions.Like("abc", "___")); - Assert.True(_functions.Like("ABC", "a_c")); - Assert.True(_functions.Like("ABC ", "a_c")); - Assert.True(_functions.Like("ABC ", "%%%")); - Assert.True(_functions.Like("a\\b", "a\\_")); - - Assert.False(_functions.Like("ABC", "__")); - Assert.False(_functions.Like("ab", "___")); - Assert.False(_functions.Like("a_", "a\\_")); - } - - [ConditionalFact] - public void Like_when_regex_chars() - { - Assert.True(_functions.Like("a.c", "a.c")); - Assert.True(_functions.Like("a$c", "a$c")); - Assert.True(_functions.Like("a^c", "a^c")); - Assert.True(_functions.Like("a{c", "a{c")); - Assert.True(_functions.Like("a}c", "a}c")); - Assert.True(_functions.Like("a(c", "a(c")); - Assert.True(_functions.Like("a)c", "a)c")); - Assert.True(_functions.Like("a[c", "a[c")); - Assert.True(_functions.Like("a]c", "a]c")); - Assert.True(_functions.Like("a|c", "a|c")); - Assert.True(_functions.Like("a*c", "a*c")); - Assert.True(_functions.Like("a+c", "a+c")); - Assert.True(_functions.Like("a?c", "a?c")); - Assert.True(_functions.Like("a\\c", "a\\c")); - - Assert.False(_functions.Like("abc", "a.c")); - Assert.False(_functions.Like("abc", "a$c")); - Assert.False(_functions.Like("abc", "a^c")); - Assert.False(_functions.Like("abc", "a{c")); - Assert.False(_functions.Like("abc", "a}c")); - Assert.False(_functions.Like("abc", "a(c")); - Assert.False(_functions.Like("abc", "a)c")); - Assert.False(_functions.Like("abc", "a[c")); - Assert.False(_functions.Like("abc", "a]c")); - Assert.False(_functions.Like("abc", "a|c")); - Assert.False(_functions.Like("abc", "a*c")); - Assert.False(_functions.Like("abc", "a+c")); - Assert.False(_functions.Like("abc", "a?c")); - Assert.False(_functions.Like("abc", "a\\c")); - } - - [ConditionalFact] - public void Like_when_escaping() - { - Assert.True(_functions.Like("50%", "%!%", "!")); - Assert.True(_functions.Like("50%", "50!%", "!")); - Assert.True(_functions.Like("50%", "__!%", "!")); - Assert.True(_functions.Like("_%_%_%", "!_!%!_!%!_!%", "!")); - - Assert.False(_functions.Like("abc", "!%", "!")); - Assert.False(_functions.Like("50%abc", "50!%", "!")); - } - - [ConditionalFact] - public void Like_when_escaping_with_regex_char() - { - Assert.True(_functions.Like("50%", "%|%", "|")); - Assert.True(_functions.Like("50%", "50|%", "|")); - Assert.True(_functions.Like("50%", "__|%", "|")); - Assert.True(_functions.Like("_%_%_%", "|_|%|_|%|_|%", "|")); - - Assert.False(_functions.Like("abc", "|%", "|")); - Assert.False(_functions.Like("50%abc", "50|%", "|")); - } - - [ConditionalFact] - public void Like_when_trailing_spaces() - { - Assert.True(_functions.Like("abc ", "abc ")); - Assert.True(_functions.Like("abc ", "abc")); - Assert.True(_functions.Like("abc ", "ABC ")); - - Assert.False(_functions.Like("ABC", "ab ")); - } - [ConditionalFact] - public void Like_when_multiline() + public void Like_on_client_throws() { - Assert.True(_functions.Like("abc\r\ndef", "abc%")); - Assert.True(_functions.Like("abc\r\ndef", "abc__def")); - Assert.True(_functions.Like("abc\ndef", "abc_def")); - Assert.True(_functions.Like("abc\rdef", "abc_def")); - - Assert.False(_functions.Like("abc\r\ndef", "ab%c")); - Assert.False(_functions.Like("abc\r\ndef", "abc_def")); - Assert.False(_functions.Like("abc\ndef", "abcdef")); - Assert.False(_functions.Like("abc\rdef", "abcdef")); + Assert.Equal( + CoreStrings.FunctionOnClient(nameof(DbFunctionsExtensions.Like)), + Assert.Throws( + () => new DbFunctions().Like("abc", "abc")).Message); } } }