/// <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 begining 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); }
private Expression BuildPropertyContainer(IEdmEntityType elementType, Expression source, Dictionary <IEdmNavigationProperty, ExpandedNavigationSelectItem> propertiesToExpand, ISet <IEdmStructuralProperty> propertiesToInclude, ISet <IEdmStructuralProperty> autoSelectedProperties, bool isSelectingOpenTypeSegments) { IList <NamedPropertyExpression> includedProperties = new List <NamedPropertyExpression>(); foreach (KeyValuePair <IEdmNavigationProperty, ExpandedNavigationSelectItem> kvp in propertiesToExpand) { IEdmNavigationProperty propertyToExpand = kvp.Key; ExpandedNavigationSelectItem expandItem = kvp.Value; SelectExpandClause projection = expandItem.SelectAndExpand; Expression propertyName = CreatePropertyNameExpression(elementType, propertyToExpand, source); Expression propertyValue = CreatePropertyValueExpressionWithFilter(elementType, propertyToExpand, source, expandItem.FilterOption); Expression nullCheck = GetNullCheckExpression(propertyToExpand, propertyValue, projection); Expression countExpression = CreateTotalCountExpression(propertyValue, expandItem); // projection can be null if the expanded navigation property is not further projected or expanded. if (projection != null) { propertyValue = ProjectAsWrapper(propertyValue, projection, propertyToExpand.ToEntityType(), expandItem.NavigationSource as IEdmEntitySet, expandItem); } NamedPropertyExpression propertyExpression = new NamedPropertyExpression(propertyName, propertyValue); if (projection != null) { if (!propertyToExpand.Type.IsCollection()) { propertyExpression.NullCheck = nullCheck; } else if (_settings.PageSize != null) { propertyExpression.PageSize = _settings.PageSize.Value; } propertyExpression.TotalCount = countExpression; propertyExpression.CountOption = expandItem.CountOption; } includedProperties.Add(propertyExpression); } foreach (IEdmStructuralProperty propertyToInclude in propertiesToInclude) { Expression propertyName = CreatePropertyNameExpression(elementType, propertyToInclude, source); Expression propertyValue = CreatePropertyValueExpression(elementType, propertyToInclude, source); includedProperties.Add(new NamedPropertyExpression(propertyName, propertyValue)); } foreach (IEdmStructuralProperty propertyToInclude in autoSelectedProperties) { Expression propertyName = CreatePropertyNameExpression(elementType, propertyToInclude, source); Expression propertyValue = CreatePropertyValueExpression(elementType, propertyToInclude, source); includedProperties.Add(new NamedPropertyExpression(propertyName, propertyValue) { AutoSelected = true }); } if (isSelectingOpenTypeSegments) { var dynamicPropertyDictionary = EdmLibHelpers.GetDynamicPropertyDictionary(elementType, _model); Expression propertyName = Expression.Constant(dynamicPropertyDictionary.Name); Expression propertyValue = Expression.Property(source, dynamicPropertyDictionary.Name); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // source == null ? null : propertyValue propertyValue = Expression.Condition( test: Expression.Equal(source, Expression.Constant(value: null)), ifTrue: Expression.Constant(value: null, type: propertyValue.Type.ToNullable()), ifFalse: nullablePropertyValue); } else { propertyValue = nullablePropertyValue; } includedProperties.Add(new NamedPropertyExpression(propertyName, propertyValue)); } // create a property container that holds all these property names and values. return(PropertyContainer.CreatePropertyContainer(includedProperties)); }
// new CollectionWrapper<ElementType> { Instance = source.Select((ElementType element) => new Wrapper { }) } private Expression ProjectCollection(Expression source, Type elementType, SelectExpandClause selectExpandClause, IEdmEntityType entityType, IEdmEntitySet entitySet, ExpandedNavigationSelectItem expandedItem) { ParameterExpression element = Expression.Parameter(elementType); // expression // new Wrapper { } Expression projection = ProjectElement(element, selectExpandClause, entityType, entitySet); // expression // (ElementType element) => new Wrapper { } LambdaExpression selector = Expression.Lambda(projection, element); if (expandedItem != null) { source = AddOrderByQueryForSource(source, expandedItem.OrderByOption, elementType); } if (_settings.PageSize.HasValue || (expandedItem != null && (expandedItem.TopOption.HasValue || expandedItem.SkipOption.HasValue))) { // nested paging. Need to apply order by first, and take one more than page size as we need to know // whether the collection was truncated or not while generating next page links. IEnumerable <IEdmStructuralProperty> properties = entityType.Key().Any() ? entityType.Key() : entityType .StructuralProperties() .Where(property => property.Type.IsPrimitive() && !property.Type.IsStream()) .OrderBy(property => property.Name); if (expandedItem == null || expandedItem.OrderByOption == null) { bool alreadyOrdered = false; foreach (var prop in properties) { source = ExpressionHelpers.OrderByPropertyExpression(source, prop.Name, elementType, alreadyOrdered); if (!alreadyOrdered) { alreadyOrdered = true; } } } if (expandedItem != null && expandedItem.SkipOption.HasValue) { Contract.Assert(expandedItem.SkipOption.Value <= Int32.MaxValue); source = ExpressionHelpers.Skip(source, (int)expandedItem.SkipOption.Value, elementType, _settings.EnableConstantParameterization); } if (expandedItem != null && expandedItem.TopOption.HasValue) { Contract.Assert(expandedItem.TopOption.Value <= Int32.MaxValue); source = ExpressionHelpers.Take(source, (int)expandedItem.TopOption.Value, elementType, _settings.EnableConstantParameterization); } source = ExpressionHelpers.Take(source, _settings.PageSize.Value + 1, elementType, _settings.EnableConstantParameterization); } // expression // source.Select((ElementType element) => new Wrapper { }) Expression selectedExpresion = Expression.Call(GetSelectMethod(elementType, projection.Type), source, selector); if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // source == null ? null : projectedCollection return(Expression.Condition( test: Expression.Equal(source, Expression.Constant(null)), ifTrue: Expression.Constant(null, selectedExpresion.Type), ifFalse: selectedExpresion)); } else { return(selectedExpresion); } }
internal Expression CreatePropertyValueExpressionWithFilter(IEdmEntityType elementType, IEdmProperty property, Expression source, FilterClause filterClause) { Contract.Assert(elementType != null); Contract.Assert(property != null); Contract.Assert(source != null); IEdmEntityType declaringType = property.DeclaringType as IEdmEntityType; Contract.Assert(declaringType != null, "only entity types are projected."); // derived property using cast if (elementType != declaringType) { Type castType = EdmLibHelpers.GetClrType(declaringType, _model); if (castType == null) { throw new ODataException("TODO: " /*Error.Format(SRResources.MappingDoesNotContainResourceType, * declaringType.FullName())*/); } source = Expression.TypeAs(source, castType); } string propertyName = EdmLibHelpers.GetClrPropertyName(property, _model); Expression propertyValue = Expression.Property(source, propertyName); Type nullablePropertyType = propertyValue.Type.ToNullable(); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (filterClause != null && property.Type.IsCollection()) { IEdmTypeReference edmElementType = property.Type.AsCollection().ElementType(); Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model); if (clrElementType == null) { throw new ODataException("TODO:" /*Error.Format(SRResources.MappingDoesNotContainResourceType, * edmElementType.FullName())*/); } Expression filterSource = typeof(IEnumerable).IsAssignableFrom(source.Type.GetProperty(propertyName).PropertyType) ? Expression.Call( ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(clrElementType), nullablePropertyValue) : nullablePropertyValue; Expression filterPredicate = FilterBinder.Bind(filterClause, clrElementType, /*_context.RequestContainer*/ null); MethodCallExpression filterResult = Expression.Call( ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(clrElementType), filterSource, filterPredicate); nullablePropertyType = filterResult.Type; if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // nullablePropertyValue == null ? null : filterResult nullablePropertyValue = Expression.Condition( test: Expression.Equal(nullablePropertyValue, Expression.Constant(value: null)), ifTrue: Expression.Constant(value: null, type: nullablePropertyType), ifFalse: filterResult); } else { nullablePropertyValue = filterResult; } } if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // source == null ? null : propertyValue propertyValue = Expression.Condition( test: Expression.Equal(source, Expression.Constant(value: null)), ifTrue: Expression.Constant(value: null, type: nullablePropertyType), ifFalse: nullablePropertyValue); } else { // need to cast this to nullable as EF would fail while materializing if the property is not nullable and source is null. propertyValue = nullablePropertyValue; } return(propertyValue); }