private List <NamedPropertyExpression> CreateGroupByMemberAssignments(IEnumerable <GroupByPropertyNode> nodes) { var properties = new List <NamedPropertyExpression>(); foreach (var grpProp in nodes) { var propertyName = grpProp.Name; if (grpProp.Expression != null) { properties.Add(new NamedPropertyExpression(Expression.Constant(propertyName), WrapConvert(BindAccessor(grpProp.Expression)))); } else { var wrapperProperty = typeof(GroupByWrapper).GetProperty(GroupByContainerProperty); List <MemberAssignment> wta = new List <MemberAssignment>(); wta.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(CreateGroupByMemberAssignments(grpProp.ChildTransformations)))); properties.Add(new NamedPropertyExpression(Expression.Constant(propertyName), Expression.MemberInit(Expression.New(typeof(GroupByWrapper)), wta))); } } return(properties); }
private IQueryable BindGroupBy(IQueryable query) { LambdaExpression groupLambda = null; Type elementType = query.ElementType; if (_groupingProperties != null && _groupingProperties.Any()) { // Generates expression // .GroupBy($it => new DynamicTypeWrapper() // { // GroupByContainer => new AggregationPropertyContainer() { // Name = "Prop1", // Value = $it.Prop1, // Next = new AggregationPropertyContainer() { // Name = "Prop2", // Value = $it.Prop2, // int // Next = new LastInChain() { // Name = "Prop3", // Value = $it.Prop3 // } // } // } // }) List <NamedPropertyExpression> properties = CreateGroupByMemberAssignments(_groupingProperties); var wrapperProperty = typeof(GroupByWrapper).GetProperty(GroupByContainerProperty); List <MemberAssignment> wta = new List <MemberAssignment>(); wta.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(properties))); groupLambda = Expression.Lambda(Expression.MemberInit(Expression.New(typeof(GroupByWrapper)), wta), LambdaParameter); } else { // We do not have properties to aggregate // .GroupBy($it => new NoGroupByWrapper()) groupLambda = Expression.Lambda(Expression.New(this._groupByClrType), this.LambdaParameter); } return(ExpressionHelpers.GroupBy(query, groupLambda, elementType, this._groupByClrType)); }
private Expression CreateEntitySetAggregateExpression( ParameterExpression accum, EntitySetAggregateExpression expression, Type baseType) { // Should return following expression // $it => $it.AsQueryable() // .SelectMany($it => $it.SomeEntitySet) // .GroupBy($gr => new Object()) // .Select($p => new DynamicTypeWrapper() // { // AliasOne = $p.AsQueryable().AggMethodOne($it => $it.SomePropertyOfSomeEntitySet), // AliasTwo = $p.AsQueryable().AggMethodTwo($it => $it.AnotherPropertyOfSomeEntitySet), // ... // AliasN = ... , // A nested expression of this same format. // ... // }) List <MemberAssignment> wrapperTypeMemberAssignments = new List <MemberAssignment>(); var asQueryableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType); Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accum); // Create lambda to access the entity set from expression var source = BindAccessor(expression.Expression.Source); string propertyName = Model.GetClrPropertyName(expression.Expression.NavigationProperty); var property = Expression.Property(source, propertyName); var baseElementType = source.Type; var selectedElementType = property.Type.GenericTypeArguments.Single(); // Create method to get property collections to aggregate MethodInfo selectManyMethod = ExpressionHelperMethods.EnumerableSelectManyGeneric.MakeGenericMethod(baseElementType, selectedElementType); // Create the lambda that access the property in the selectMany clause. var selectManyParam = Expression.Parameter(baseElementType, "$it"); var propertyExpression = Expression.Property(selectManyParam, expression.Expression.NavigationProperty.Name); // Collection selector body is IQueryable, we need to adjust the type to IEnumerable, to match the SelectMany signature // therefore the delegate type is specified explicitly var collectionSelectorLambdaType = typeof(Func <,>).MakeGenericType( source.Type, typeof(IEnumerable <>).MakeGenericType(selectedElementType)); var selectManyLambda = Expression.Lambda(collectionSelectorLambdaType, propertyExpression, selectManyParam); // Get expression to get collection of entities var entitySet = Expression.Call(null, selectManyMethod, asQueryableExpression, selectManyLambda); // Getting method and lambda expression of groupBy var groupKeyType = typeof(object); MethodInfo groupByMethod = ExpressionHelperMethods.EnumerableGroupByGeneric.MakeGenericMethod(selectedElementType, groupKeyType); var groupByLambda = Expression.Lambda( Expression.New(groupKeyType), Expression.Parameter(selectedElementType, "$gr")); // Group entities in a single group to apply select var groupedEntitySet = Expression.Call(null, groupByMethod, entitySet, groupByLambda); var groupingType = typeof(IGrouping <,>).MakeGenericType(groupKeyType, selectedElementType); ParameterExpression innerAccum = Expression.Parameter(groupingType, "$p"); // Nested properties // Create dynamicTypeWrapper to encapsulate the aggregate result var properties = new List <NamedPropertyExpression>(); foreach (var aggExpression in expression.Children) { properties.Add(new NamedPropertyExpression(Expression.Constant(aggExpression.Alias), CreateAggregationExpression(innerAccum, aggExpression, selectedElementType))); } var nestedResultType = typeof(EntitySetAggregationWrapper); var wrapperProperty = nestedResultType.GetProperty("Container"); wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(properties))); var initializedMember = Expression.MemberInit(Expression.New(nestedResultType), wrapperTypeMemberAssignments); var selectLambda = Expression.Lambda(initializedMember, innerAccum); // Get select method MethodInfo selectMethod = ExpressionHelperMethods.EnumerableSelectGeneric.MakeGenericMethod( groupingType, selectLambda.Body.Type); return(Expression.Call(null, selectMethod, groupedEntitySet, selectLambda)); }
/// <summary> /// Pre flattens properties referenced in aggregate clause to avoid generation of nested queries by EF. /// For query like groupby((A), aggregate(B/C with max as Alias1, B/D with max as Alias2)) we need to generate /// .Select( /// $it => new FlattenninWrapper () { /// Source = $it, // Will used in groupby stage /// Container = new { /// Value = $it.B.C /// Next = new { /// Value = $it.B.D /// } /// } /// } /// ) /// Also we need to populate expressions to access B/C and B/D in aggregate stage. It will look like: /// B/C : $it.Container.Value /// B/D : $it.Container.Next.Value /// </summary> /// <param name="query"></param> /// <returns>Query with Select that flattens properties</returns> private IQueryable FlattenReferencedProperties(IQueryable query) { if (_aggregateExpressions != null && _aggregateExpressions.OfType <AggregateExpression>().Any(e => e.Method != AggregationMethod.VirtualPropertyCount) && _groupingProperties != null && _groupingProperties.Any() && (FlattenedPropertyContainer == null || !FlattenedPropertyContainer.Any())) { var wrapperType = typeof(FlatteningWrapper <>).MakeGenericType(this.ElementType); var sourceProperty = wrapperType.GetProperty("Source"); List <MemberAssignment> wta = new List <MemberAssignment>(); wta.Add(Expression.Bind(sourceProperty, this.LambdaParameter)); var aggrregatedPropertiesToFlatten = _aggregateExpressions.OfType <AggregateExpression>().Where(e => e.Method != AggregationMethod.VirtualPropertyCount).ToList(); // Generated Select will be stack like, meaning that first property in the list will be deepest one // For example if we add $it.B.C, $it.B.D, select will look like // new { // Value = $it.B.C // Next = new { // Value = $it.B.D // } // } // We are generated references (in currentContainerExpression) from the beginning of the Select ($it.Value, then $it.Next.Value etc.) // We have proper match we need insert properties in reverse order // After this // properties = { $it.B.D, $it.B.C} // _preFlattendMAp = { {$it.B.C, $it.Value}, {$it.B.D, $it.Next.Value} } var properties = new NamedPropertyExpression[aggrregatedPropertiesToFlatten.Count]; var aliasIdx = aggrregatedPropertiesToFlatten.Count - 1; var aggParam = Expression.Parameter(wrapperType, "$it"); var currentContainerExpression = Expression.Property(aggParam, GroupByContainerProperty); foreach (var aggExpression in aggrregatedPropertiesToFlatten) { var alias = "Property" + aliasIdx.ToString(CultureInfo.CurrentCulture); // We just need unique alias, we aren't going to use it // Add Value = $it.B.C var propAccessExpression = BindAccessor(aggExpression.Expression); var type = propAccessExpression.Type; propAccessExpression = WrapConvert(propAccessExpression); properties[aliasIdx] = new NamedPropertyExpression(Expression.Constant(alias), propAccessExpression); // Save $it.Container.Next.Value for future use UnaryExpression flatAccessExpression = Expression.Convert( Expression.Property(currentContainerExpression, "Value"), type); currentContainerExpression = Expression.Property(currentContainerExpression, "Next"); _preFlattenedMap.Add(aggExpression.Expression, flatAccessExpression); aliasIdx--; } var wrapperProperty = ResultClrType.GetProperty(GroupByContainerProperty); wta.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(properties))); var flatLambda = Expression.Lambda(Expression.MemberInit(Expression.New(wrapperType), wta), LambdaParameter); query = ExpressionHelpers.Select(query, flatLambda, this.ElementType); // We applied flattening let .GroupBy know about it. this.LambdaParameter = aggParam; } return(query); }