/// <summary> /// Generate a new type for the group-by results and return an instance of the results with the aggregated values. /// </summary> /// <param name="groupByTrasformation">The group-by transformation clause.</param> /// <param name="keys">The collection of the group-by keys.</param> /// <param name="aggragatedValues">The results of the group-by aggregation.</param> /// <param name="keyType">The group-by key type.</param> /// <param name="context">The OData query context.</param> /// <returns>The group-by results as <see cref="IQueryable"/>.</returns> private IQueryable ProjectGroupedResult(ApplyGroupbyClause groupByTrasformation, IQueryable keys, object[] aggragatedValues, Type keyType, ODataQueryContext context) { List <object> result = new List <object>(); var keyProperties = keyType.GetProperties(); var projectionType = this.GetAggregationResultProjectionType(groupByTrasformation, keyType); int i = 0; foreach (var key in keys) { var objToProject = Activator.CreateInstance(projectionType); var pi = projectionType.GetProperty(groupByTrasformation.Aggregate.Alias); pi.SetValue(objToProject, aggragatedValues[i]); foreach (var key_pi in keyProperties) { if (key_pi.Name == "ComparerInstance") { continue; } pi = projectionType.GetProperty(key_pi.Name); pi.SetValue(objToProject, key_pi.GetValue(key)); } result.Add(objToProject); i++; } return(ExpressionHelpers.Cast(projectionType, result.AsQueryable())); }
/// <summary> /// Helper method to create a new dynamic type for the group-by key. /// </summary> /// <param name="transformation">The group-by query.</param> /// <returns>The type of the key which was dynamically generated.</returns> internal Type GetGroupByKeyType(ApplyGroupbyClause transformation) { Contract.Assert(transformation != null); Contract.Assert(transformation.SelectedStatements != null); Contract.Assert(transformation.SelectedStatements.Any()); var keyProperties = new List <Tuple <Type, string> >(); var selectedStatementsDictionary = GetSelectedStatementsDictionary(transformation.SelectedStatements); foreach (var statement in selectedStatementsDictionary) { // simple property var statementString = statement.Value.First(); if ((statement.Value.Count() == 1) && (statementString == statement.Key)) { string samplingMethod, alias, samplingProperty; GroupByImplementation.GetSamplingMethod(statementString, out samplingMethod, out alias, out samplingProperty); if (samplingMethod != null) { var pi = this.Context.ElementClrType.GetProperty(samplingProperty); if (pi == null) { throw new ArgumentException(string.Format("Entity does not contain {0}", samplingProperty)); } var implementation = SamplingMethodsImplementations.GetAggregationImplementation(samplingMethod); var samplingType = implementation.GetResultType(pi.PropertyType); keyProperties.Add(new Tuple <Type, string>(samplingType, alias)); } else { var propName = statementString.TrimMethodCall().Split(' ').First(); var pi = this.Context.ElementClrType.GetProperty(propName); if (pi == null) { throw new ArgumentException(string.Format("Entity does not contain {0}", propName)); } keyProperties.Add(new Tuple <Type, string>(pi.PropertyType, pi.Name)); } } else { // complex property var propName = statement.Key.TrimMethodCall(); var pi = this.Context.ElementClrType.GetProperty(propName); if (pi == null) { throw new ArgumentException(string.Format("Entity does not contain {0}", propName)); } var newPropertyType = this.GenerateComplexType(pi.PropertyType, statement.Value); keyProperties.Add(new Tuple <Type, string>(newPropertyType, propName)); } } return(AggregationTypesGenerator.CreateType(keyProperties.Distinct(new TypeStringTupleComapere()).ToList(), Context, true)); }
private static void GetGroupByParams(string value, out IQueryable data, out ODataQueryContext context, out ODataQuerySettings settings, out ApplyGroupbyClause groupByClause, out DefaultAssembliesResolver assembliesResolver, out GroupByImplementation groupByImplementation, out Type keyType, out IEnumerable <LambdaExpression> propertiesToGroupByExpressions) { string queryOption = "$apply"; data = TestDataSource.CreateData(); settings = new ODataQuerySettings() { PageSize = 2000, HandleNullPropagation = HandleNullPropagationOption.False }; var _settings = settings; var model = TestModelBuilder.CreateModel(new Type[] { typeof(Category), typeof(Product), typeof(Sales) }); context = new ODataQueryContext(model, typeof(Sales), new ODataPath(new ODataPathSegment[] { new EntitySetPathSegment("Sales") })); var _context = context; IEdmNavigationSource source = model.FindDeclaredEntitySet("Sales"); var parser = new ODataQueryOptionParser(model, model.FindDeclaredType("System.Web.OData.Aggregation.Tests.Common.Sales"), source, new Dictionary <string, string>() { { queryOption, value } }); var applyCaluse = parser.ParseApply(); groupByClause = applyCaluse.Transformations.First().Item2 as ApplyGroupbyClause; assembliesResolver = new DefaultAssembliesResolver(); var _assembliesResolver = assembliesResolver; groupByImplementation = new GroupByImplementation() { Context = context }; keyType = groupByImplementation.GetGroupByKeyType(groupByClause); var entityParam = Expression.Parameter(context.ElementClrType, "$it"); propertiesToGroupByExpressions = groupByClause.SelectedPropertiesExpressions.Select( exp => FilterBinder.Bind(exp, _context.ElementClrType, _context.Model, _assembliesResolver, _settings, entityParam)); }
/// <summary> /// Get the type to use for returning aggregation results in a group-by query. /// </summary> /// <param name="groupByTrasformation">The group by transformation.</param> /// <param name="keyType">The type of a group by key.</param> /// <returns>The new dynamic type.</returns> private Type GetAggregationResultProjectionType(ApplyGroupbyClause groupByTrasformation, Type keyType) { if (groupByTrasformation.Aggregate == null) { throw new ArgumentException("group by without aggregate"); } var keyProperties = new List <Tuple <Type, string> >(); var aggregationImplementation = AggregationImplementations <AggregationImplementationBase> .GetAggregationImplementation(groupByTrasformation.Aggregate.AggregationMethod); var aliasType = aggregationImplementation.GetResultType(Context.ElementClrType, groupByTrasformation.Aggregate); keyProperties.Add(new Tuple <Type, string>(aliasType, groupByTrasformation.Aggregate.Alias)); foreach (var prop in keyType.GetProperties()) { if (prop.Name == "ComparerInstance") { continue; } keyProperties.Add(new Tuple <Type, string>(prop.PropertyType, prop.Name)); } return(AggregationTypesGenerator.CreateType(keyProperties.Distinct(new TypeStringTupleComapere()).ToList(), Context, true)); }
/// <summary> /// Execute group-by without aggregation on the results. /// </summary> /// <param name="query">The collection to group.</param> /// <param name="maxResults">The max number of elements in a result set.</param> /// <param name="transformation">The group-by transformation as <see cref="ApplyGroupbyClause"/>.</param> /// <param name="keyType">The key type to group by.</param> /// <param name="propertiesToGroupByExpressions">Lambda expression that represents access to the properties to group by.</param> /// <returns>The results of the group by transformation as <see cref="IQueryable"/>.</returns> public IQueryable DoGroupBy(IQueryable query, int maxResults, ApplyGroupbyClause transformation, Type keyType, IEnumerable <LambdaExpression> propertiesToGroupByExpressions) { var propToGroupBy = (propertiesToGroupByExpressions != null) ? propertiesToGroupByExpressions.ToArray() : null; var keySelector = this.GetGroupByProjectionLambda(transformation.SelectedStatements.ToArray(), keyType, propToGroupBy); object comparer = keyType.GetProperty("ComparerInstance").GetValue(null); var results = ExpressionHelpers.GroupBy(query, keySelector, this.Context.ElementClrType, keyType, comparer); var keys = this.GetGroupingKeys(results, keyType, this.Context.ElementClrType); // if group by is not supported in this IQueriable provider convert the grouping into memory implementation object convertedResult = null; int numberOfTempResults; if (QueriableProviderAdapter.ConvertionIsRequiredAsExpressionIfNotSupported(keys, maxResults, out convertedResult, out numberOfTempResults)) { keys = convertedResult as IQueryable; } var keysToReturn = ExpressionHelpers.Distinct(keyType, keys); return(keysToReturn); }
/// <summary> /// Execute group-by with aggregation on the results. /// </summary> /// <param name="query">The collection to group.</param> /// <param name="maxResults">The max number of elements in a result set.</param> /// <param name="transformation">The group-by transformation as <see cref="ApplyGroupbyClause"/>.</param> /// <param name="keyType">The key type to group by.</param> /// <param name="propertiesToGroupByExpressions">Lambda expression that represents access to the properties to group by.</param> /// <param name="propertyToAggregateExpression">Lambda expression that represents access to the property to aggregate.</param> /// <param name="keys">Output the collection keys of the grouped results.</param> /// <param name="aggragatedValues">Output the aggregated results.</param> public void DoAggregatedGroupBy( IQueryable query, int maxResults, ApplyGroupbyClause transformation, Type keyType, IEnumerable <LambdaExpression> propertiesToGroupByExpressions, LambdaExpression propertyToAggregateExpression, out IQueryable keys, out object[] aggragatedValues) { var propToGroupBy = (propertiesToGroupByExpressions != null) ? propertiesToGroupByExpressions.ToArray() : null; var keySelector = this.GetGroupByProjectionLambda(transformation.SelectedStatements.ToArray(), keyType, propToGroupBy); object comparer = keyType.GetProperty("ComparerInstance").GetValue(null); var groupingResults = ExpressionHelpers.GroupBy(query, keySelector, this.Context.ElementClrType, keyType, comparer); var aggregationImplementation = AggregationMethodsImplementations.GetAggregationImplementation(transformation.Aggregate.AggregationMethod); // if group by is not supported in this IQueriable provider convert the grouping into memory implementation object convertedResult = null; int numberOfTempResults; if (QueriableProviderAdapter.ConvertionIsRequiredAsExpressionIfNotSupported(groupingResults, maxResults, out convertedResult, out numberOfTempResults)) { groupingResults = convertedResult as IQueryable; } var resType = typeof(List <>).MakeGenericType(this.Context.ElementClrType); keys = this.GetGroupingKeys(groupingResults, keyType, this.Context.ElementClrType); var groupedValues = this.GetGroupingValues(groupingResults, keyType, resType, this.Context.ElementClrType); // In case of paging due to memory execution of unsupported functions keys may not be distinct. // Here we make sure that keys are distinct and all values that belong to a key are written to the right list. List <object> distictKeys; List <object> groupedValuesPerKey; if (numberOfTempResults > 1) { this.CombineValuesListsPerKey(keys.AllElements(), groupedValues.AllElements(), out distictKeys, out groupedValuesPerKey); keys = distictKeys.AsQueryable(); } groupedValuesPerKey = groupedValues.AllElements(); int numberOfResults = groupedValuesPerKey.Count; var tmpAggragatedValues = new object[numberOfResults]; var projectionLambda = AggregationImplementationBase.GetProjectionLambda(this.Context.ElementClrType, transformation.Aggregate, propertyToAggregateExpression); string[] aggregationParams = AggregationImplementationBase.GetAggregationParams(transformation.Aggregate.AggregationMethod); Parallel.For(0, numberOfResults, (i => { IQueryable valuesAsQueryable; if (groupedValuesPerKey[i] is IEnumerable <object> ) { valuesAsQueryable = ExpressionHelpers.Cast(this.Context.ElementClrType, (groupedValuesPerKey[i] as IEnumerable <Object>).AsQueryable()); } else { valuesAsQueryable = ExpressionHelpers.Cast(this.Context.ElementClrType, (new List <Object>() { groupedValuesPerKey[i] }).AsQueryable()); } IQueryable queryToUse = valuesAsQueryable; if (transformation.Aggregate.AggregatableProperty.Contains('/')) { queryToUse = AggregationImplementationBase.FilterNullValues(query, this.Context.ElementClrType, transformation.Aggregate); } var aggragationResult = aggregationImplementation.DoAggregatinon(this.Context.ElementClrType, queryToUse, transformation.Aggregate, projectionLambda, aggregationParams); tmpAggragatedValues[i] = aggragationResult; })); aggragatedValues = tmpAggragatedValues; }