Example #1
0
        private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith)
        {
            var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);

            instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
            pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

            if (pattern is SqlConstantExpression constantExpression)
            {
                // The pattern is constant. Aside from null or empty, we escape all special characters (%, _, \)
                // in C# and send a simple LIKE
                if (!(constantExpression.Value is string constantString))
                {
                    return(_sqlExpressionFactory.Like(
                               instance,
                               _sqlExpressionFactory.Constant(null, stringTypeMapping)));
                }

                return(constantString.Any(c => IsLikeWildChar(c))
                    ? _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Constant(
                               startsWith
                                ? EscapeLikePattern(constantString) + '%'
                                : '%' + EscapeLikePattern(constantString)),
                           _sqlExpressionFactory.Constant(
                               LikeEscapeChar.ToString())) // SQL Server has no char mapping, avoid value conversion warning)
                    : _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Constant(startsWith ? constantString + '%' : '%' + constantString)));
            }

            // The pattern is non-constant, we use LEFT or RIGHT to extract substring and compare.
            // For StartsWith we also first run a LIKE to quickly filter out most non-matching results (sargable, but imprecise
            // because of wildchars).
            if (startsWith)
            {
                return(_sqlExpressionFactory.AndAlso(
                           _sqlExpressionFactory.Like(
                               instance,
                               _sqlExpressionFactory.Add(
                                   instance,
                                   _sqlExpressionFactory.Constant("%"))),
                           _sqlExpressionFactory.Equal(
                               _sqlExpressionFactory.Function(
                                   "LEFT",
                                   new[] { instance, _sqlExpressionFactory.Function("LEN", new[] { pattern }, typeof(int)) },
                                   typeof(string),
                                   stringTypeMapping),
                               pattern)));
            }

            return(_sqlExpressionFactory.Equal(
                       _sqlExpressionFactory.Function(
                           "RIGHT",
                           new[] { instance, _sqlExpressionFactory.Function("LEN", new[] { pattern }, typeof(int)) },
                           typeof(string),
                           stringTypeMapping),
                       pattern));
        }
        SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith)
        {
            var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);

            instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
            pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

            if (pattern is SqlConstantExpression constantExpression)
            {
                // The pattern is constant. Aside from null, we escape all special characters (%, _, \)
                // in C# and send a simple LIKE
                return(constantExpression.Value is string constantPattern
                    ? _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Constant(
                               startsWith
                                ? EscapeLikePattern(constantPattern) + '%'
                                : '%' + EscapeLikePattern(constantPattern)))
                    : _sqlExpressionFactory.Like(instance, _sqlExpressionFactory.Constant(null, stringTypeMapping)));
            }

            // The pattern is non-constant, we use LEFT or RIGHT to extract substring and compare.
            // For StartsWith we also first run a LIKE to quickly filter out most non-matching results (sargable, but imprecise
            // because of wildchars).
            SqlExpression leftRight = _sqlExpressionFactory.Function(
                startsWith ? "LEFT" : "RIGHT",
                new[]
            {
                instance,
                _sqlExpressionFactory.Function("LENGTH", new[] { pattern }, typeof(int))
            },
                typeof(string),
                stringTypeMapping);

            // LEFT/RIGHT of a citext return a text, so for non-default text mappings we apply an explicit cast.
            if (instance.TypeMapping != _textTypeMapping)
            {
                leftRight = _sqlExpressionFactory.Convert(leftRight, typeof(string), instance.TypeMapping);
            }

            // Also add an explicit cast on the pattern; this is only required because of
            // The following is only needed because of https://github.com/aspnet/EntityFrameworkCore/issues/19120
            var castPattern = pattern.TypeMapping == _textTypeMapping
                ? pattern
                : _sqlExpressionFactory.Convert(pattern, typeof(string), pattern.TypeMapping);

            return(startsWith
                ? _sqlExpressionFactory.AndAlso(
                       _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Add(
                               pattern,
                               _sqlExpressionFactory.Constant("%"))),
                       _sqlExpressionFactory.Equal(leftRight, castPattern))
                : _sqlExpressionFactory.Equal(leftRight, castPattern));
        }
        public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList <SqlExpression> arguments)
        {
            if (Equals(method, _methodInfo))
            {
                return(_sqlExpressionFactory.Like(arguments[1], arguments[2]));
            }

            if (Equals(method, _methodInfoWithEscape))
            {
                return(_sqlExpressionFactory.Like(arguments[1], arguments[2], arguments[3]));
            }

            return(null);
        }
Example #4
0
    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual SqlExpression?Translate(
        SqlExpression?instance,
        MethodInfo method,
        IReadOnlyList <SqlExpression> arguments,
        IDiagnosticsLogger <DbLoggerCategory.Query> logger)
    {
        if (Equals(method, MethodInfo))
        {
            return(_sqlExpressionFactory.Like(arguments[1], arguments[2]));
        }

        return(Equals(method, MethodInfoWithEscape)
            ? _sqlExpressionFactory.Like(arguments[1], arguments[2], arguments[3])
            : null);
    }
Example #5
0
        /// <summary>
        ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
        ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
        ///     any release. You should only use it directly in your code with extreme caution and knowing that
        ///     doing so can result in application failures when updating to a new Entity Framework Core release.
        /// </summary>
        public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));

            if (Equals(method, _methodInfo))
            {
                return(_sqlExpressionFactory.Like(arguments[1], arguments[2]));
            }

            if (Equals(method, _methodInfoWithEscape))
            {
                return(_sqlExpressionFactory.Like(arguments[1], arguments[2], arguments[3]));
            }

            return(null);
        }
        public virtual SqlExpression?Translate(
            SqlExpression?instance,
            MethodInfo method,
            IReadOnlyList <SqlExpression> arguments,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));
            Check.NotNull(logger, nameof(logger));

            if (Equals(method, _methodInfo))
            {
                return(_sqlExpressionFactory.Like(arguments[1], arguments[2]));
            }

            if (Equals(method, _methodInfoWithEscape))
            {
                return(_sqlExpressionFactory.Like(arguments[1], arguments[2], arguments[3]));
            }

            return(null);
        }
        /// <summary>
        ///     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.
        /// </summary>
        public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            if (_methodInfos.Any(m => Equals(method, m)))
            {
                var match = _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]);

                var pattern = InferStringTypeMappingOrApplyDefault(
                    arguments[2],
                    match.TypeMapping);

                var excapeChar = arguments.Count == 4
            ? InferStringTypeMappingOrApplyDefault(
                    arguments[3],
                    match.TypeMapping)
            : null;

                return(_sqlExpressionFactory.Like(
                           match,
                           pattern,
                           excapeChar));
            }

            return(null);
        }
Example #8
0
        /// <summary>
        ///     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.
        /// </summary>
        public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments, IDiagnosticsLogger <Microsoft.EntityFrameworkCore.DbLoggerCategory.Query> logger)
        {
            if (_methodInfos.Any(m => Equals(method, m)))
            {
                var match = _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]);

                var pattern = InferStringTypeMappingOrApplyDefault(
                    arguments[2],
                    match.TypeMapping);

                var excapeChar = arguments.Count == 4
            ? InferStringTypeMappingOrApplyDefault(
                    arguments[3],
                    match.TypeMapping)
            : null;

                return(_sqlExpressionFactory.Like(
                           match,
                           pattern,
                           excapeChar));
            }

            return(null);
        }
        public virtual SqlExpression?Translate(
            SqlExpression?instance,
            MethodInfo method,
            IReadOnlyList <SqlExpression> arguments,
            IDiagnosticsLogger <DbLoggerCategory.Query> logger)
        {
            Check.NotNull(method, nameof(method));
            Check.NotNull(arguments, nameof(arguments));
            Check.NotNull(logger, nameof(logger));

            if (instance != null)
            {
                if (_indexOfMethodInfo.Equals(method))
                {
                    var argument          = arguments[0];
                    var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument) !;
                    argument = _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping);

                    SqlExpression charIndexExpression;
                    var           storeType = stringTypeMapping.StoreType;
                    if (string.Equals(storeType, "nvarchar(max)", StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(storeType, "varchar(max)", StringComparison.OrdinalIgnoreCase))
                    {
                        charIndexExpression = _sqlExpressionFactory.Function(
                            "CHARINDEX",
                            new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) },
                            nullable: true,
                            argumentsPropagateNullability: new[] { true, true },
                            typeof(long));

                        charIndexExpression = _sqlExpressionFactory.Convert(charIndexExpression, typeof(int));
                    }
                    else
                    {
                        charIndexExpression = _sqlExpressionFactory.Function(
                            "CHARINDEX",
                            new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) },
                            nullable: true,
                            argumentsPropagateNullability: new[] { true, true },
                            method.ReturnType);
                    }

                    charIndexExpression = _sqlExpressionFactory.Subtract(charIndexExpression, _sqlExpressionFactory.Constant(1));

                    return(_sqlExpressionFactory.Case(
                               new[]
                    {
                        new CaseWhenClause(
                            _sqlExpressionFactory.Equal(
                                argument,
                                _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                            _sqlExpressionFactory.Constant(0))
                    },
                               charIndexExpression));
                }

                if (_replaceMethodInfo.Equals(method))
                {
                    var firstArgument     = arguments[0];
                    var secondArgument    = arguments[1];
                    var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, firstArgument, secondArgument);

                    instance       = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                    firstArgument  = _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping);
                    secondArgument = _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping);

                    return(_sqlExpressionFactory.Function(
                               "REPLACE",
                               new[] { instance, firstArgument, secondArgument },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true, true, true },
                               method.ReturnType,
                               stringTypeMapping));
                }

                if (_toLowerMethodInfo.Equals(method) ||
                    _toUpperMethodInfo.Equals(method))
                {
                    return(_sqlExpressionFactory.Function(
                               _toLowerMethodInfo.Equals(method) ? "LOWER" : "UPPER",
                               new[] { instance },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               method.ReturnType,
                               instance.TypeMapping));
                }

                if (_substringMethodInfo.Equals(method))
                {
                    return(_sqlExpressionFactory.Function(
                               "SUBSTRING",
                               new[]
                    {
                        instance,
                        _sqlExpressionFactory.Add(
                            arguments[0],
                            _sqlExpressionFactory.Constant(1)),
                        arguments[1]
                    },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true, true, true },
                               method.ReturnType,
                               instance.TypeMapping));
                }

                if (_trimStartMethodInfoWithoutArgs?.Equals(method) == true ||
                    (_trimStartMethodInfoWithCharArrayArg.Equals(method)
                     // SqlServer LTRIM does not take arguments
                     && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
                {
                    return(_sqlExpressionFactory.Function(
                               "LTRIM",
                               new[] { instance },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               instance.Type,
                               instance.TypeMapping));
                }

                if (_trimEndMethodInfoWithoutArgs?.Equals(method) == true ||
                    (_trimEndMethodInfoWithCharArrayArg.Equals(method)
                     // SqlServer RTRIM does not take arguments
                     && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
                {
                    return(_sqlExpressionFactory.Function(
                               "RTRIM",
                               new[] { instance },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               instance.Type,
                               instance.TypeMapping));
                }

                if (_trimMethodInfoWithoutArgs?.Equals(method) == true ||
                    (_trimMethodInfoWithCharArrayArg.Equals(method)
                     // SqlServer LTRIM/RTRIM does not take arguments
                     && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
                {
                    return(_sqlExpressionFactory.Function(
                               "LTRIM",
                               new[]
                    {
                        _sqlExpressionFactory.Function(
                            "RTRIM",
                            new[] { instance },
                            nullable: true,
                            argumentsPropagateNullability: new[] { true },
                            instance.Type,
                            instance.TypeMapping)
                    },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true },
                               instance.Type,
                               instance.TypeMapping));
                }

                if (_containsMethodInfo.Equals(method))
                {
                    var pattern           = arguments[0];
                    var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
                    instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                    pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

                    if (pattern is SqlConstantExpression constantPattern)
                    {
                        if (!(constantPattern.Value is string patternValue))
                        {
                            return(_sqlExpressionFactory.Like(
                                       instance,
                                       _sqlExpressionFactory.Constant(null, stringTypeMapping)));
                        }

                        if (patternValue.Length == 0)
                        {
                            return(_sqlExpressionFactory.Constant(true));
                        }

                        return(patternValue.Any(IsLikeWildChar)
                            ? _sqlExpressionFactory.Like(
                                   instance,
                                   _sqlExpressionFactory.Constant($"%{EscapeLikePattern(patternValue)}%"),
                                   _sqlExpressionFactory.Constant(LikeEscapeString))
                            : _sqlExpressionFactory.Like(instance, _sqlExpressionFactory.Constant($"%{patternValue}%")));
                    }

                    return(_sqlExpressionFactory.OrElse(
                               _sqlExpressionFactory.Like(
                                   pattern,
                                   _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                               _sqlExpressionFactory.GreaterThan(
                                   _sqlExpressionFactory.Function(
                                       "CHARINDEX",
                                       new[] { pattern, instance },
                                       nullable: true,
                                       argumentsPropagateNullability: new[] { true, true },
                                       typeof(int)),
                                   _sqlExpressionFactory.Constant(0))));
                }

                if (_startsWithMethodInfo.Equals(method))
                {
                    return(TranslateStartsEndsWith(instance, arguments[0], true));
                }

                if (_endsWithMethodInfo.Equals(method))
                {
                    return(TranslateStartsEndsWith(instance, arguments[0], false));
                }
            }

            if (_isNullOrEmptyMethodInfo.Equals(method))
            {
                var argument = arguments[0];

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.IsNull(argument),
                           _sqlExpressionFactory.Like(
                               argument,
                               _sqlExpressionFactory.Constant(string.Empty))));
            }

            if (_isNullOrWhiteSpaceMethodInfo.Equals(method))
            {
                var argument = arguments[0];

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.IsNull(argument),
                           _sqlExpressionFactory.Equal(
                               argument,
                               _sqlExpressionFactory.Constant(string.Empty, argument.TypeMapping))));
            }

            if (_firstOrDefaultMethodInfoWithoutArgs.Equals(method))
            {
                var argument = arguments[0];
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[] { argument, _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1) },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType));
            }

            if (_lastOrDefaultMethodInfoWithoutArgs.Equals(method))
            {
                var argument = arguments[0];
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[]
                {
                    argument,
                    _sqlExpressionFactory.Function(
                        "LEN",
                        new[] { argument },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        typeof(int)),
                    _sqlExpressionFactory.Constant(1)
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType));
            }

            return(null);
        }
        private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith)
        {
            var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);

            instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
            pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

            if (pattern is SqlConstantExpression constantExpression)
            {
                // The pattern is constant. Aside from null or empty, we escape all special characters (%, _, \)
                // in C# and send a simple LIKE
                if (!(constantExpression.Value is string constantString))
                {
                    return(_sqlExpressionFactory.Like(
                               instance,
                               _sqlExpressionFactory.Constant(null, stringTypeMapping)));
                }

                return(constantString.Any(c => IsLikeWildChar(c))
                    ? _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Constant(
                               startsWith
                                ? EscapeLikePattern(constantString) + '%'
                                : '%' + EscapeLikePattern(constantString)),
                           _sqlExpressionFactory.Constant(
                               LikeEscapeChar.ToString())) // SQL Server has no char mapping, avoid value conversion warning)
                    : _sqlExpressionFactory.Like(
                           instance,
                           _sqlExpressionFactory.Constant(startsWith ? constantString + '%' : '%' + constantString)));
            }

            // The pattern is non-constant, we use LEFT or RIGHT to extract substring and compare.
            if (startsWith)
            {
                return(_sqlExpressionFactory.Equal(
                           _sqlExpressionFactory.Function(
                               "LEFT",
                               new[]
                {
                    instance,
                    _sqlExpressionFactory.Function(
                        "LEN",
                        new[] { pattern },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        typeof(int))
                },
                               nullable: true,
                               argumentsPropagateNullability: new[] { true, true },
                               typeof(string),
                               stringTypeMapping),
                           pattern));
            }

            return(_sqlExpressionFactory.Equal(
                       _sqlExpressionFactory.Function(
                           "RIGHT",
                           new[]
            {
                instance,
                _sqlExpressionFactory.Function(
                    "LEN",
                    new[] { pattern },
                    nullable: true,
                    argumentsPropagateNullability: new[] { true },
                    typeof(int))
            },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true },
                           typeof(string),
                           stringTypeMapping),
                       pattern));
        }
Example #11
0
    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual SqlExpression?Translate(
        SqlExpression?instance,
        MethodInfo method,
        IReadOnlyList <SqlExpression> arguments,
        IDiagnosticsLogger <DbLoggerCategory.Query> logger)
    {
        if (instance != null)
        {
            if (IndexOfMethodInfo.Equals(method))
            {
                return(TranslateIndexOf(instance, method, arguments[0], null));
            }

            if (IndexOfMethodInfoWithStartingPosition.Equals(method))
            {
                return(TranslateIndexOf(instance, method, arguments[0], arguments[1]));
            }

            if (ReplaceMethodInfo.Equals(method))
            {
                var firstArgument     = arguments[0];
                var secondArgument    = arguments[1];
                var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, firstArgument, secondArgument);

                instance       = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                firstArgument  = _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping);
                secondArgument = _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping);

                return(_sqlExpressionFactory.Function(
                           "REPLACE",
                           new[] { instance, firstArgument, secondArgument },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType,
                           stringTypeMapping));
            }

            if (ToLowerMethodInfo.Equals(method) ||
                ToUpperMethodInfo.Equals(method))
            {
                return(_sqlExpressionFactory.Function(
                           ToLowerMethodInfo.Equals(method) ? "LOWER" : "UPPER",
                           new[] { instance },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           method.ReturnType,
                           instance.TypeMapping));
            }

            if (SubstringMethodInfoWithOneArg.Equals(method))
            {
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[]
                {
                    instance,
                    _sqlExpressionFactory.Add(
                        arguments[0],
                        _sqlExpressionFactory.Constant(1)),
                    _sqlExpressionFactory.Function(
                        "LEN",
                        new[] { instance },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        typeof(int))
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType,
                           instance.TypeMapping));
            }

            if (SubstringMethodInfoWithTwoArgs.Equals(method))
            {
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[]
                {
                    instance,
                    _sqlExpressionFactory.Add(
                        arguments[0],
                        _sqlExpressionFactory.Constant(1)),
                    arguments[1]
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true, true, true },
                           method.ReturnType,
                           instance.TypeMapping));
            }

            if (TrimStartMethodInfoWithoutArgs?.Equals(method) == true ||
                (TrimStartMethodInfoWithCharArrayArg.Equals(method)
                 // SqlServer LTRIM does not take arguments
                 && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
            {
                return(_sqlExpressionFactory.Function(
                           "LTRIM",
                           new[] { instance },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           instance.Type,
                           instance.TypeMapping));
            }

            if (TrimEndMethodInfoWithoutArgs?.Equals(method) == true ||
                (TrimEndMethodInfoWithCharArrayArg.Equals(method)
                 // SqlServer RTRIM does not take arguments
                 && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
            {
                return(_sqlExpressionFactory.Function(
                           "RTRIM",
                           new[] { instance },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           instance.Type,
                           instance.TypeMapping));
            }

            if (TrimMethodInfoWithoutArgs?.Equals(method) == true ||
                (TrimMethodInfoWithCharArrayArg.Equals(method)
                 // SqlServer LTRIM/RTRIM does not take arguments
                 && ((arguments[0] as SqlConstantExpression)?.Value as Array)?.Length == 0))
            {
                return(_sqlExpressionFactory.Function(
                           "LTRIM",
                           new[]
                {
                    _sqlExpressionFactory.Function(
                        "RTRIM",
                        new[] { instance },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true },
                        instance.Type,
                        instance.TypeMapping)
                },
                           nullable: true,
                           argumentsPropagateNullability: new[] { true },
                           instance.Type,
                           instance.TypeMapping));
            }

            if (ContainsMethodInfo.Equals(method))
            {
                var pattern           = arguments[0];
                var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
                instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);

                if (pattern is SqlConstantExpression constantPattern)
                {
                    if (!(constantPattern.Value is string patternValue))
                    {
                        return(_sqlExpressionFactory.Like(
                                   instance,
                                   _sqlExpressionFactory.Constant(null, stringTypeMapping)));
                    }

                    if (patternValue.Length == 0)
                    {
                        return(_sqlExpressionFactory.Constant(true));
                    }

                    return(patternValue.Any(IsLikeWildChar)
                        ? _sqlExpressionFactory.Like(
                               instance,
                               _sqlExpressionFactory.Constant($"%{EscapeLikePattern(patternValue)}%"),
                               _sqlExpressionFactory.Constant(LikeEscapeString))
                        : _sqlExpressionFactory.Like(instance, _sqlExpressionFactory.Constant($"%{patternValue}%")));
                }

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.Like(
                               pattern,
                               _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)),
                           _sqlExpressionFactory.GreaterThan(
                               _sqlExpressionFactory.Function(
                                   "CHARINDEX",
                                   new[] { pattern, instance },
                                   nullable: true,
                                   argumentsPropagateNullability: new[] { true, true },
                                   typeof(int)),
                               _sqlExpressionFactory.Constant(0))));
            }

            if (StartsWithMethodInfo.Equals(method))
            {
                return(TranslateStartsEndsWith(instance, arguments[0], true));
            }

            if (EndsWithMethodInfo.Equals(method))
            {
                return(TranslateStartsEndsWith(instance, arguments[0], false));
            }
        }

        if (IsNullOrEmptyMethodInfo.Equals(method))
        {
            var argument = arguments[0];

            return(_sqlExpressionFactory.OrElse(
                       _sqlExpressionFactory.IsNull(argument),
                       _sqlExpressionFactory.Like(
                           argument,
                           _sqlExpressionFactory.Constant(string.Empty))));
        }

        if (IsNullOrWhiteSpaceMethodInfo.Equals(method))
        {
            var argument = arguments[0];

            return(_sqlExpressionFactory.OrElse(
                       _sqlExpressionFactory.IsNull(argument),
                       _sqlExpressionFactory.Equal(
                           argument,
                           _sqlExpressionFactory.Constant(string.Empty, argument.TypeMapping))));
        }

        if (FirstOrDefaultMethodInfoWithoutArgs.Equals(method))
        {
            var argument = arguments[0];
            return(_sqlExpressionFactory.Function(
                       "SUBSTRING",
                       new[] { argument, _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1) },
                       nullable: true,
                       argumentsPropagateNullability: new[] { true, true, true },
                       method.ReturnType));
        }

        if (LastOrDefaultMethodInfoWithoutArgs.Equals(method))
        {
            var argument = arguments[0];
            return(_sqlExpressionFactory.Function(
                       "SUBSTRING",
                       new[]
            {
                argument,
                _sqlExpressionFactory.Function(
                    "LEN",
                    new[] { argument },
                    nullable: true,
                    argumentsPropagateNullability: new[] { true },
                    typeof(int)),
                _sqlExpressionFactory.Constant(1)
            },
                       nullable: true,
                       argumentsPropagateNullability: new[] { true, true, true },
                       method.ReturnType));
        }

        return(null);
    }
        public SqlExpression?Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments)
        {
            if (_toLowerMethodInfo.Equals(method) ||
                _toUpperMethodInfo.Equals(method))
            {
                return(_sqlExpressionFactory.Function(
                           _toLowerMethodInfo.Equals(method) ? "LOWER" : "UPPER",
                           new[] { instance },
                           method.ReturnType,
                           instance.TypeMapping));
            }

            if (_substringMethodInfo.Equals(method))
            {
                return(_sqlExpressionFactory.Function(
                           "SUBSTRING",
                           new[] { instance, _sqlExpressionFactory.Add(arguments[0], _sqlExpressionFactory.Constant(1)), arguments[1] },
                           method.ReturnType,
                           instance.TypeMapping));
            }

            if (_isNullOrWhiteSpaceMethodInfo.Equals(method))
            {
                var argument = arguments[0];

                return(_sqlExpressionFactory.OrElse(
                           _sqlExpressionFactory.IsNull(argument),
                           _sqlExpressionFactory.Equal(
                               _sqlExpressionFactory.Function(
                                   "TRIM", new[] { argument }, argument.Type, argument.TypeMapping),
                               _sqlExpressionFactory.Constant(string.Empty))));
            }

            if (_containsMethodInfo.Equals(method))
            {
                var pattern           = arguments[0];
                var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);

                instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
                pattern  = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);
                if (pattern is SqlConstantExpression constantExpression)
                {
                    if (constantExpression.Value is string constantString)
                    {
                        return(constantString.Any(c => IsLikeWildChar(c))
                            ? _sqlExpressionFactory.Like(
                                   instance,
                                   _sqlExpressionFactory.Constant('%' + EscapeLikePattern(constantString) + '%'))
                            : _sqlExpressionFactory.Like(
                                   instance,
                                   _sqlExpressionFactory.Constant('%' + constantString + '%')));
                    }
                }
            }

            if (_startsWithMethodInfo.Equals(method))
            {
                return(TranslateStartsEndsWith(instance, arguments[0], true));
            }

            if (_endsWithMethodInfo.Equals(method))
            {
                return(TranslateStartsEndsWith(instance, arguments[0], false));
            }

            return(null);
        }