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 })); }
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))")); }
public void FilterNullValuesWithValidArgs(ApplyAggregateClause clause) { if (clause.AggregatableProperty.Contains('/')) { this.FilterNullValuesWithValidArgsComplexProperty(clause); } else { this.FilterNullValuesWithValidArgsSimpleProperty(clause); } }
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(); }); }
/// <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); }
/// <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); }
/// <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)); }
/// <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)); }
/// <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)); }
/// <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)); }
/// <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 }); }
/// <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);
/// <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);
/// <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); }
/// <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)); } }
/// <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)); }
/// <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)); }
/// <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)); }