/// <summary> /// Initialize a new instance of the <see cref="ODataQuerySettings"/> class based on an existing one. /// </summary> /// <param name="settings">The setting to copy from.</param> public ODataQuerySettings(ODataQuerySettings settings) { EnsureStableOrdering = settings.EnsureStableOrdering; EnableConstantParameterization = settings.EnableConstantParameterization; HandleNullPropagation = settings.HandleNullPropagation; PageSize = settings.PageSize; }
/// <summary> /// Apply the individual query to the given IQueryable in the right order. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The settings to use in query composition.</param> /// <returns>The new <see cref="IQueryable"/> after the query has been applied to.</returns> public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { if (query == null) { throw Error.ArgumentNull("query"); } // Construct the actual query and apply them in the following order: filter if (Filter != null) { query = Filter.ApplyTo(query, querySettings, _assemblyProvider); } return query; }
/// <summary> /// Apply the individual query to the given IQueryable in the right order. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The settings to use in query composition.</param> /// <returns>The new <see cref="IQueryable"/> after the query has been applied to.</returns> public override IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { ValidateQuery(query); return(base.ApplyTo(query, querySettings)); }
/// <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)); }
/// <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"); } // Linq to SQL not supported for $apply if (query.Provider.GetType().Namespace == HandleNullPropagationOptionHelper.Linq2SqlQueryProviderNamespace) { throw Error.NotSupported(SRResources.ApplyQueryOptionNotSupportedForLinq2SQL); } 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.Compute) { var binder = new ComputeBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, (ComputeTransformationNode)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); }
/// <summary> /// Apply the $skiptoken query to the given IQueryable. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The query settings to use while applying this query option.</param> /// <param name="queryOptions">Information about the other query options.</param> /// <returns>The new <see cref="IQueryable"/> after the skiptoken query has been applied to.</returns> public virtual IQueryable <T> ApplyTo <T>(IQueryable <T> query, ODataQuerySettings querySettings, ODataQueryOptions queryOptions) { QuerySettings = querySettings; QueryOptions = queryOptions; return(skipTokenHandler.ApplyTo <T>(query, this) as IOrderedQueryable <T>); }
/// <summary> /// Apply the $skiptoken query to the given IQueryable. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The query settings to use while applying this query option.</param> /// <param name="queryOptions">Information about the other query options.</param> /// <returns>The new <see cref="IQueryable"/> after the skiptoken query has been applied to.</returns> public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, ODataQueryOptions queryOptions) { QuerySettings = querySettings; QueryOptions = queryOptions; return(skipTokenHandler.ApplyTo(query, this)); }
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 <object> propertiesSoFar = new HashSet <object>(); HashSet <string> openPropertiesSoFar = new HashSet <string>(); bool orderByItSeen = false; foreach (OrderByNode node in nodes) { OrderByPropertyNode propertyNode = node as OrderByPropertyNode; OrderByOpenPropertyNode openPropertyNode = node as OrderByOpenPropertyNode; OrderByCountNode countNode = node as OrderByCountNode; if (propertyNode != null) { // Use autonomy class to achieve value equality for HasSet. var edmPropertyWithPath = new { propertyNode.Property, propertyNode.PropertyPath }; 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(edmPropertyWithPath)) { throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, edmPropertyWithPath.PropertyPath)); } propertiesSoFar.Add(edmPropertyWithPath); if (propertyNode.OrderByClause != null) { querySoFar = AddOrderByQueryForProperty(query, querySettings, propertyNode.OrderByClause, querySoFar, direction, alreadyOrdered); } else { querySoFar = ExpressionHelpers.OrderByProperty(querySoFar, Context.Model, edmPropertyWithPath.Property, direction, Context.ElementClrType, alreadyOrdered); } alreadyOrdered = true; } else if (openPropertyNode != null) { // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows if (openPropertiesSoFar.Contains(openPropertyNode.PropertyName)) { throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, openPropertyNode.PropertyPath)); } openPropertiesSoFar.Add(openPropertyNode.PropertyName); Contract.Assert(openPropertyNode.OrderByClause != null); querySoFar = AddOrderByQueryForProperty(query, querySettings, openPropertyNode.OrderByClause, querySoFar, openPropertyNode.Direction, alreadyOrdered); alreadyOrdered = true; } else if (countNode != null) { Contract.Assert(countNode.OrderByClause != null); querySoFar = AddOrderByQueryForProperty(query, querySettings, countNode.OrderByClause, querySoFar, countNode.Direction, 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> /// Apply the $orderby query to the given IQueryable. /// </summary> /// <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 orderby query has been applied to.</returns> public IOrderedQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { return(ApplyToCore(query, querySettings)); }
/// <summary> /// Apply the $orderby query to the given IQueryable. /// </summary> /// <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 orderby query has been applied to.</returns> public IOrderedQueryable <T> ApplyTo <T>(IQueryable <T> query, ODataQuerySettings querySettings) { return(ApplyToCore(query, querySettings) as IOrderedQueryable <T>); }
/// <summary> /// Apply the individual query to the given IQueryable in the right order. /// </summary> /// <param name="entity">The original entity.</param> /// <param name="querySettings">The <see cref="ODataQuerySettings"/> that contains all the query application related settings.</param> /// <param name="ignoreQueryOptions">The query parameters that are already applied in queries.</param> /// <returns>The new entity after the $select and $expand query has been applied to.</returns> /// <remarks>Only $select and $expand query options can be applied on single entities. This method throws if the query contains any other /// query options.</remarks> public virtual object ApplyTo(object entity, ODataQuerySettings querySettings, AllowedQueryOptions ignoreQueryOptions) { _ignoreQueryOptions = ignoreQueryOptions; return(ApplyTo(entity, new ODataQuerySettings())); }
public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } IQueryable result = query; // First apply $apply // Section 3.15 of the spec http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326311 if (IsAvailableODataQueryOption(Apply, AllowedQueryOptions.Apply)) { result = Apply.ApplyTo(result, querySettings); InternalRequest.Context.ApplyClause = Apply.ApplyClause; if (Apply.SelectExpandClause != null) { // In case of just expand in $apply falling back to $expand serialization InternalRequest.Context.ProcessedSelectExpandClause = Apply.SelectExpandClause; } this.Context.ElementClrType = Apply.ResultClrType; } // Apply compute // It should be executed before $filter, because it defines computed properties that can be used in a $select or within a $filter or $orderby expression. if (IsAvailableODataQueryOption(Compute, AllowedQueryOptions.Compute)) { result = Compute.ApplyTo(result, querySettings); } // Construct the actual query and apply them in the following order: filter, orderby, skip, top if (IsAvailableODataQueryOption(Filter, AllowedQueryOptions.Filter)) { result = Filter.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Count, AllowedQueryOptions.Count)) { if (InternalRequest.Context.TotalCountFunc == null) { Func <long> countFunc = Count.GetEntityCountFunc(result); if (countFunc != null) { InternalRequest.Context.TotalCountFunc = countFunc; } } if (InternalRequest.IsCountRequest()) { return(result); } } OrderByQueryOption orderBy = OrderBy; // $skip or $top require a stable sort for predictable results. // Result limits require a stable sort to be able to generate a next page link. // If either is present in the query and we have permission, // generate an $orderby that will produce a stable sort. if (querySettings.EnsureStableOrdering && (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip) || IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top) || querySettings.PageSize.HasValue)) { // If there is no OrderBy present, we manufacture a default. // If an OrderBy is already present, we add any missing // properties necessary to make a stable sort. // Instead of failing early here if we cannot generate the OrderBy, // let the IQueryable backend fail (if it has to). orderBy = GenerateStableOrder(); } if (IsAvailableODataQueryOption(orderBy, AllowedQueryOptions.OrderBy)) { result = orderBy.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(SkipToken, AllowedQueryOptions.SkipToken)) { result = SkipToken.ApplyTo(result, querySettings, this); } if (!IsAggregated(Apply?.ApplyClause)) { AddAutoSelectExpandProperties(); } if (SelectExpand != null) { var tempResult = ApplySelectExpand(result, querySettings); if (tempResult != default(IQueryable)) { result = tempResult; } } if (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip)) { result = Skip.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top)) { result = Top.ApplyTo(result, querySettings); } result = ApplyPaging(result, querySettings); return(result); }
/// <summary> /// Apply the individual query to the given IQueryable in the right order. /// </summary> /// <param name="query">The original <see cref="IQueryable"/>.</param> /// <param name="querySettings">The settings to use in query composition.</param> /// <param name="ignoreQueryOptions">The query parameters that are already applied in queries.</param> /// <returns>The new <see cref="IQueryable"/> after the query has been applied to.</returns> public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, AllowedQueryOptions ignoreQueryOptions) { _ignoreQueryOptions = ignoreQueryOptions; return(ApplyTo(query, querySettings)); }
/// <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); }
public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) { if (query == null) { throw Error.ArgumentNull("query"); } if (querySettings == null) { throw Error.ArgumentNull("querySettings"); } IQueryable result = query; // First apply $apply // Section 3.15 of the spec http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326311 ApplyClause apply = null; if (IsAvailableODataQueryOption(Apply, AllowedQueryOptions.Apply)) { result = Apply.ApplyTo(result, querySettings); InternalRequest.Context.ApplyClause = Apply.ApplyClause; this.Context.ElementClrType = Apply.ResultClrType; apply = Apply.ApplyClause; } // Construct the actual query and apply them in the following order: filter, orderby, skip, top if (IsAvailableODataQueryOption(Filter, AllowedQueryOptions.Filter)) { result = Filter.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Count, AllowedQueryOptions.Count)) { if (InternalRequest.Context.TotalCountFunc == null) { Func <long> countFunc = Count.GetEntityCountFunc(result); if (countFunc != null) { InternalRequest.Context.TotalCountFunc = countFunc; } } if (InternalRequest.IsCountRequest()) { return(result); } } OrderByQueryOption orderBy = OrderBy; // $skip or $top require a stable sort for predictable results. // Result limits require a stable sort to be able to generate a next page link. // If either is present in the query and we have permission, // generate an $orderby that will produce a stable sort. if (querySettings.EnsureStableOrdering && (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip) || IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top) || querySettings.PageSize.HasValue)) { // If there is no OrderBy present, we manufacture a default. // If an OrderBy is already present, we add any missing // properties necessary to make a stable sort. // Instead of failing early here if we cannot generate the OrderBy, // let the IQueryable backend fail (if it has to). List <string> applySortOptions = GetApplySortOptions(apply); orderBy = orderBy == null ? GenerateDefaultOrderBy(Context, applySortOptions) : EnsureStableSortOrderBy(orderBy, Context, applySortOptions); } if (IsAvailableODataQueryOption(orderBy, AllowedQueryOptions.OrderBy)) { result = orderBy.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip)) { result = Skip.ApplyTo(result, querySettings); } if (IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top)) { result = Top.ApplyTo(result, querySettings); } AddAutoSelectExpandProperties(); if (SelectExpand != null) { var tempResult = ApplySelectExpand(result, querySettings); if (tempResult != default(IQueryable)) { result = tempResult; } } int pageSize = -1; if (querySettings.PageSize.HasValue) { pageSize = querySettings.PageSize.Value; } else if (querySettings.ModelBoundPageSize.HasValue) { pageSize = querySettings.ModelBoundPageSize.Value; } if (pageSize > 0) { bool resultsLimited; result = LimitResults(result, pageSize, out resultsLimited); if (resultsLimited && InternalRequest.RequestUri != null && InternalRequest.RequestUri.IsAbsoluteUri && InternalRequest.Context.NextLink == null) { Uri nextPageLink = InternalRequest.GetNextPageLink(pageSize); InternalRequest.Context.NextLink = nextPageLink; } } return(result); }
private IQueryable GetQuery() { ODataPath path = this.GetPath(); RestierQueryBuilder builder = new RestierQueryBuilder(this.Api, path); IQueryable queryable = builder.BuildQuery(); this.shouldReturnCount = builder.IsCountPathSegmentPresent; this.shouldWriteRawValue = builder.IsValuePathSegmentPresent; if (queryable == null) { throw new HttpResponseException( this.Request.CreateErrorResponse( HttpStatusCode.NotFound, Resources.ResourceNotFound)); } if (this.shouldReturnCount || this.shouldWriteRawValue) { // Query options don't apply to $count or $value. return queryable; } ODataQueryContext queryContext = new ODataQueryContext(this.Request.ODataProperties().Model, queryable.ElementType, path); ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, this.Request); // TODO GitHubIssue#41 : Ensure stable ordering for query ODataQuerySettings settings = new ODataQuerySettings() { HandleNullPropagation = HandleNullPropagationOption.False, EnsureStableOrdering = true, EnableConstantParameterization = false, PageSize = null, // no support for server enforced PageSize, yet }; queryable = queryOptions.ApplyTo(queryable, settings); return queryable; }