/// <summary> /// Apply the filter 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> /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } if (Context.ElementClrType == null) { throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); } FilterClause filterClause = FilterClause; Contract.Assert(filterClause != null); ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query); Expression filter = FilterBinder.Bind(query, filterClause, Context.ElementClrType, Context, updatedSettings); query = ExpressionHelpers.Where(query, filter, Context.ElementClrType); return(query); }
/// <summary> /// Apply the filter 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 IQueryable that we are applying filter query against.</param> /// <param name="querySettings">Specifies if we need to handle null propagation. Pass false if the underlying query provider handles null propagation. Otherwise pass true.</param> /// <param name="assembliesResolver">The <see cref="IAssembliesResolver"/> to use.</param> /// <returns>The query that the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssembliesResolver assembliesResolver) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } if (assembliesResolver == null) { throw Error.ArgumentNull("assembliesResolver"); } FilterQueryNode node = QueryNode; Contract.Assert(node != null); Expression filter = FilterBinder.Bind(node, Context.EntityClrType, Context.Model, assembliesResolver, querySettings); query = ExpressionHelpers.Where(query, filter, Context.EntityClrType); return(query); }
/// <summary> /// Apply the filter 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">The <see cref="IAssembliesResolver"/> to use.</param> /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssembliesResolver assembliesResolver) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } if (assembliesResolver == null) { throw Error.ArgumentNull("assembliesResolver"); } FilterClause filterClause = FilterClause; Contract.Assert(filterClause != null); // Ensure we have decided how to handle null propagation ODataQuerySettings updatedSettings = querySettings; if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default) { updatedSettings = new ODataQuerySettings(updatedSettings); updatedSettings.HandleNullPropagation = HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query); } Expression filter = FilterBinder.Bind(filterClause, Context.ElementClrType, Context.Model, assembliesResolver, updatedSettings); query = ExpressionHelpers.Where(query, filter, Context.ElementClrType); return(query); }
private Expression AddOrderByQueryForSource(Expression source, OrderByClause orderbyClause, Type elementType) { if (orderbyClause != null) { LambdaExpression orderByExpression = FilterBinder.Bind(orderbyClause, elementType, _model, _settings); source = ExpressionHelpers.OrderBy(source, orderByExpression, elementType, orderbyClause.Direction); } return(source); }
/// <summary> /// Apply 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> /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { 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); ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query); // The IWebApiAssembliesResolver service is internal and can only be injected by WebApi. // This code path may be used in cases when the service container is not available // and the service container is available but may not contain an instance of IWebApiAssembliesResolver. IWebApiAssembliesResolver assembliesResolver = WebApiAssembliesResolver.Default; if (Context.RequestContainer != null) { IWebApiAssembliesResolver injectedResolver = Context.RequestContainer.GetService <IWebApiAssembliesResolver>(); if (injectedResolver != null) { assembliesResolver = injectedResolver; } } foreach (var transformation in applyClause.Transformations) { if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy) { var binder = new AggregationBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, transformation); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; } else if (transformation.Kind == TransformationNodeKind.Filter) { var filterTransformation = transformation as FilterTransformationNode; Expression filter = FilterBinder.Bind(query, filterTransformation.FilterClause, ResultClrType, Context, querySettings); query = ExpressionHelpers.Where(query, filter, ResultClrType); } } return(query); }
public IQueryable Bind(IQueryable query, ApplyClause applyClause) { // groupby and aggregate transform input by collapsing everything not used in groupby/aggregate // as a result we have two distinct cases for expand implementation // 1. Expands followed by groupby/aggregate with entity set aggregations => filters in expand need to be applied (pushed down) to corresponding entityset aggregations // 2. Mix of expands and filters w/o any groupby/aggregation => falling back to $expand behavior and could just use SelectExpandBinder bool inputShapeChanged = false; foreach (var transformation in applyClause.Transformations) { if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy) { var binder = new AggregationBinder(_settings, _assembliesResolver, ResultClrType, _context.Model, transformation, _context, SelectExpandClause); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; inputShapeChanged = true; } else if (transformation.Kind == TransformationNodeKind.Compute) { var binder = new ComputeBinder(_settings, _assembliesResolver, ResultClrType, _context.Model, (ComputeTransformationNode)transformation); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; inputShapeChanged = true; } else if (transformation.Kind == TransformationNodeKind.Filter) { var filterTransformation = (FilterTransformationNode)transformation; Expression filter = FilterBinder.Bind(query, filterTransformation.FilterClause, ResultClrType, _context, _settings); query = ExpressionHelpers.Where(query, filter, ResultClrType); } else if (transformation.Kind == TransformationNodeKind.Expand) { var newClause = ((ExpandTransformationNode)transformation).ExpandClause; if (SelectExpandClause == null) { SelectExpandClause = newClause; } else { SelectExpandClause = new SelectExpandClause(SelectExpandClause.SelectedItems.Concat(newClause.SelectedItems), false); } } } if (SelectExpandClause != null && !inputShapeChanged) { var expandString = GetExpandsOnlyString(SelectExpandClause); var selectExpandQueryOption = new SelectExpandQueryOption(null, expandString, _context, SelectExpandClause); query = SelectExpandBinder.Bind(query, _settings, selectExpandQueryOption); } return(query); }
/// <summary> /// Apply the filter query to the given IQueryable. /// </summary> /// <param name="query">The IQueryable that we are applying filter query against.</param> /// <param name="handleNullPropagation">Specifies if we need to handle null propagation. Pass false if the underlying query provider handles null propagation. Otherwise pass true.</param> /// <returns>The query that the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, bool handleNullPropagation) { FilterQueryNode node = QueryNode; Contract.Assert(node != null); Expression filter = FilterBinder.Bind(node, Context.EntityClrType, Context.Model, handleNullPropagation); query = ExpressionHelpers.Where(query, filter, Context.EntityClrType); return(query); }
private Expression AddOrderByQueryForSource(Expression source, OrderByClause orderbyClause, Type elementType) { if (orderbyClause != null) { // TODO: Implement proper support for $select/$expand after $apply LambdaExpression orderByExpression = FilterBinder.Bind(null, orderbyClause, elementType, _context.RequestContainer); source = ExpressionHelpers.OrderBy(source, orderByExpression, elementType, orderbyClause.Direction); } return(source); }
private IQueryable AddOrderByQueryForProperty(IQueryable query, ODataQuerySettings querySettings, OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered) { Context.UpdateQuerySettings(querySettings, query); LambdaExpression orderByExpression = FilterBinder.Bind(query, orderbyClause, Context.ElementClrType, Context.RequestContainer); querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, Context.ElementClrType, alreadyOrdered); return(querySoFar); }
private static IQueryable AddOrderByQueryForProperty <T>(ODataQuery <T> query, ODataQuerySettings querySettings, OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered) { //Context.UpdateQuerySettings(querySettings, query); LambdaExpression orderByExpression = FilterBinder.Bind(query, orderbyClause, typeof(T), query.ServiceProvider); querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, typeof(T), alreadyOrdered); return(querySoFar); }
/// <summary> /// Apply 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="assemblyProvider">The <see cref="IAssemblyProvider"/> to use.</param> /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssemblyProvider assemblyProvider) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } if (assemblyProvider == null) { throw Error.ArgumentNull("assemblyProvider"); } 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 = HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query); } foreach (var transformation in applyClause.Transformations) { if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy) { var binder = new AggregationBinder(updatedSettings, assemblyProvider, ResultClrType, Context.Model, transformation as TransformationNode, _serviceProvider); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; } else if (transformation.Kind == TransformationNodeKind.Filter) { var filterTransformation = transformation as FilterTransformationNode; Expression filter = FilterBinder.Bind(filterTransformation.FilterClause, ResultClrType, Context.Model, assemblyProvider, updatedSettings, _serviceProvider); query = ExpressionHelpers.Where(query, filter, ResultClrType); } } return(query); }
private void DoAggregatedGroupBy(string value, out IQueryable keys, out object[] aggragatedValues) { IQueryable data; ODataQueryContext context; ODataQuerySettings settings; ApplyGroupbyClause groupByClause; DefaultAssembliesResolver assembliesResolver; GroupByImplementation groupByImplementation; Type keyType; IEnumerable <LambdaExpression> propertiesToGroupByExpressions; GetGroupByParams(value, out data, out context, out settings, out groupByClause, out assembliesResolver, out groupByImplementation, out keyType, out propertiesToGroupByExpressions); var propertyToAggregateExpression = FilterBinder.Bind(groupByClause.Aggregate.AggregatablePropertyExpression, context.ElementClrType, context.Model, assembliesResolver, settings); groupByImplementation.DoAggregatedGroupBy(data, 2000, groupByClause, keyType, propertiesToGroupByExpressions, propertyToAggregateExpression, out keys, out aggragatedValues); }
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)); }
private IQueryable AddOrderByQueryForProperty( IQueryable query, ODataQuerySettings querySettings, OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered) { // Ensure we have decided how to handle null propagation ODataQuerySettings updatedSettings = querySettings; if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default) { updatedSettings = new ODataQuerySettings(updatedSettings); updatedSettings.HandleNullPropagation = HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query); } LambdaExpression orderByExpression = FilterBinder.Bind(orderbyClause, _contextElementClrType, Context.Model, updatedSettings); querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, _contextElementClrType, alreadyOrdered); return(querySoFar); }
/// <summary> /// Apply 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> /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { 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); ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query); foreach (var transformation in applyClause.Transformations) { if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy) { var binder = new AggregationBinder(updatedSettings, _assembliesResolver, ResultClrType, Context.Model, transformation); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; } else if (transformation.Kind == TransformationNodeKind.Filter) { var filterTransformation = transformation as FilterTransformationNode; Expression filter = FilterBinder.Bind(query, filterTransformation.FilterClause, ResultClrType, Context.RequestContainer); query = ExpressionHelpers.Where(query, filter, ResultClrType); } } return(query); }
private IQueryable AddOrderByQueryForProperty(IQueryable query, ODataQuerySettings querySettings, OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered) { // TODO: //Context.UpdateQuerySettings(querySettings, query); var services = new ServiceCollection(); services.AddSingleton <ODataQuerySettings>(querySettings); services.AddSingleton <IEdmModel>(Context.Model); services.AddSingleton <IAssemblyProvider>(_assemblyProvider); var requestContainer = services.BuildServiceProvider(); LambdaExpression orderByExpression = //FilterBinder.Bind(orderbyClause, Context.ElementClrType, /*Context.RequestContainer*/ null); FilterBinder.Bind(orderbyClause, Context.ElementClrType, requestContainer); querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, Context.ElementClrType, alreadyOrdered); return(querySoFar); }
/// <summary> /// Apply the filter 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="assemblyProvider">The <see cref="IAssemblyProvider"/> to use.</param> /// <returns>The new <see cref="IQueryable"/> after the filter query has been applied to.</returns> public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, IAssemblyProvider assemblyProvider) { if (query == null) { throw Error.ArgumentNull("query"); } if (assemblyProvider == null) { throw Error.ArgumentNull("assemblyProvider"); } if (Context.ElementClrType == null) { throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); } var filter = FilterBinder.Bind(FilterClause, Context.ElementClrType, Context.Model, assemblyProvider, querySettings); return(ExpressionHelpers.Where(query, filter, Context.ElementClrType)); }
/// <summary> /// Apply $filter parameter to query. /// </summary> /// <param name="query"> /// The OData aware query. /// </param> /// <param name="filterText"> /// The $filter parameter text. /// </param> /// <param name="entitySetName"> /// The entity set name. /// </param> /// <typeparam name="T"> /// The query type param /// </typeparam> /// <returns> /// The <see cref="ODataQuery{T}"/> query with applied filter parameter. /// </returns> /// <exception cref="ArgumentNullException"> /// Argument Null Exception /// </exception> public static ODataQuery <T> Filter <T>(this ODataQuery <T> query, string filterText, string entitySetName = null) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (filterText == null) { throw new ArgumentNullException(nameof(filterText)); } IEdmModel edmModel = query.EdmModel; ODataQueryOptionParser queryOptionParser = GetParser( query, entitySetName, new Dictionary <string, string> { { "$filter", filterText } }); ODataSettings settings = query.ServiceProvider.GetRequiredService <ODataSettings>(); FilterClause filterClause = queryOptionParser.ParseFilter(); SingleValueNode filterExpression = filterClause.Expression.Accept( new ParameterAliasNodeTranslator(queryOptionParser.ParameterAliasNodes)) as SingleValueNode; filterExpression = filterExpression ?? new ConstantNode(null); filterClause = new FilterClause(filterExpression, filterClause.RangeVariable); Contract.Assert(filterClause != null); var validator = new FilterQueryValidator(settings.DefaultQuerySettings); validator.Validate(filterClause, settings.ValidationSettings, edmModel); Expression filter = FilterBinder.Bind(query, filterClause, typeof(T), query.ServiceProvider); var result = ExpressionHelpers.Where(query, filter, typeof(T)); return(new ODataQuery <T>(result, query.ServiceProvider)); }
private IOrderedQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings) { if (Context.ElementClrType == null) { throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); } ICollection <OrderByNode> nodes = OrderByNodes; bool alreadyOrdered = false; IQueryable querySoFar = query; HashSet <IEdmProperty> propertiesSoFar = new HashSet <IEdmProperty>(); bool orderByItSeen = false; foreach (OrderByNode node in nodes) { OrderByPropertyNode propertyNode = node as OrderByPropertyNode; if (propertyNode != null) { IEdmProperty property = propertyNode.Property; OrderByDirection direction = propertyNode.Direction; // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows if (propertiesSoFar.Contains(property)) { throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, property.Name)); } propertiesSoFar.Add(property); if (propertyNode.OrderByClause != null) { // Ensure we have decided how to handle null propagation ODataQuerySettings updatedSettings = querySettings; if (querySettings.HandleNullPropagation == HandleNullPropagationOption.Default) { updatedSettings = new ODataQuerySettings(updatedSettings); updatedSettings.HandleNullPropagation = HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query); } LambdaExpression orderByExpression = FilterBinder.Bind(propertyNode.OrderByClause, Context.ElementClrType, Context.Model, updatedSettings); querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, Context.ElementClrType, alreadyOrdered); } else { querySoFar = ExpressionHelpers.OrderByProperty(querySoFar, property, direction, Context.ElementClrType, alreadyOrdered); } alreadyOrdered = true; } else { // This check prevents queries with duplicate nodes (e.g. $orderby=$it,$it,$it,$it...) from causing stack overflows if (orderByItSeen) { throw new ODataException(Error.Format(SRResources.OrderByDuplicateIt)); } querySoFar = ExpressionHelpers.OrderByIt(querySoFar, node.Direction, Context.ElementClrType, alreadyOrdered); alreadyOrdered = true; orderByItSeen = true; } } return(querySoFar as IOrderedQueryable); }
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 = ExpressionBinderBase.GetPropertyExpression(source, propertyName); Type nullablePropertyType = propertyValue.Type.ToNullable(); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (filterClause != null) { var 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; 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.RequestContainer); filterResult = Expression.Call( ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(clrElementType), filterSource, filterPredicate); nullablePropertyType = filterResult.Type; } else if (_settings.HandleReferenceNavigationPropertyExpandFilter) { var filterLambdaExpression = FilterBinder.Bind(null, filterClause, clrElementType, _context.RequestContainer) as LambdaExpression; if (filterLambdaExpression == null) { throw new ODataException(Error.Format(SRResources.ExpandFilterExpressionNotLambdaExpression, property.Name, nameof(LambdaExpression))); } var filterParameter = filterLambdaExpression.Parameters.First(); var predicateExpression = new ReferenceNavigationPropertyExpandFilterVisitor(filterParameter, nullablePropertyValue).Visit(filterLambdaExpression.Body); // predicateExpression == true ? nullablePropertyValue : null filterResult = Expression.Condition( test: predicateExpression, ifTrue: nullablePropertyValue, ifFalse: Expression.Constant(value: null, type: nullablePropertyType)); } 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); }
/// <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); }
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.MappingDoesNotContainEntityType, 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(Error.Format(SRResources.MappingDoesNotContainEntityType, 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, _model, _assembliesResolver, _settings); 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); }