示例#1
0
        public override object DoAggregatinon(Type elementType, IQueryable query, ApplyAggregateClause transformation, LambdaExpression propertyToAggregateExpression, params string[] parameters)
        {
            var aggregatedProperyType = GetAggregatedPropertyType(elementType, transformation.AggregatableProperty);
            var selected = GetItemsToQuery(elementType, query, propertyToAggregateExpression, aggregatedProperyType);

            MethodInfo minMethod = this.GetType().GetMethod("FirstPlusOne");

            return(minMethod.Invoke(null, new[] { selected }));
        }
示例#2
0
 public void FilterNullValuesWithValidArgsComplexProperty(ApplyAggregateClause clause)
 {
     "path is a complex property".Given(() => clause.AggregatableProperty.Contains('/').Should().BeTrue());
     "When calling FilterNullValues".When(
         () =>
         FilterNullValues(TestDataSource.CreateData(), typeof(Sales), clause)
         .Expression.ToString()
         .ShouldBeEquivalentTo(
             "System.Collections.Generic.List`1[System.Web.OData.Aggregation.Tests.Common.Sales].Where(e => (e.Product != null)).Where(e => (e.Product.Category != null))"));
 }
示例#3
0
 public void FilterNullValuesWithValidArgs(ApplyAggregateClause clause)
 {
     if (clause.AggregatableProperty.Contains('/'))
     {
         this.FilterNullValuesWithValidArgsComplexProperty(clause);
     }
     else
     {
         this.FilterNullValuesWithValidArgsSimpleProperty(clause);
     }
 }
示例#4
0
        public void FilterNullValuesWithValidArgsSimpleProperty(ApplyAggregateClause clause)
        {
            var data = TestDataSource.CreateData();

            "path is a simple property".Given(() => clause.AggregatableProperty.Contains('/').Should().BeFalse());
            "When calling FilterNullValues".When(
                () =>
            {
                var res = FilterNullValues(data, typeof(Sales), clause);
                (res == data).Should().BeTrue();
            });
        }
示例#5
0
        /// <summary>
        /// Create a projection lambda if one was not provided.
        /// </summary>
        /// <param name="elementType">The type of entities.</param>
        /// <param name="transformation">The transformation clause created by the parser.</param>
        /// <param name="propertyToAggregateExpression">Projection Expression to that defines access to the property to aggregate.</param>
        /// <returns>A lambda expression to the property to aggregate.</returns>
        public static LambdaExpression GetProjectionLambda(Type elementType, ApplyAggregateClause transformation,
                                                           LambdaExpression propertyToAggregateExpression)
        {
            LambdaExpression projectionLambda;

            if (propertyToAggregateExpression == null)
            {
                projectionLambda = GetProjectionLambda(elementType, transformation.AggregatableProperty);
            }
            else
            {
                projectionLambda = propertyToAggregateExpression as LambdaExpression;
            }
            return(projectionLambda);
        }
示例#6
0
        /// <summary>
        /// Execute the apply query to the given IQueryable.
        /// </summary>
        /// <remarks>
        /// The <see cref="ODataQuerySettings.HandleNullPropagation"/> property specifies
        /// how this method should handle null propagation.
        /// </remarks>
        /// <param name="query">The original <see cref="IQueryable"/>.</param>
        /// <param name="querySettings">The <see cref="ODataQuerySettings"/> that contains all the query application related settings.</param>
        /// <param name="assembliesResolver">IAssembliesResolver provided by the framework.</param>
        /// <param name="aggregationWindowSize">The max number of results to aggregate in each aggregation batch</param>
        /// <returns>The new <see cref="IQueryable"/> After the apply query has been applied to.</returns>
        public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssembliesResolver assembliesResolver, int aggregationWindowSize)
        {
            if (query == null)
            {
                throw Error.ArgumentNull("query");
            }
            if (querySettings == null)
            {
                throw Error.ArgumentNull("querySettings");
            }
            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            ApplyClause applyClause = ApplyClause;

            Contract.Assert(applyClause != null);

            // Ensure we have decided how to handle null propagation
            ODataQuerySettings updatedSettings = querySettings;

            if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default)
            {
                updatedSettings = new ODataQuerySettings(updatedSettings);
                updatedSettings.HandleNullPropagation = HandleNullPropagationOption.False;
            }

            var maxResults = querySettings.PageSize ?? 2000;

            if (aggregationWindowSize != 0)
            {
                maxResults = (aggregationWindowSize < MAX_AGGREGATION_WINDOW_SIZE)
                    ? aggregationWindowSize
                    : MAX_AGGREGATION_WINDOW_SIZE;
            }

            // Call InterceptingProvider.Intercept that will create a new <see cref="InterceptingProvider"/> and set its visitors.
            // InterceptingProvider will wrap the actual IQueryable to implement unsupported operations in memory.
            var        mi      = _intercept_mi.MakeGenericMethod(this.Context.ElementClrType);
            IQueryable results = mi.Invoke(null, new object[] { query, maxResults, null }) as IQueryable;

            // Each transformation is the input for the next transformation in the transformations list
            foreach (var transformation in applyClause.Transformations)
            {
                switch (transformation.Item1)
                {
                case "aggregate":
                    ApplyAggregateClause aggregateClause = transformation.Item2 as ApplyAggregateClause;
                    if (aggregateClause == null)
                    {
                        throw Error.Argument("aggregation transformation type mismatch", transformation.Item2);
                    }

                    LambdaExpression propertyToAggregateExpression = FilterBinder.Bind(aggregateClause.AggregatablePropertyExpression, Context.ElementClrType, Context.Model, assembliesResolver, updatedSettings);

                    var aggregationImplementation = AggregationMethodsImplementations.GetAggregationImplementation(aggregateClause.AggregationMethod);
                    if (results.Provider is InterceptingProvider)
                    {
                        (results.Provider as InterceptingProvider).Combiner =
                            aggregationImplementation.CombineTemporaryResults;
                    }

                    IQueryable queryToUse = results;
                    if (aggregateClause.AggregatableProperty.Contains('/'))
                    {
                        queryToUse = AggregationImplementationBase.FilterNullValues(query, this.Context.ElementClrType, aggregateClause);
                    }

                    var      projectionLambda  = AggregationImplementationBase.GetProjectionLambda(this.Context.ElementClrType, aggregateClause, propertyToAggregateExpression);
                    string[] aggregationParams = AggregationImplementationBase.GetAggregationParams(aggregateClause.AggregationMethod);
                    var      aggragationResult = aggregationImplementation.DoAggregatinon(this.Context.ElementClrType, queryToUse, aggregateClause, projectionLambda, aggregationParams);
                    var      aliasType         = aggregationImplementation.GetResultType(this.Context.ElementClrType, aggregateClause);

                    results = this.ProjectResult(aggragationResult, aggregateClause.Alias, aliasType);
                    Context = new ODataQueryContext(this.Context.Model, results.ElementType);

                    break;

                case "groupby":
                    IEnumerable <LambdaExpression> propertiesToGroupByExpressions = null;
                    var groupByImplementation = new GroupByImplementation()
                    {
                        Context = this.Context
                    };
                    var groupByClause = transformation.Item2 as ApplyGroupbyClause;
                    if (groupByClause == null)
                    {
                        throw Error.Argument("aggregation transformation type mismatch", transformation.Item2);
                    }

                    var entityParam = Expression.Parameter(this.Context.ElementClrType, "$it");
                    if (groupByClause.SelectedPropertiesExpressions != null)
                    {
                        propertiesToGroupByExpressions =
                            groupByClause.SelectedPropertiesExpressions.Select(
                                exp =>
                                FilterBinder.Bind(
                                    exp, this.Context.ElementClrType, this.Context.Model, assembliesResolver, updatedSettings, entityParam));
                    }

                    var keyType = groupByImplementation.GetGroupByKeyType(groupByClause);
                    if (groupByClause.Aggregate == null)
                    {
                        // simple group-by without aggregation method
                        results = groupByImplementation.DoGroupBy(results, maxResults, groupByClause, keyType, propertiesToGroupByExpressions);
                    }
                    else
                    {
                        IQueryable keys = null;
                        propertyToAggregateExpression = FilterBinder.Bind(groupByClause.Aggregate.AggregatablePropertyExpression, Context.ElementClrType, Context.Model, assembliesResolver, updatedSettings);

                        object[] aggragatedValues = null;
                        groupByImplementation.DoAggregatedGroupBy(results, maxResults, groupByClause, keyType, propertiesToGroupByExpressions, propertyToAggregateExpression,
                                                                  out keys, out aggragatedValues);

                        results = ProjectGroupedResult(groupByClause, keys, aggragatedValues, keyType, Context);
                    }

                    Context = new ODataQueryContext(this.Context.Model, results.ElementType);
                    break;

                case "filter":
                    var filterClause = transformation.Item2 as ApplyFilterClause;
                    if (filterClause == null)
                    {
                        throw Error.Argument("aggregation transformation type mismatch", transformation.Item2);
                    }

                    var filterImplementation = new FilterImplementation()
                    {
                        Context = this.Context
                    };
                    results = filterImplementation.DoFilter(results, filterClause, querySettings, this._queryOptionParser);

                    break;

                default:
                    throw Error.NotSupported("aggregation not supported", transformation.Item1);
                }
            }

            object convertedResult = null;

            return(results);
        }
示例#7
0
 /// <summary>
 /// The type of the result of Average aggregation method is: double
 /// </summary>
 /// <param name="elementType">>The type of entities</param>
 /// <param name="transformation">The transformation clause created by the parser</param>
 /// <returns>result type</returns>
 public override Type GetResultType(Type elementType, ApplyAggregateClause transformation)
 {
     return(typeof(double));
 }
示例#8
0
        /// <summary>
        /// Implement the Average aggregation method
        /// </summary>
        /// <param name="elementType">The type of entities</param>
        /// <param name="query">The collection</param>
        /// <param name="transformation">The transformation clause created by the parser</param>
        /// <param name="propertyToAggregateExpression">Projection Expression that defines access to the property to aggregate</param>
        /// <param name="parameters">A list of string parameters sent to the aggregation method</param>
        /// <returns>The Sum result</returns>
        public override object DoAggregatinon(Type elementType, IQueryable collection, ApplyAggregateClause transformation, LambdaExpression propertyToAggregateExpression, params string[] parameters)
        {
            var pwr            = double.Parse(parameters.First());
            var selectedValues = GetSelectedValues(elementType, collection, transformation, propertyToAggregateExpression);

            return(SumPwr(selectedValues, pwr));
        }
示例#9
0
        /// <summary>
        /// Implement the Average aggregation method
        /// </summary>
        /// <param name="elementType">The type of entities</param>
        /// <param name="query">The collection</param>
        /// <param name="transformation">The transformation clause created by the parser</param>
        /// <param name="propertyToAggregateExpression">Projection Expression that defines access to the property to aggregate</param>
        /// <param name="paramaters">A list of string parameters sent to the aggregation method</param>
        /// <returns>The Sum result</returns>
        public override object DoAggregatinon(Type elementType, IQueryable collection, ApplyAggregateClause transformation, LambdaExpression propertyToAggregateExpression, params string[] parameters)
        {
            var selectedValues = GetSelectedValues(elementType, collection, transformation, propertyToAggregateExpression);

            return(Total(selectedValues));
        }
示例#10
0
        /// <summary>
        /// Implement the Sum aggregation method
        /// </summary>
        /// <param name="elementType">The type of entities</param>
        /// <param name="query">The collection</param>
        /// <param name="transformation">The transformation clause created by the parser</param>
        /// <param name="propertyToAggregateExpression">Projection Expression that defines access to the property to aggregate</param>
        /// <param name="paramaters">A list of string parameters sent to the aggregation method</param>
        /// <returns>The Sum result</returns>
        public override object DoAggregatinon(Type elementType, IQueryable collection, ApplyAggregateClause transformation, LambdaExpression propertyToAggregateExpression, params string[] parameters)
        {
            var resultType     = this.GetResultType(elementType, transformation);
            var selectedValues = GetSelectedValues(elementType, collection, transformation, propertyToAggregateExpression);

            if (resultType == typeof(decimal))
            {
                return(TotalDecimal(selectedValues));
            }

            if (resultType == typeof(float))
            {
                return(TotalFloat(selectedValues));
            }

            return(Total(selectedValues));
        }
示例#11
0
        /// <summary>
        /// Parse queries such as : "groupby(Customer/Name,Customer/ID,Product/Name,Account)" or "groupby((Customer/Country,Product/Name), aggregate(Amount with sum as Total))"
        /// </summary>
        /// <param name="apply"></param>
        /// <param name="query"></param>
        /// <param name="oDataUriParserConfiguration"></param>
        /// <param name="edmType"></param>
        /// <param name="edmNavigationSource"></param>
        /// <returns></returns>
        private static ApplyGroupbyClause ParseGroupBy(ApplyClause apply, string query,
                                                       ODataUriParserConfiguration oDataUriParserConfiguration, IEdmType edmType, IEdmNavigationSource edmNavigationSource)
        {
            string selectQuery;
            ApplyAggregateClause aggregateClause = null;

            query = IsolateQuery(query, UriQueryConstants.GroupbyTransformation);
            if (query.StartsWith("(") && query.EndsWith(")"))
            {
                query = query.TrimOne('(', ')');
            }
            var p = query.IndexOf(UriQueryConstants.AggregateTransformation + '(');

            if (p == -1)
            {
                if (query.StartsWith("(") && query.EndsWith(")"))
                {
                    query = query.TrimOne('(', ')');
                }
                selectQuery = query;
            }
            else
            {
                selectQuery     = query.Substring(0, p).Trim().Trim(',').TrimOne('(', ')');
                aggregateClause = ParseAggregate(apply, query.Substring(p), oDataUriParserConfiguration, edmType, edmNavigationSource);
            }

            var selectedStatements = selectQuery.Split(',');
            var withIndex          = selectQuery.IndexOf("with");

            if (withIndex > 0)
            {
                selectedStatements = selectQuery.Substring(0, withIndex).Split(',');
                var withStatement = selectQuery.Substring(withIndex, selectQuery.Length - withIndex);
                selectedStatements[selectedStatements.Count() - 1] =
                    selectedStatements[selectedStatements.Count() - 1] + withStatement;
            }

            string aggregationMethod, alias;
            List <ExpressionClause> aggregatablePropertyExpressions = null;

            try
            {
                aggregatablePropertyExpressions =
                    selectedStatements.Select(statement => ODataQueryOptionParser.ParseExpressionImplementation(
                                                  GetAggregatableProperty(statement, false, out alias, out aggregationMethod),
                                                  oDataUriParserConfiguration,
                                                  edmType,
                                                  edmNavigationSource)).ToList();
            }
            catch (Exception)
            {
                //parsing of some expressions (like property on enum such as DateTimeOffset/Minute) are not supported so ODataQueryOptionParser.ParseExpressionImplementation will fail
                aggregatablePropertyExpressions = null;
            }


            return(new ApplyGroupbyClause()
            {
                SelectedStatements = selectedStatements,
                SelectedPropertiesExpressions = aggregatablePropertyExpressions,
                Aggregate = aggregateClause,
                Apply = apply
            });
        }
示例#12
0
 /// <summary>
 /// Determines the type that is returned from the aggregation method.
 /// </summary>
 /// <param name="elementType">The element type on which the aggregation method operates.</param>
 /// <param name="transformation">The name of the aggregation transformation.</param>
 /// <returns>The type that is returned from the aggregation method.</returns>
 public abstract Type GetResultType(Type elementType, ApplyAggregateClause transformation);
示例#13
0
 /// <summary>
 /// Execute the aggregation method.
 /// </summary>
 /// <param name="elementType">The element type on which the aggregation method operates.</param>
 /// <param name="collection">The collection on which to execute.</param>
 /// <param name="transformation">The name of the aggregation transformation.</param>
 /// <param name="propertyToAggregateExpression">Expression to the property to aggregate.</param>
 /// <param name="parameters">A list of string parameters sent to the aggregation method</param>
 /// <returns>The result of the aggregation.</returns>
 public abstract object DoAggregatinon(Type elementType, IQueryable collection, ApplyAggregateClause transformation, LambdaExpression propertyToAggregateExpression, params string[] parameters);
示例#14
0
        /// <summary>
        /// Create an expression that validate that a path to a property does not contain null values
        /// For example: for the query product/category/name, create expression such as x.product != null && x.product.Category != null.
        /// </summary>
        /// <param name="elementType">The element type.</param>
        /// <param name="transformation">The aggregation transformation.</param>
        /// <returns>The result with null propagation checks.</returns>
        public static IQueryable FilterNullValues(IQueryable query, Type elementType, ApplyAggregateClause transformation)
        {
            var        entityParam = Expression.Parameter(elementType, "e");
            IQueryable queryToUse  = query;

            var accessorExpressions      = GetProjectionExpressions(transformation.AggregatableProperty, entityParam).ToArray();
            var accessorExpressionsToUse = new List <Expression>();

            for (int i = 0; i < accessorExpressions.Length - 1; i++)
            {
                accessorExpressionsToUse.Add(
                    Expression.Lambda(Expression.MakeBinary(ExpressionType.NotEqual, accessorExpressions[i], Expression.Constant(null)),
                                      entityParam));
            }

            foreach (var exp in accessorExpressionsToUse)
            {
                queryToUse = ExpressionHelpers.Where(queryToUse, exp, elementType);
            }
            return(queryToUse);
        }
示例#15
0
        /// <summary>
        /// Implement the Count-Distinct aggregation method
        /// </summary>
        /// <param name="elementType">The type of entities</param>
        /// <param name="query">The collection</param>
        /// <param name="transformation">The transformation clause created by the parser</param>
        /// <param name="propertyToAggregateExpression">Projection Expression that defines access to the property to aggregate</param>
        /// <param name="parameters">A list of string parameters sent to the aggregation method</param>
        /// <returns>The Sum result</returns>
        public override object DoAggregatinon(Type elementType, IQueryable collection, ApplyAggregateClause transformation, LambdaExpression propertyToAggregateExpression, params string[] parameters)
        {
            var propertyType   = GetAggregatedPropertyType(elementType, transformation.AggregatableProperty);
            var selectedValues = GetSelectedValues(elementType, collection, transformation, propertyToAggregateExpression);

            //call: (selected.AsQueryable() as IQueryable<double>).Ditinct();
            var distinct = ExpressionHelpers.Distinct(propertyType, selectedValues);

            try
            {
                //call: (distinct.AsQueryable() as IQueryable<double>).Count();
                return(ExpressionHelpers.Count(propertyType, distinct));
            }
            catch (TargetInvocationException)
            {
                //salve a problem in mongo that throw the error "No further operators may follow Distinct in a LINQ query." when trying to construct the expression tree.
                distinct = ExpressionHelpers.Cast(propertyType, distinct.AllElements().AsQueryable());
                return(ExpressionHelpers.Count(propertyType, distinct));
            }
        }
示例#16
0
        /// <summary>
        /// Implement the Max aggregation method
        /// </summary>
        /// <param name="elementType">The type of entities</param>
        /// <param name="query">The collection</param>
        /// <param name="transformation">The transformation clause created by the parser</param>
        /// <param name="propertyToAggregateExpression">Projection Expression that defines access to the property to aggregate</param>
        /// <param name="parameters">A list of string parameters sent to the aggregation method</param>
        /// <returns>The Sum result</returns>
        public override object DoAggregatinon(Type elementType, IQueryable collection, ApplyAggregateClause transformation, LambdaExpression propertyToAggregateExpression, params string[] parameters)
        {
            var resultType     = this.GetResultType(elementType, transformation);
            var selectedValues = GetSelectedValues(elementType, collection, transformation, propertyToAggregateExpression);

            //call: (selected.AsQueryable() as IQueryable<double>).Max();
            return(ExpressionHelpers.Max(resultType, selectedValues));
        }
示例#17
0
 /// <summary>
 /// Get the type of the aggregation result
 /// </summary>
 /// <param name="elementType">the entity type</param>
 /// <param name="transformation">The transformation clause created by the parser</param>
 /// <returns>The type of the aggregation result.</returns>
 public override Type GetResultType(Type elementType, ApplyAggregateClause transformation)
 {
     return(this.GetAggregatedPropertyType(elementType, transformation.AggregatableProperty));
 }
示例#18
0
        /// <summary>
        /// Get the selected values to aggregate
        /// </summary>
        /// <param name="elementType">The type of entities</param>
        /// <param name="collection">The collection to aggregate</param>
        /// <param name="transformation">The transformation clause created by the parser</param>
        /// <param name="propertyToAggregateExpression">Projection Expression that defines access to the property to aggregate</param>
        /// <returns>The selected values to aggregate</returns>
        protected IQueryable GetSelectedValues(Type elementType, IQueryable collection, ApplyAggregateClause transformation,
                                               LambdaExpression propertyToAggregateExpression)
        {
            var aggregatedProperyType = this.GetAggregatedPropertyType(elementType, transformation.AggregatableProperty);
            var projectionDelegate    = GetProjectionDelegate(elementType, transformation.AggregatableProperty, propertyToAggregateExpression);

            return(GetItemsToQuery(elementType, collection, projectionDelegate, aggregatedProperyType));
        }