public ApplyBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) { this.bindMethod = bindMethod; this.state = state; this.filterBinder = new FilterBinder(bindMethod, state); this.configuration = configuration; this.odataPathInfo = odataPathInfo; }
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 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 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 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 Expression BindFilter <T>(string filter, IEdmModel model, ODataQuerySettings querySettings = null, IAssemblyResolver assembliesResolver = null) { Type elementType = typeof(T); FilterClause orderByClause = CreateFilterClause(filter, model, elementType); Assert.NotNull(orderByClause); querySettings = querySettings ?? _defaultSettings; QueryBinderContext context = new QueryBinderContext(model, querySettings, elementType) { AssembliesResolver = assembliesResolver, }; IFilterBinder filterBinder = new FilterBinder(); return(filterBinder.BindFilter(orderByClause, context)); }
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); }
/// <summary> /// Parses a <paramref name="filter"/> clause on the given <paramref name="elementType"/>, binding /// the text into semantic nodes using the provided model. /// </summary> /// <param name="filter">String representation of the filter expression.</param> /// <param name="configuration">The configuration used for binding.</param> /// <param name="elementType">Type that the filter clause refers to.</param> /// <param name="navigationSource">Navigation source that the elements being filtered are from.</param> /// <returns>A <see cref="FilterClause"/> representing the metadata bound filter expression.</returns> private static FilterClause ParseFilterImplementation(string filter, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource) { ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); ExceptionUtils.CheckArgumentNotNull(elementType, "elementType"); ExceptionUtils.CheckArgumentNotNull(filter, "filter"); // Get the syntactic representation of the filter expression UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveBuiltinIdentifier); QueryToken filterToken = expressionParser.ParseFilter(filter); // Bind it to metadata BindingState state = new BindingState(configuration); state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(elementType.ToTypeReference(), navigationSource); state.RangeVariables.Push(state.ImplicitRangeVariable); MetadataBinder binder = new MetadataBinder(state); FilterBinder filterBinder = new FilterBinder(binder.Bind, state); FilterClause boundNode = filterBinder.BindFilter(filterToken); return(boundNode); }
/// <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> /// Parses a <paramref name="query"/> clause on the given <paramref name="elementType"/>, binding /// the text into semantic nodes using the provided model. /// </summary> /// <param name="query">String representation of the filter expression.</param> /// <param name="configuration">The configuration used for binding.</param> /// <param name="elementType">Type that the filter clause refers to.</param> /// <param name="navigationSource">Navigation source that the elements being filtered are from.</param> /// <returns>A <see cref="FilterClause"/> representing the metadata bound filter expression.</returns> internal static ExpressionClause ParseExpressionImplementation(string query, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource) { ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); ExceptionUtils.CheckArgumentNotNull(elementType, "elementType"); ExceptionUtils.CheckArgumentNotNull(query, "query"); // Get the syntactic representation of the filter expression UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit); QueryToken expressionToken = expressionParser.ParseFilter(query); // Bind it to metadata BindingState state = new BindingState(configuration); state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(elementType.ToTypeReference(), navigationSource); state.RangeVariables.Push(state.ImplicitRangeVariable); MetadataBinder binder = new MetadataBinder(state); FilterBinder filterBinder = new FilterBinder(binder.Bind, state); ExpressionClause boundNode = filterBinder.BindProperyExpression(expressionToken); return(boundNode); }
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 $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)); }
/// <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"); } if (Context.ElementClrType == null) { throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); } 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); }
public ApplyBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) { this.bindMethod = bindMethod; this.state = state; this.filterBinder = new FilterBinder(bindMethod, state); }
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); }
/// <summary> /// Generate an expand item based on an ExpandTermToken /// </summary> /// <param name="tokenIn">the expandTerm token to visit</param> /// <returns>the expand item for this expand term token.</returns> private ExpandedNavigationSelectItem GenerateExpandItem(ExpandTermToken tokenIn) { ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); // ensure that we're always dealing with a normalized tree if (tokenIn.PathToNavProp.NextToken != null && !tokenIn.PathToNavProp.IsNamespaceOrContainerQualified()) { throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingANonNormalizedTree); } PathSegmentToken currentToken = tokenIn.PathToNavProp; IEdmEntityType currentLevelEntityType = this.entityType; List <ODataPathSegment> pathSoFar = new List <ODataPathSegment>(); PathSegmentToken firstNonTypeToken = currentToken; if (currentToken.IsNamespaceOrContainerQualified()) { pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, ref currentLevelEntityType, out firstNonTypeToken)); } IEdmProperty edmProperty = currentLevelEntityType.FindProperty(firstNonTypeToken.Identifier); if (edmProperty == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentLevelEntityType.FullName(), currentToken.Identifier)); } IEdmNavigationProperty currentNavProp = edmProperty as IEdmNavigationProperty; if (currentNavProp == null) { // the server allowed non-navigation, non-stream properties to be expanded, but then ignored them. if (this.Settings.UseWcfDataServicesServerBehavior && !edmProperty.Type.IsStream()) { return(null); } throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationProperty(currentToken.Identifier, currentLevelEntityType.FullName())); } pathSoFar.Add(new NavigationPropertySegment(currentNavProp, /*entitySet*/ null)); ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar); SelectExpandClause subSelectExpand; if (tokenIn.ExpandOption != null) { subSelectExpand = this.GenerateSubExpand(currentNavProp, tokenIn); } else { subSelectExpand = BuildDefaultSubExpand(); } subSelectExpand = this.DecorateExpandWithSelect(subSelectExpand, currentNavProp, tokenIn.SelectOption); IEdmEntitySet targetEntitySet = null; if (this.entitySet != null) { targetEntitySet = this.entitySet.FindNavigationTarget(currentNavProp); } // call MetadataBinder to build the filter clause FilterClause filterOption = null; if (tokenIn.FilterOption != null) { MetadataBinder binder = this.BuildNewMetadataBinder(targetEntitySet); FilterBinder filterBinder = new FilterBinder(binder.Bind, binder.BindingState); filterOption = filterBinder.BindFilter(tokenIn.FilterOption); } // call MetadataBinder again to build the orderby clause OrderByClause orderbyOption = null; if (tokenIn.OrderByOption != null) { MetadataBinder binder = this.BuildNewMetadataBinder(targetEntitySet); OrderByBinder orderByBinder = new OrderByBinder(binder.Bind); orderbyOption = orderByBinder.BindOrderBy(binder.BindingState, new OrderByToken[] { tokenIn.OrderByOption }); } return(new ExpandedNavigationSelectItem(pathToNavProp, targetEntitySet, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.InlineCountOption, subSelectExpand)); }
/// <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); }
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); }
/// <summary> /// Core logic for applying the query option to the IQueryable. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">Query setting used for validating the query option.</param> /// <param name="orderByNodes">OrderBy information required to correctly apply the query option for default implementation.</param> /// <param name="context">The <see cref="ODataQueryContext"/> which contains the <see cref="IEdmModel"/> and some type information</param> /// <param name="skipTokenRawValue">The raw string value of the skiptoken query parameter.</param> /// <returns></returns> private static IQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings, IList <OrderByNode> orderByNodes, ODataQueryContext context, string skipTokenRawValue) { if (context.ElementClrType == null) { throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); } IDictionary <string, OrderByDirection> directionMap; if (orderByNodes != null) { directionMap = orderByNodes.OfType <OrderByPropertyNode>().ToDictionary(node => node.Property.Name, node => node.Direction); } else { directionMap = new Dictionary <string, OrderByDirection>(); } IDictionary <string, object> propertyValuePairs = PopulatePropertyValuePairs(skipTokenRawValue, context); if (propertyValuePairs.Count == 0) { throw Error.InvalidOperation("Unable to get property values from the skiptoken value."); } ExpressionBinderBase binder = new FilterBinder(context.RequestContainer); bool parameterizeConstant = querySettings.EnableConstantParameterization; ParameterExpression param = Expression.Parameter(context.ElementClrType); Expression where = null; /* We will create a where lambda of the following form - * Where (Prop1>Value1) * OR (Prop1=Value1 AND Prop2>Value2) * OR (Prop1=Value1 AND Prop2=Value2 AND Prop3>Value3) * and so on... * Adding the first true to simplify implementation. */ Expression lastEquality = null; bool firstProperty = true; foreach (KeyValuePair <string, object> item in propertyValuePairs) { string key = item.Key; MemberExpression property = Expression.Property(param, key); object value = item.Value; Expression compare = null; ODataEnumValue enumValue = value as ODataEnumValue; if (enumValue != null) { value = enumValue.Value; } Expression constant = parameterizeConstant ? LinqParameterContainer.Parameterize(value.GetType(), value) : Expression.Constant(value); if (directionMap.ContainsKey(key) && directionMap[key] == OrderByDirection.Descending) { compare = binder.CreateBinaryExpression(BinaryOperatorKind.LessThan, property, constant, true); } else { compare = binder.CreateBinaryExpression(BinaryOperatorKind.GreaterThan, property, constant, true); } if (firstProperty) { lastEquality = binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true); where = compare; firstProperty = false; } else { Expression condition = Expression.AndAlso(lastEquality, compare); where = Expression.OrElse(where, condition); lastEquality = Expression.AndAlso(lastEquality, binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true)); } } Expression whereLambda = Expression.Lambda(where, param); return(ExpressionHelpers.Where(query, whereLambda, query.ElementType)); }
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); }