static bool IsAggregateResultWithCustomShaper(MethodInfo method)
                            {
                                if (method.IsGenericMethod)
                                {
                                    method = method.GetGenericMethodDefinition();
                                }

                                return(QueryableMethods.IsAverageWithoutSelector(method) ||
                                       QueryableMethods.IsAverageWithSelector(method) ||
                                       method == QueryableMethods.MaxWithoutSelector ||
                                       method == QueryableMethods.MaxWithSelector ||
                                       method == QueryableMethods.MinWithoutSelector ||
                                       method == QueryableMethods.MinWithSelector ||
                                       QueryableMethods.IsSumWithoutSelector(method) ||
                                       QueryableMethods.IsSumWithSelector(method));
                            }
Пример #2
0
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            Check.NotNull(node, nameof(node));

            if (!node.Method.IsGenericMethod)
            {
                return(node);
            }

            if (!node.Method.IsSupportedNativelyOrByComposition())
            {
                throw new NotSupportedException($"Method {node.Method.Name} cannot be converter to a valid query.");
            }

            MethodInfo genericDefinition = node.Method.GetGenericMethodDefinition();

            #region Bool member to constants

            if (!_isVisitingWhereMethodOrChild && genericDefinition == QueryableMethods.Where)
            {
                _isVisitingWhereMethodOrChild = true;
                Expression whereNode = VisitMethodCall(node);
                _isVisitingWhereMethodOrChild = false;
                return(whereNode);
            }

            #endregion

            #region Multi-Where Optimization

            if (genericDefinition == QueryableMethods.Where)
            {
                if (_nextWhereCalls.Count == 0)
                {
                    _nextWhereCalls.Enqueue(node);
                    Expression       tail                = Visit(node.Arguments[0]);
                    LambdaExpression currentLambda       = node.GetLambda();
                    Expression       conditionExpression = Visit(currentLambda.Body);
                    _nextWhereCalls.Dequeue();

                    while (_nextWhereCalls.Count > 0)
                    {
                        Expression nextWhereBody = Visit(_nextWhereCalls.Dequeue().GetLambdaBody());
                        conditionExpression = Expression.And(nextWhereBody, conditionExpression);
                    }

                    Expression conditionLambda = conditionExpression.WrapInLambda(currentLambda.Parameters);
                    return(Expression.Call(typeof(Queryable), nameof(Queryable.Where),
                                           node.Method.GetGenericArguments(), tail, conditionLambda));
                }

                _nextWhereCalls.Enqueue(node);
                return(Visit(node.Arguments[0]));
            }

            #endregion

            #region Min/Max

            // Min(d => d.Property) == OrderBy(d => d.Property).Take(1).Select(d => d.Property).Min()
            if (genericDefinition == QueryableMethods.MinWithSelector)
            {
                return(node
                       .SubstituteWithQueryableCall(nameof(Queryable.OrderBy))
                       .WrapInTake(1)
                       .WrapInSelect(node)
                       .WrapInMinMax(QueryableMethods.MinWithoutSelector));
            }

            // Max(d => d.Property) == OrderByDescending(d => d.Property).Take(1).Select(d => d.Property).Max()
            if (genericDefinition == QueryableMethods.MaxWithSelector)
            {
                return(node
                       .SubstituteWithQueryableCall(nameof(Queryable.OrderByDescending))
                       .WrapInTake(1)
                       .WrapInSelect(node)
                       .WrapInMinMax(QueryableMethods.MaxWithoutSelector));
            }

            #endregion

            #region Sum/Average

            // Sum(d => d.Property) == Select(d => d.Property).Sum()
            if (QueryableMethods.IsSumWithSelector(genericDefinition))
            {
                return(node
                       .SubstituteWithSelect(node)
                       .WrapInAverageSum(node));
            }

            // Average(d => d.Property) == Select(d => d.Property).Average()
            if (QueryableMethods.IsAverageWithSelector(genericDefinition))
            {
                return(node
                       .SubstituteWithSelect(node)
                       .WrapInAverageSum(node));
            }

            #endregion

            #region Any/All

            // Any() => Take(1).Any()
            if (genericDefinition == QueryableMethods.AnyWithoutPredicate)
            {
                return(node
                       .WrapInTake(1)
                       .WrapInMethodWithoutSelector(QueryableMethods.AnyWithoutPredicate));
            }

            // Any(d => condition) == Where(d => condition).Take(1).Any()
            if (genericDefinition == QueryableMethods.AnyWithPredicate)
            {
                return(node
                       .SubstituteWithWhere()
                       .WrapInTake(1)
                       .WrapInMethodWithoutSelector(QueryableMethods.AnyWithoutPredicate));
            }

            // All(d => condition) == Where(d => !condition).Take(1).Any()
            if (genericDefinition == QueryableMethods.All)
            {
                return(node
                       .SubstituteWithWhere(true)
                       .WrapInTake(1)
                       .WrapInMethodWithoutSelector(QueryableMethods.AnyWithoutPredicate));
            }

            #endregion

            #region Single/SingleOrDefault

            // Single() == Take(2).Single()
            if (genericDefinition == QueryableMethods.SingleWithoutPredicate)
            {
                return(node
                       .SubstituteWithTake(2)
                       .WrapInMethodWithoutSelector(QueryableMethods.SingleWithoutPredicate));
            }

            // SingleOrDefault() == Take(2).SingleOrDefault()
            if (genericDefinition == QueryableMethods.SingleOrDefaultWithoutPredicate)
            {
                return(node
                       .SubstituteWithTake(2)
                       .WrapInMethodWithoutSelector(QueryableMethods.SingleOrDefaultWithoutPredicate));
            }

            // Single(d => condition) == Where(d => condition).Take(2).Single()
            if (genericDefinition == QueryableMethods.SingleWithPredicate)
            {
                return(node
                       .SubstituteWithWhere()
                       .SubstituteWithTake(2)
                       .WrapInMethodWithoutSelector(QueryableMethods.SingleWithoutPredicate));
            }

            // SingleOrDefault(d => condition) == Where(d => condition).Take(2).SingleOrDefault()
            if (genericDefinition == QueryableMethods.SingleOrDefaultWithPredicate)
            {
                return(node
                       .SubstituteWithWhere()
                       .SubstituteWithTake(2)
                       .WrapInMethodWithoutSelector(QueryableMethods.SingleOrDefaultWithoutPredicate));
            }

            #endregion

            #region First/FirstOrDefault

            // First() == Take(1).First()
            if (genericDefinition == QueryableMethods.FirstWithoutPredicate)
            {
                return(node
                       .SubstituteWithTake(1)
                       .WrapInMethodWithoutSelector(QueryableMethods.FirstWithoutPredicate));
            }

            // FirstOrDefault() == Take(1).FirstOrDefault()
            if (genericDefinition == QueryableMethods.FirstOrDefaultWithoutPredicate)
            {
                return(node
                       .SubstituteWithTake(1)
                       .WrapInMethodWithoutSelector(QueryableMethods.FirstOrDefaultWithoutPredicate));
            }

            // First(d => condition) == Where(d => condition).Take(1).First()
            if (genericDefinition == QueryableMethods.FirstWithPredicate)
            {
                return(node
                       .SubstituteWithWhere()
                       .SubstituteWithTake(1)
                       .WrapInMethodWithoutSelector(QueryableMethods.FirstWithoutPredicate));
            }

            // FirstOrDefault(d => condition) == Where(d => condition).Take(1).FirstOrDefault()
            if (genericDefinition == QueryableMethods.FirstOrDefaultWithPredicate)
            {
                return(node
                       .SubstituteWithWhere()
                       .SubstituteWithTake(1)
                       .WrapInMethodWithoutSelector(QueryableMethods.FirstOrDefaultWithoutPredicate));
            }

            #endregion

            #region Last/LastOrDefault

            // Last() == Last()
            // LastOrDefault() == LastOrDefault()
            if (genericDefinition == QueryableMethods.LastWithoutPredicate ||
                genericDefinition == QueryableMethods.LastOrDefaultWithoutPredicate)
            {
                return(node);
            }

            // Last(d => condition) == Where(d => condition).Last()
            if (genericDefinition == QueryableMethods.LastWithPredicate)
            {
                return(node
                       .SubstituteWithWhere()
                       .WrapInMethodWithoutSelector(QueryableMethods.LastWithoutPredicate));
            }

            // LastOrDefault(d => condition) == Where(d => condition).LastOrDefault()
            if (genericDefinition == QueryableMethods.LastOrDefaultWithPredicate)
            {
                return(node
                       .SubstituteWithWhere()
                       .WrapInMethodWithoutSelector(QueryableMethods.LastOrDefaultWithoutPredicate));
            }

            #endregion

            return(base.VisitMethodCall(node));
        }
Пример #3
0
    public virtual SqlExpression?Translate(
        MethodInfo method,
        EnumerableExpression source,
        IReadOnlyList <SqlExpression> arguments,
        IDiagnosticsLogger <DbLoggerCategory.Query> logger)
    {
        if (method.DeclaringType == typeof(Queryable))
        {
            var methodInfo = method.IsGenericMethod
                ? method.GetGenericMethodDefinition()
                : method;
            switch (methodInfo.Name)
            {
            case nameof(Queryable.Average)
                when(QueryableMethods.IsAverageWithoutSelector(methodInfo) ||
                     QueryableMethods.IsAverageWithSelector(methodInfo)) &&
                source.Selector is SqlExpression averageSqlExpression:
                var averageInputType = averageSqlExpression.Type;
                if (averageInputType == typeof(int) ||
                    averageInputType == typeof(long))
                {
                    averageSqlExpression = _sqlExpressionFactory.ApplyDefaultTypeMapping(
                        _sqlExpressionFactory.Convert(averageSqlExpression, typeof(double)));
                }

                return(averageInputType == typeof(float)
                        ? _sqlExpressionFactory.Convert(
                           _sqlExpressionFactory.AggregateFunction(
                               "avg",
                               new[] { averageSqlExpression },
                               nullable: true,
                               argumentsPropagateNullability: FalseArrays[1],
                               source,
                               typeof(double)),
                           averageSqlExpression.Type,
                           averageSqlExpression.TypeMapping)
                        : _sqlExpressionFactory.AggregateFunction(
                           "avg",
                           new[] { averageSqlExpression },
                           nullable: true,
                           argumentsPropagateNullability: FalseArrays[1],
                           source,
                           averageSqlExpression.Type,
                           averageSqlExpression.TypeMapping));

            // PostgreSQL COUNT() always returns bigint, so we need to downcast to int
            case nameof(Queryable.Count)
                when methodInfo == QueryableMethods.CountWithoutPredicate ||
                methodInfo == QueryableMethods.CountWithPredicate:
                var countSqlExpression = (source.Selector as SqlExpression) ?? _sqlExpressionFactory.Fragment("*");
                return(_sqlExpressionFactory.Convert(
                           _sqlExpressionFactory.AggregateFunction(
                               "count",
                               new[] { countSqlExpression },
                               nullable: false,
                               argumentsPropagateNullability: FalseArrays[1],
                               source,
                               typeof(long)),
                           typeof(int), _typeMappingSource.FindMapping(typeof(int))));

            case nameof(Queryable.LongCount)
                when methodInfo == QueryableMethods.LongCountWithoutPredicate ||
                methodInfo == QueryableMethods.LongCountWithPredicate:
                var longCountSqlExpression = (source.Selector as SqlExpression) ?? _sqlExpressionFactory.Fragment("*");
                return(_sqlExpressionFactory.AggregateFunction(
                           "count",
                           new[] { longCountSqlExpression },
                           nullable: false,
                           argumentsPropagateNullability: FalseArrays[1],
                           source,
                           typeof(long)));

            case nameof(Queryable.Max)
                when(methodInfo == QueryableMethods.MaxWithoutSelector ||
                     methodInfo == QueryableMethods.MaxWithSelector) &&
                source.Selector is SqlExpression maxSqlExpression:
                return(_sqlExpressionFactory.AggregateFunction(
                           "max",
                           new[] { maxSqlExpression },
                           nullable: true,
                           argumentsPropagateNullability: FalseArrays[1],
                           source,
                           maxSqlExpression.Type,
                           maxSqlExpression.TypeMapping));

            case nameof(Queryable.Min)
                when(methodInfo == QueryableMethods.MinWithoutSelector ||
                     methodInfo == QueryableMethods.MinWithSelector) &&
                source.Selector is SqlExpression minSqlExpression:
                return(_sqlExpressionFactory.AggregateFunction(
                           "min",
                           new[] { minSqlExpression },
                           nullable: true,
                           argumentsPropagateNullability: FalseArrays[1],
                           source,
                           minSqlExpression.Type,
                           minSqlExpression.TypeMapping));

            // In PostgreSQL SUM() doesn't return the same type as its argument for smallint, int and bigint.
            // Cast to get the same type.
            // http://www.postgresql.org/docs/current/static/functions-aggregate.html
            case nameof(Queryable.Sum)
                when(QueryableMethods.IsSumWithoutSelector(methodInfo) ||
                     QueryableMethods.IsSumWithSelector(methodInfo)) &&
                source.Selector is SqlExpression sumSqlExpression:
                var sumInputType = sumSqlExpression.Type;

                // Note that there is no Sum over short in LINQ
                if (sumInputType == typeof(int))
                {
                    return(_sqlExpressionFactory.Convert(
                               _sqlExpressionFactory.AggregateFunction(
                                   "sum",
                                   new[] { sumSqlExpression },
                                   nullable: true,
                                   argumentsPropagateNullability: FalseArrays[1],
                                   source,
                                   typeof(long)),
                               sumInputType,
                               sumSqlExpression.TypeMapping));
                }

                if (sumInputType == typeof(long))
                {
                    return(_sqlExpressionFactory.Convert(
                               _sqlExpressionFactory.AggregateFunction(
                                   "sum",
                                   new[] { sumSqlExpression },
                                   nullable: true,
                                   argumentsPropagateNullability: FalseArrays[1],
                                   source,
                                   typeof(decimal)),
                               sumInputType,
                               sumSqlExpression.TypeMapping));
                }

                return(_sqlExpressionFactory.AggregateFunction(
                           "sum",
                           new[] { sumSqlExpression },
                           nullable: true,
                           argumentsPropagateNullability: FalseArrays[1],
                           source,
                           sumInputType,
                           sumSqlExpression.TypeMapping));
            }
        }

        return(null);
    }