private static FilterBinder GetOrCreateFilterBinder(ODataQueryContext context, ODataQuerySettings querySettings) { FilterBinder binder = null; if (context.RequestContainer != null) { binder = context.RequestContainer.GetRequiredService <FilterBinder>(); if (binder != null && binder.Model != context.Model && binder.Model == EdmCoreModel.Instance) { binder.Model = context.Model; } } return(binder ?? new FilterBinder(querySettings, WebApiAssembliesResolver.Default, context.Model)); }
private static LambdaExpression BindFilterClause(FilterBinder binder, FilterClause filterClause, Type filterType) { LambdaExpression filter = binder.BindExpression(filterClause.Expression, filterClause.RangeVariable, filterType); filter = Expression.Lambda(binder.ApplyNullPropagationForFilterBody(filter.Body), filter.Parameters); Type expectedFilterType = typeof(Func <,>).MakeGenericType(filterType, typeof(bool)); if (filter.Type != expectedFilterType) { throw Error.Argument("filterType", SRResources.CannotCastFilter, filter.Type.FullName, expectedFilterType.FullName); } return(filter); }
private Expression AddOrderByQueryForSource(Expression source, OrderByClause orderbyClause, Type elementType) { if (orderbyClause != null) { // TODO: Implement proper support for $select/$expand after $apply ODataQuerySettings querySettings = new ODataQuerySettings() { HandleNullPropagation = HandleNullPropagationOption.True, }; LambdaExpression orderByExpression = FilterBinder.Bind(null, orderbyClause, elementType, _context, querySettings); source = ExpressionHelpers.OrderBy(source, orderByExpression, elementType, orderbyClause.Direction); } return(source); }
internal static LambdaExpression Bind( IQueryable baseQuery, OrderByClause orderBy, Type elementType, ODataQueryContext context, ODataQuerySettings querySettings) { Contract.Assert(orderBy != null); Contract.Assert(elementType != null); Contract.Assert(context != null); FilterBinder binder = GetOrCreateFilterBinder(context, querySettings); binder._filterType = elementType; binder.BaseQuery = baseQuery; return(BindOrderByClause(binder, orderBy, elementType)); }
internal static Expression Bind(FilterClause filterClause, Type filterType, IEdmModel model, IWebApiAssembliesResolver assembliesResolver, ODataQuerySettings querySettings) { if (filterClause == null) { throw Error.ArgumentNull("filterClause"); } if (filterType == null) { throw Error.ArgumentNull("filterType"); } if (model == null) { throw Error.ArgumentNull("model"); } if (assembliesResolver == null) { throw Error.ArgumentNull("assembliesResolver"); } FilterBinder binder = new FilterBinder(model, assembliesResolver, querySettings, filterType); return(BindFilterClause(binder, filterClause, filterType)); }
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(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 = TypeHelper.ToNullable(propertyValue.Type); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (filterClause != null) { bool isCollection = property.Type.IsCollection(); IEdmTypeReference edmElementType = (isCollection ? property.Type.AsCollection().ElementType() : property.Type); Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model); if (clrElementType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, edmElementType.FullName())); } Expression filterResult = nullablePropertyValue; ODataQuerySettings querySettings = new ODataQuerySettings() { HandleNullPropagation = HandleNullPropagationOption.True, }; if (isCollection) { Expression filterSource = typeof(IEnumerable).IsAssignableFrom(source.Type.GetProperty(propertyName).PropertyType) ? Expression.Call( ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(clrElementType), nullablePropertyValue) : nullablePropertyValue; // TODO: Implement proper support for $select/$expand after $apply Expression filterPredicate = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings); filterResult = Expression.Call( ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(clrElementType), filterSource, filterPredicate); nullablePropertyType = filterResult.Type; } else if (_settings.HandleReferenceNavigationPropertyExpandFilter) { LambdaExpression filterLambdaExpression = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings) as LambdaExpression; if (filterLambdaExpression == null) { throw new ODataException(Error.Format(SRResources.ExpandFilterExpressionNotLambdaExpression, property.Name, "LambdaExpression")); } ParameterExpression filterParameter = filterLambdaExpression.Parameters.First(); Expression predicateExpression = new ReferenceNavigationPropertyExpandFilterVisitor(filterParameter, nullablePropertyValue).Visit(filterLambdaExpression.Body); // create expression similar to: 'predicateExpression == true ? nullablePropertyValue : null' filterResult = Expression.Condition( test: predicateExpression, ifTrue: nullablePropertyValue, ifFalse: Expression.Constant(value: null, type: nullablePropertyType)); } if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // create expression similar to: '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) { // create expression similar to: '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); }
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 = EdmLibHelpers.GetClrPropertyName(expression.Expression.NavigationProperty, Model); 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 acceses the property in the selectMany clause. var selectManyParam = Expression.Parameter(baseElementType, "$it"); var propertyExpression = Expression.Property(selectManyParam, expression.Expression.NavigationProperty.Name); var selectManyLambda = Expression.Lambda(propertyExpression, selectManyParam); // Get expression to get collection of entities var entitySet = Expression.Call(null, selectManyMethod, asQueryableExpression, selectManyLambda); // Do we have filter from expand to push down? EnsurePushedDownFilters(); if (_filtersPushDown.TryGetValue(expression.Expression.NavigationSource.Path.Path, out FilterClause filterClause)) { var filterExpression = FilterBinder.Bind(null, filterClause, selectedElementType, _context, QuerySettings); var whereMethod = ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(selectedElementType); var asNestedQueryableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(selectedElementType); entitySet = Expression.Call(null, whereMethod, Expression.Call(null, asNestedQueryableMethod, entitySet), filterExpression); } // Getting method and lambda expression of groupBy // TODO: We always aggregatin whole collection. Not sure that we really need it // Using object caused an issue with Linq-to-objects (I replaced by bool ) var groupKeyType = typeof(bool); 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)); }
private static LambdaExpression BindOrderByClause(FilterBinder binder, OrderByClause orderBy, Type elementType) { LambdaExpression orderByLambda = binder.BindExpression(orderBy.Expression, orderBy.RangeVariable, elementType); return(orderByLambda); }
internal Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType elementType, IEdmProperty property, Expression source, ExpandedReferenceSelectItem expandItem) { Contract.Assert(elementType != null); Contract.Assert(property != null); Contract.Assert(source != null); FilterClause filterClause = expandItem != null ? expandItem.FilterOption : null; IEdmStructuredType declaringType; IEdmStructuredType currentType = elementType; if (expandItem != null) { foreach (ODataPathSegment segment in expandItem.PathToNavigationProperty) { string currentProperty = String.Empty; PropertySegment propertyAccessPathSegment = segment as PropertySegment; if (propertyAccessPathSegment != null) { IEdmProperty propertyInPath = propertyAccessPathSegment.Property; currentProperty = EdmLibHelpers.GetClrPropertyName(propertyInPath, _model); declaringType = propertyInPath.DeclaringType; Contract.Assert(!String.IsNullOrEmpty(currentProperty), "Property name could not be found on the model."); if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // create expression similar to: 'source == null ? null : propertyValue' if (declaringType != currentType) { Type castType = EdmLibHelpers.GetClrType(property.DeclaringType, _model); if (castType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, property.DeclaringType.FullTypeName())); } source = Expression.TypeAs(source, castType); } Expression propertyExpression = Expression.Property(source, currentProperty); Type nullablePropType = TypeHelper.ToNullable(propertyExpression.Type); source = Expression.Condition( test: Expression.Equal(propertyExpression, Expression.Constant(value: null)), ifTrue: Expression.Constant(value: null, type: nullablePropType), ifFalse: propertyExpression); } else { source = Expression.Property(source, currentProperty); } currentType = propertyInPath.Type.ToStructuredType(); } else { TypeSegment typeSegment = segment as TypeSegment; if (typeSegment != null) { Type castType = EdmLibHelpers.GetClrType(typeSegment.EdmType, _model); source = Expression.TypeAs(source, castType); currentType = typeSegment.EdmType as IEdmStructuredType; } } } } // derived property using cast if (currentType != property.DeclaringType) { Type castType = EdmLibHelpers.GetClrType(property.DeclaringType, _model); if (castType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, property.DeclaringType.FullTypeName())); } source = Expression.TypeAs(source, castType); } string propertyName = EdmLibHelpers.GetClrPropertyName(property, _model); PropertyInfo propertyInfo = source.Type.GetProperty(propertyName); Expression propertyValue = Expression.Property(source, propertyInfo); Type nullablePropertyType = TypeHelper.ToNullable(propertyValue.Type); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (filterClause != null) { bool isCollection = property.Type.IsCollection(); IEdmTypeReference edmElementType = (isCollection ? property.Type.AsCollection().ElementType() : property.Type); Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model); if (clrElementType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, edmElementType.FullName())); } Expression filterResult = nullablePropertyValue; ODataQuerySettings querySettings = new ODataQuerySettings() { HandleNullPropagation = HandleNullPropagationOption.True, }; if (isCollection) { Expression filterSource = nullablePropertyValue; // TODO: Implement proper support for $select/$expand after $apply Expression filterPredicate = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings); filterResult = Expression.Call( ExpressionHelperMethods.EnumerableWhereGeneric.MakeGenericMethod(clrElementType), filterSource, filterPredicate); nullablePropertyType = filterResult.Type; } else if (_settings.HandleReferenceNavigationPropertyExpandFilter) { LambdaExpression filterLambdaExpression = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings) as LambdaExpression; if (filterLambdaExpression == null) { throw new ODataException(Error.Format(SRResources.ExpandFilterExpressionNotLambdaExpression, property.Name, "LambdaExpression")); } ParameterExpression filterParameter = filterLambdaExpression.Parameters.First(); Expression predicateExpression = new ReferenceNavigationPropertyExpandFilterVisitor(filterParameter, nullablePropertyValue).Visit(filterLambdaExpression.Body); // create expression similar to: 'predicateExpression == true ? nullablePropertyValue : null' filterResult = Expression.Condition( test: predicateExpression, ifTrue: nullablePropertyValue, ifFalse: Expression.Constant(value: null, type: nullablePropertyType)); } if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { // create expression similar to: '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) { // create expression similar to: '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); }