Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataWeb / Server / System / Data / Services / RequestQueryProcessor.cs / 1 / RequestQueryProcessor.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides a class capable of processing query arguments.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Data.Services.Parsing;
using System.Data.Services.Providers;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
/// Use this class to process a web data service request URI.
internal struct RequestQueryProcessor
{
#region Private fields.
/// Original description over which query composition takes place.
private readonly RequestDescription description;
/// Whether the $filter query option can be applied to the request.
private readonly bool filterQueryApplicable;
/// Service with data and configuration.
private readonly IDataService service;
/// Whether the $orderby, $skip, $take options can be applied to the request.
private readonly bool setQueryApplicable;
/// List of paths to be expanded.
private List expandPaths;
/// List of paths to be expanded, before resolving the identifiers
private List> expandPathsAsText;
/// Whether any order has been applied.
private bool orderApplied;
/// Query being composed.
private IQueryable query;
/// Query results to be returned.
private IEnumerable queryResults;
#endregion Private fields.
/// Initializes a new instance.
/// Service with data and configuration.
/// Description for request processed so far.
private RequestQueryProcessor(IDataService service, RequestDescription description)
{
this.service = service;
this.description = description;
this.orderApplied = false;
this.query = (IQueryable)description.RequestEnumerable;
this.filterQueryApplicable =
description.TargetKind == RequestTargetKind.Resource ||
#if ASTORIA_OPEN_OBJECT
description.TargetKind == RequestTargetKind.OpenProperty ||
#endif
description.TargetKind == RequestTargetKind.ComplexObject;
this.setQueryApplicable = (
#if ASTORIA_OPEN_OBJECT
description.TargetKind == RequestTargetKind.OpenProperty ||
#endif
description.TargetKind == RequestTargetKind.Resource) && !description.IsSingleResult;
this.expandPaths = null;
this.expandPathsAsText = null;
this.queryResults = null;
}
/// Checks that no query arguments were sent in the request.
/// Service to check.
///
/// Regular processing checks argument applicability, but for
/// service operations that return an IEnumerable this is part
/// of the contract on service operations, rather than a structural
/// check on the request.
///
internal static void CheckEmptyQueryArguments(IDataService service)
{
Debug.Assert(service != null, "service != null");
IDataServiceHost host = service.Host;
if (!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSkip)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringTop)))
{
// 400: Bad Request
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryNoOptionsApplicable);
}
}
///
/// Composes a property navigation with the appropriate filter lamba, as appropriate.
///
/// Member access expression to compose.
/// Lambda expression used for the filter.
/// Whether null propagation is required on the .
/// The composed expression.
internal static Expression ComposePropertyNavigation(
Expression expression,
LambdaExpression filterLambda,
bool propagateNull)
{
Debug.Assert(expression != null, "expression != null");
Debug.Assert(filterLambda != null, "filterLambda != null");
Expression fixedFilter = ParameterReplacerVisitor.Replace(
filterLambda.Body,
filterLambda.Parameters[0],
expression);
Expression test;
if (propagateNull)
{
Expression nullConstant = System.Data.Services.Parsing.RequestQueryParser.NullLiteral;
test = Expression.AndAlso(Expression.NotEqual(expression, nullConstant), fixedFilter);
}
else
{
test = fixedFilter;
}
Expression conditionTrue = expression;
Expression conditionFalse = Expression.Constant(null, conditionTrue.Type);
return Expression.Condition(test, conditionTrue, conditionFalse);
}
///
/// Processes query arguments and returns a request description for
/// the resulting query.
///
/// Service with data and configuration information.
/// Description for request processed so far.
/// A new .
internal static RequestDescription ProcessQuery(IDataService service, RequestDescription description)
{
Debug.Assert(service != null, "service != null");
// When the request doesn't produce an IQueryable result,
// we can short-circuit all further processing.
if (!(description.RequestEnumerable is IQueryable))
{
RequestQueryProcessor.CheckEmptyQueryArguments(service);
return description;
}
else
{
return new RequestQueryProcessor(service, description).ProcessQuery();
}
}
/// Generic method to invoke an OrderBy or OrderByDescending method on an IQueryable source.
/// Element type of the source.
/// Result type of the key.
/// Source query.
/// Lambda expression that turns TSource into TKey.
/// Whether the ordering should be ascending or not.
/// Whether this is the first-level order; false if this is an inner-level order-by.
/// A new query that sorts TSource by TKey.
private static IQueryable InvokeOrderBy(IQueryable source, LambdaExpression keySelector, bool ascending, bool firstLevelOrder)
{
Debug.Assert(source != null, "source != null");
Debug.Assert(keySelector != null, "keySelector != null");
Expression> typedKeySelector = (Expression>)keySelector;
if (firstLevelOrder)
{
IQueryable typedSource = (IQueryable)source;
if (ascending)
{
return Queryable.OrderBy(typedSource, typedKeySelector);
}
else
{
return Queryable.OrderByDescending(typedSource, typedKeySelector);
}
}
else
{
IOrderedQueryable typedSource = (IOrderedQueryable)source;
if (ascending)
{
return Queryable.ThenBy(typedSource, typedKeySelector);
}
else
{
return Queryable.ThenByDescending(typedSource, typedKeySelector);
}
}
}
/// Generic method to invoke a Skip method on an IQueryable source.
/// Element type of the source.
/// Source query.
/// Element count to skip.
/// A new query that skips elements of .
private static IQueryable InvokeSkip(IQueryable source, int count)
{
Debug.Assert(source != null, "source != null");
IQueryable typedSource = (IQueryable)source;
return Queryable.Skip(typedSource, count);
}
/// Generic method to invoke a Take method on an IQueryable source.
/// Element type of the source.
/// Source query.
/// Element count to take.
/// A new query that takes elements of .
private static IQueryable InvokeTake(IQueryable source, int count)
{
Debug.Assert(source != null, "source != null");
IQueryable typedSource = (IQueryable)source;
return Queryable.Take(typedSource, count);
}
/// Composes an OrderBy operation on the specified query.
/// Query to compose over.
/// Property to order by.
/// Whether the ordering should be ascending or descending.
/// Whether this is the first-level order; false if this is an inner-level order-by.
/// A query that orders the specified query.
private static IQueryable OrderBy(IQueryable query, PropertyInfo property, bool ascending, bool firstLevelOrder)
{
Debug.Assert(query != null, "query != null");
Debug.Assert(property != null, "property != null");
ParameterExpression parameter = Expression.Parameter(query.ElementType, "element");
MemberExpression body = Expression.Property(parameter, property);
LambdaExpression selector = Expression.Lambda(body, parameter);
MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeOrderBy", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(query.ElementType, body.Type);
return (IQueryable)method.Invoke(null, new object[] { query, selector, ascending, firstLevelOrder });
}
/// Reads an $expand clause.
/// Value to read.
/// A list of paths, each of which is a list of identifiers.
private static List> ReadExpand(string expand)
{
Debug.Assert(!String.IsNullOrEmpty(expand), "!String.IsNullOrEmpty(expand)");
List> result = new List>();
List currentPath = null;
ExpressionLexer lexer = new ExpressionLexer(expand);
while (lexer.CurrentToken.Id != TokenId.End)
{
string identifier = lexer.ReadDottedIdentifier();
if (currentPath == null)
{
currentPath = new List();
result.Add(currentPath);
}
currentPath.Add(identifier);
// Check whether we're at the end, whether we're drilling in,
// or whether we're finishing with this path.
TokenId tokenId = lexer.CurrentToken.Id;
if (tokenId != TokenId.End)
{
if (tokenId == TokenId.Comma)
{
currentPath = null;
}
else
{
lexer.ValidateToken(TokenId.Slash);
}
lexer.NextToken();
}
}
return result;
}
/// Composes a Skip operation on the specified query.
/// Query to compose over.
/// Number of elements to skip.
/// A query that skips the specified number of elements.
private static IQueryable Skip(IQueryable query, int count)
{
Debug.Assert(query != null, "query != null");
MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeSkip", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(query.ElementType);
return (IQueryable)method.Invoke(null, new object[] { query, count });
}
/// Composes a Take operation on the specified query.
/// Query to compose over.
/// Number of elements to take.
/// A query that takes up to a specific number of elements.
private static IQueryable Take(IQueryable query, int count)
{
Debug.Assert(query != null, "query != null");
MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeTake", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(query.ElementType);
return (IQueryable)method.Invoke(null, new object[] { query, count });
}
/// Applies a default order, if possible.
private void ApplyDefaultOrder()
{
// Check that default ordering should be applied.
if (this.description.TargetKind == RequestTargetKind.Resource &&
(this.description.TargetSource == RequestTargetSource.EntitySet ||
this.description.TargetSource == RequestTargetSource.Property))
{
ResourceType resourceType = this.service.Provider.GetResourceType(this.description.TargetElementType);
Debug.Assert(resourceType != null, "resourceType != null");
bool firstLevelOrder = true;
foreach (ResourceProperty keyProperty in resourceType.KeyProperties)
{
this.query = OrderBy(this.query, keyProperty.PropertyInfo, true /* ascending */, firstLevelOrder);
firstLevelOrder = false;
}
}
this.orderApplied = true;
}
/// Checks and resolved all textual expand paths and removes unnecessary paths.
private void CheckExpandPaths()
{
Debug.Assert(this.expandPathsAsText != null, "this.expandPaths != null");
if (this.expandPathsAsText.Count > 0 && this.query == null)
{
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable);
}
this.expandPaths = new List(this.expandPathsAsText.Count);
for (int i = this.expandPathsAsText.Count - 1; i >= 0; i--)
{
Type type = this.query.ElementType;
ResourceType resourceType = this.service.Provider.GetResourceType(type);
List path = this.expandPathsAsText[i];
ExpandSegmentCollection segments = new ExpandSegmentCollection(path.Count);
bool ignorePath = false;
for (int j = 0; j < path.Count; j++)
{
string pathSegment = path[j];
#if ASTORIA_OPEN_OBJECT
if (resourceType.IsOpenType)
{
ignorePath = false;
segments.Add(new ExpandSegment(pathSegment, null));
continue;
}
#endif
ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment);
if (property == null)
{
throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment));
}
Expression filter = null;
if (property.ResourceContainer != null)
{
bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference;
this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, singleResult);
filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer);
}
int expected = this.service.Configuration.MaxResultsPerCollection;
segments.Add(new ExpandSegment(property.Name, filter, expected, property.ResourceContainer));
resourceType = property.ResourceType;
ignorePath = ignorePath || property.TypeKind != ResourceTypeKind.EntityType;
}
// Even though the path might be correct, we might need to
// ignore because it's a primitive.
if (ignorePath)
{
this.expandPathsAsText.RemoveAt(i);
}
else
{
this.expandPaths.Add(segments);
}
}
}
/// Checks that all resolved expand paths are valid.
private void CheckExpandUpdatedPaths()
{
Debug.Assert(this.expandPaths != null, "this.expandPaths != null");
for (int i = this.expandPaths.Count - 1; i >= 0; i--)
{
Type type = this.query.ElementType;
ResourceType resourceType = this.service.Provider.GetResourceType(type);
ExpandSegmentCollection path = this.expandPaths[i];
for (int j = 0; j < path.Count; j++)
{
string pathSegment = path[j].Name;
#if ASTORIA_OPEN_OBJECT
if (resourceType.IsOpenType)
{
break;
}
#endif
ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment);
if (property == null)
{
throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment));
}
resourceType = property.ResourceType;
}
// No need to strip primitive types for object context; the framework will throw, but the user code
// should know not to add them.
}
}
/// Checks that the query option is applicable to this request.
private void CheckFilterQueryApplicable()
{
if (!this.filterQueryApplicable)
{
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryFilterOptionNotApplicable);
}
}
/// Checks that set query options are applicable to this request.
private void CheckSetQueryApplicable()
{
if (!this.setQueryApplicable)
{
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySetOptionsNotApplicable);
}
}
/// Processes the $expand argument of the request.
private void ProcessExpand()
{
string expand = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand);
if (!String.IsNullOrEmpty(expand))
{
this.expandPathsAsText = ReadExpand(expand);
}
else
{
this.expandPathsAsText = new List>();
}
this.CheckExpandPaths();
this.service.InternalApplyingExpansions(this.query, this.expandPaths);
this.CheckExpandUpdatedPaths();
if (this.expandPaths.Count > 0)
{
this.queryResults = ((IExpandProvider)this.service.Provider).ApplyExpansions(this.query, this.expandPaths);
}
else
{
this.queryResults = this.query;
}
Debug.Assert(this.queryResults != null, "this.queryResults != null");
}
/// Processes the $filter argument of the request.
private void ProcessFilter()
{
string filter = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter);
if (!String.IsNullOrEmpty(filter))
{
this.CheckFilterQueryApplicable();
this.query = RequestQueryParser.Where(this.service, this.query, filter);
}
}
/// Processes the $orderby argument of the request.
private void ProcessOrderBy()
{
string orderBy = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy);
if (!String.IsNullOrEmpty(orderBy))
{
this.CheckSetQueryApplicable();
IQueryable orderedQuery = RequestQueryParser.OrderBy(this.service, this.query, orderBy);
if (orderedQuery != this.query)
{
this.query = orderedQuery;
this.orderApplied = true;
}
}
}
///
/// Processes query arguments and returns a request description for
/// the resulting query.
///
/// A modified that includes query information.
private RequestDescription ProcessQuery()
{
// NOTE: The order set by ProcessOrderBy may be reset by other operations other than skip/top,
// so filtering needs to occur first.
this.ProcessFilter();
this.ProcessOrderBy();
this.ProcessSkip();
this.ProcessTop();
// NOTE: expand goes last, as it may be reset by other operations.
this.ProcessExpand();
Debug.Assert(this.queryResults != null, "this.queryResults != null -- otherwise ProcessExpand didn't set it");
return new RequestDescription(this.description, this.queryResults, this.expandPaths);
}
/// Processes the $skip argument of the request.
private void ProcessSkip()
{
int count;
if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringSkip, out count))
{
this.CheckSetQueryApplicable();
this.query = Skip(this.query, count);
}
}
///
/// Checks whether the specified argument should be processed and what
/// its value is.
///
/// Name of the query item, $top or $skip.
/// The value for the query item.
/// true if the argument should be processed; false otherwise.
private bool ProcessSkipOrTopArgument(string queryItem, out int count)
{
Debug.Assert(queryItem != null, "queryItem != null");
string itemText = this.service.Host.GetQueryStringItem(queryItem);
if (String.IsNullOrEmpty(itemText))
{
count = 0;
return false;
}
if (!this.orderApplied)
{
this.ApplyDefaultOrder();
}
if (!Int32.TryParse(itemText, NumberStyles.Integer, CultureInfo.InvariantCulture, out count))
{
throw DataServiceException.CreateSyntaxError(
Strings.RequestQueryProcessor_IncorrectArgumentFormat(queryItem, itemText));
}
return true;
}
/// Processes the $top argument of the request.
private void ProcessTop()
{
int count;
if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringTop, out count))
{
this.CheckSetQueryApplicable();
this.query = Take(this.query, count);
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides a class capable of processing query arguments.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Data.Services.Parsing;
using System.Data.Services.Providers;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
/// Use this class to process a web data service request URI.
internal struct RequestQueryProcessor
{
#region Private fields.
/// Original description over which query composition takes place.
private readonly RequestDescription description;
/// Whether the $filter query option can be applied to the request.
private readonly bool filterQueryApplicable;
/// Service with data and configuration.
private readonly IDataService service;
/// Whether the $orderby, $skip, $take options can be applied to the request.
private readonly bool setQueryApplicable;
/// List of paths to be expanded.
private List expandPaths;
/// List of paths to be expanded, before resolving the identifiers
private List> expandPathsAsText;
/// Whether any order has been applied.
private bool orderApplied;
/// Query being composed.
private IQueryable query;
/// Query results to be returned.
private IEnumerable queryResults;
#endregion Private fields.
/// Initializes a new instance.
/// Service with data and configuration.
/// Description for request processed so far.
private RequestQueryProcessor(IDataService service, RequestDescription description)
{
this.service = service;
this.description = description;
this.orderApplied = false;
this.query = (IQueryable)description.RequestEnumerable;
this.filterQueryApplicable =
description.TargetKind == RequestTargetKind.Resource ||
#if ASTORIA_OPEN_OBJECT
description.TargetKind == RequestTargetKind.OpenProperty ||
#endif
description.TargetKind == RequestTargetKind.ComplexObject;
this.setQueryApplicable = (
#if ASTORIA_OPEN_OBJECT
description.TargetKind == RequestTargetKind.OpenProperty ||
#endif
description.TargetKind == RequestTargetKind.Resource) && !description.IsSingleResult;
this.expandPaths = null;
this.expandPathsAsText = null;
this.queryResults = null;
}
/// Checks that no query arguments were sent in the request.
/// Service to check.
///
/// Regular processing checks argument applicability, but for
/// service operations that return an IEnumerable this is part
/// of the contract on service operations, rather than a structural
/// check on the request.
///
internal static void CheckEmptyQueryArguments(IDataService service)
{
Debug.Assert(service != null, "service != null");
IDataServiceHost host = service.Host;
if (!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSkip)) ||
!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringTop)))
{
// 400: Bad Request
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryNoOptionsApplicable);
}
}
///
/// Composes a property navigation with the appropriate filter lamba, as appropriate.
///
/// Member access expression to compose.
/// Lambda expression used for the filter.
/// Whether null propagation is required on the .
/// The composed expression.
internal static Expression ComposePropertyNavigation(
Expression expression,
LambdaExpression filterLambda,
bool propagateNull)
{
Debug.Assert(expression != null, "expression != null");
Debug.Assert(filterLambda != null, "filterLambda != null");
Expression fixedFilter = ParameterReplacerVisitor.Replace(
filterLambda.Body,
filterLambda.Parameters[0],
expression);
Expression test;
if (propagateNull)
{
Expression nullConstant = System.Data.Services.Parsing.RequestQueryParser.NullLiteral;
test = Expression.AndAlso(Expression.NotEqual(expression, nullConstant), fixedFilter);
}
else
{
test = fixedFilter;
}
Expression conditionTrue = expression;
Expression conditionFalse = Expression.Constant(null, conditionTrue.Type);
return Expression.Condition(test, conditionTrue, conditionFalse);
}
///
/// Processes query arguments and returns a request description for
/// the resulting query.
///
/// Service with data and configuration information.
/// Description for request processed so far.
/// A new .
internal static RequestDescription ProcessQuery(IDataService service, RequestDescription description)
{
Debug.Assert(service != null, "service != null");
// When the request doesn't produce an IQueryable result,
// we can short-circuit all further processing.
if (!(description.RequestEnumerable is IQueryable))
{
RequestQueryProcessor.CheckEmptyQueryArguments(service);
return description;
}
else
{
return new RequestQueryProcessor(service, description).ProcessQuery();
}
}
/// Generic method to invoke an OrderBy or OrderByDescending method on an IQueryable source.
/// Element type of the source.
/// Result type of the key.
/// Source query.
/// Lambda expression that turns TSource into TKey.
/// Whether the ordering should be ascending or not.
/// Whether this is the first-level order; false if this is an inner-level order-by.
/// A new query that sorts TSource by TKey.
private static IQueryable InvokeOrderBy(IQueryable source, LambdaExpression keySelector, bool ascending, bool firstLevelOrder)
{
Debug.Assert(source != null, "source != null");
Debug.Assert(keySelector != null, "keySelector != null");
Expression> typedKeySelector = (Expression>)keySelector;
if (firstLevelOrder)
{
IQueryable typedSource = (IQueryable)source;
if (ascending)
{
return Queryable.OrderBy(typedSource, typedKeySelector);
}
else
{
return Queryable.OrderByDescending(typedSource, typedKeySelector);
}
}
else
{
IOrderedQueryable typedSource = (IOrderedQueryable)source;
if (ascending)
{
return Queryable.ThenBy(typedSource, typedKeySelector);
}
else
{
return Queryable.ThenByDescending(typedSource, typedKeySelector);
}
}
}
/// Generic method to invoke a Skip method on an IQueryable source.
/// Element type of the source.
/// Source query.
/// Element count to skip.
/// A new query that skips elements of .
private static IQueryable InvokeSkip(IQueryable source, int count)
{
Debug.Assert(source != null, "source != null");
IQueryable typedSource = (IQueryable)source;
return Queryable.Skip(typedSource, count);
}
/// Generic method to invoke a Take method on an IQueryable source.
/// Element type of the source.
/// Source query.
/// Element count to take.
/// A new query that takes elements of .
private static IQueryable InvokeTake(IQueryable source, int count)
{
Debug.Assert(source != null, "source != null");
IQueryable typedSource = (IQueryable)source;
return Queryable.Take(typedSource, count);
}
/// Composes an OrderBy operation on the specified query.
/// Query to compose over.
/// Property to order by.
/// Whether the ordering should be ascending or descending.
/// Whether this is the first-level order; false if this is an inner-level order-by.
/// A query that orders the specified query.
private static IQueryable OrderBy(IQueryable query, PropertyInfo property, bool ascending, bool firstLevelOrder)
{
Debug.Assert(query != null, "query != null");
Debug.Assert(property != null, "property != null");
ParameterExpression parameter = Expression.Parameter(query.ElementType, "element");
MemberExpression body = Expression.Property(parameter, property);
LambdaExpression selector = Expression.Lambda(body, parameter);
MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeOrderBy", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(query.ElementType, body.Type);
return (IQueryable)method.Invoke(null, new object[] { query, selector, ascending, firstLevelOrder });
}
/// Reads an $expand clause.
/// Value to read.
/// A list of paths, each of which is a list of identifiers.
private static List> ReadExpand(string expand)
{
Debug.Assert(!String.IsNullOrEmpty(expand), "!String.IsNullOrEmpty(expand)");
List> result = new List>();
List currentPath = null;
ExpressionLexer lexer = new ExpressionLexer(expand);
while (lexer.CurrentToken.Id != TokenId.End)
{
string identifier = lexer.ReadDottedIdentifier();
if (currentPath == null)
{
currentPath = new List();
result.Add(currentPath);
}
currentPath.Add(identifier);
// Check whether we're at the end, whether we're drilling in,
// or whether we're finishing with this path.
TokenId tokenId = lexer.CurrentToken.Id;
if (tokenId != TokenId.End)
{
if (tokenId == TokenId.Comma)
{
currentPath = null;
}
else
{
lexer.ValidateToken(TokenId.Slash);
}
lexer.NextToken();
}
}
return result;
}
/// Composes a Skip operation on the specified query.
/// Query to compose over.
/// Number of elements to skip.
/// A query that skips the specified number of elements.
private static IQueryable Skip(IQueryable query, int count)
{
Debug.Assert(query != null, "query != null");
MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeSkip", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(query.ElementType);
return (IQueryable)method.Invoke(null, new object[] { query, count });
}
/// Composes a Take operation on the specified query.
/// Query to compose over.
/// Number of elements to take.
/// A query that takes up to a specific number of elements.
private static IQueryable Take(IQueryable query, int count)
{
Debug.Assert(query != null, "query != null");
MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeTake", BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(query.ElementType);
return (IQueryable)method.Invoke(null, new object[] { query, count });
}
/// Applies a default order, if possible.
private void ApplyDefaultOrder()
{
// Check that default ordering should be applied.
if (this.description.TargetKind == RequestTargetKind.Resource &&
(this.description.TargetSource == RequestTargetSource.EntitySet ||
this.description.TargetSource == RequestTargetSource.Property))
{
ResourceType resourceType = this.service.Provider.GetResourceType(this.description.TargetElementType);
Debug.Assert(resourceType != null, "resourceType != null");
bool firstLevelOrder = true;
foreach (ResourceProperty keyProperty in resourceType.KeyProperties)
{
this.query = OrderBy(this.query, keyProperty.PropertyInfo, true /* ascending */, firstLevelOrder);
firstLevelOrder = false;
}
}
this.orderApplied = true;
}
/// Checks and resolved all textual expand paths and removes unnecessary paths.
private void CheckExpandPaths()
{
Debug.Assert(this.expandPathsAsText != null, "this.expandPaths != null");
if (this.expandPathsAsText.Count > 0 && this.query == null)
{
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable);
}
this.expandPaths = new List(this.expandPathsAsText.Count);
for (int i = this.expandPathsAsText.Count - 1; i >= 0; i--)
{
Type type = this.query.ElementType;
ResourceType resourceType = this.service.Provider.GetResourceType(type);
List path = this.expandPathsAsText[i];
ExpandSegmentCollection segments = new ExpandSegmentCollection(path.Count);
bool ignorePath = false;
for (int j = 0; j < path.Count; j++)
{
string pathSegment = path[j];
#if ASTORIA_OPEN_OBJECT
if (resourceType.IsOpenType)
{
ignorePath = false;
segments.Add(new ExpandSegment(pathSegment, null));
continue;
}
#endif
ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment);
if (property == null)
{
throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment));
}
Expression filter = null;
if (property.ResourceContainer != null)
{
bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference;
this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, singleResult);
filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer);
}
int expected = this.service.Configuration.MaxResultsPerCollection;
segments.Add(new ExpandSegment(property.Name, filter, expected, property.ResourceContainer));
resourceType = property.ResourceType;
ignorePath = ignorePath || property.TypeKind != ResourceTypeKind.EntityType;
}
// Even though the path might be correct, we might need to
// ignore because it's a primitive.
if (ignorePath)
{
this.expandPathsAsText.RemoveAt(i);
}
else
{
this.expandPaths.Add(segments);
}
}
}
/// Checks that all resolved expand paths are valid.
private void CheckExpandUpdatedPaths()
{
Debug.Assert(this.expandPaths != null, "this.expandPaths != null");
for (int i = this.expandPaths.Count - 1; i >= 0; i--)
{
Type type = this.query.ElementType;
ResourceType resourceType = this.service.Provider.GetResourceType(type);
ExpandSegmentCollection path = this.expandPaths[i];
for (int j = 0; j < path.Count; j++)
{
string pathSegment = path[j].Name;
#if ASTORIA_OPEN_OBJECT
if (resourceType.IsOpenType)
{
break;
}
#endif
ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment);
if (property == null)
{
throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment));
}
resourceType = property.ResourceType;
}
// No need to strip primitive types for object context; the framework will throw, but the user code
// should know not to add them.
}
}
/// Checks that the query option is applicable to this request.
private void CheckFilterQueryApplicable()
{
if (!this.filterQueryApplicable)
{
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryFilterOptionNotApplicable);
}
}
/// Checks that set query options are applicable to this request.
private void CheckSetQueryApplicable()
{
if (!this.setQueryApplicable)
{
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySetOptionsNotApplicable);
}
}
/// Processes the $expand argument of the request.
private void ProcessExpand()
{
string expand = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand);
if (!String.IsNullOrEmpty(expand))
{
this.expandPathsAsText = ReadExpand(expand);
}
else
{
this.expandPathsAsText = new List>();
}
this.CheckExpandPaths();
this.service.InternalApplyingExpansions(this.query, this.expandPaths);
this.CheckExpandUpdatedPaths();
if (this.expandPaths.Count > 0)
{
this.queryResults = ((IExpandProvider)this.service.Provider).ApplyExpansions(this.query, this.expandPaths);
}
else
{
this.queryResults = this.query;
}
Debug.Assert(this.queryResults != null, "this.queryResults != null");
}
/// Processes the $filter argument of the request.
private void ProcessFilter()
{
string filter = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter);
if (!String.IsNullOrEmpty(filter))
{
this.CheckFilterQueryApplicable();
this.query = RequestQueryParser.Where(this.service, this.query, filter);
}
}
/// Processes the $orderby argument of the request.
private void ProcessOrderBy()
{
string orderBy = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy);
if (!String.IsNullOrEmpty(orderBy))
{
this.CheckSetQueryApplicable();
IQueryable orderedQuery = RequestQueryParser.OrderBy(this.service, this.query, orderBy);
if (orderedQuery != this.query)
{
this.query = orderedQuery;
this.orderApplied = true;
}
}
}
///
/// Processes query arguments and returns a request description for
/// the resulting query.
///
/// A modified that includes query information.
private RequestDescription ProcessQuery()
{
// NOTE: The order set by ProcessOrderBy may be reset by other operations other than skip/top,
// so filtering needs to occur first.
this.ProcessFilter();
this.ProcessOrderBy();
this.ProcessSkip();
this.ProcessTop();
// NOTE: expand goes last, as it may be reset by other operations.
this.ProcessExpand();
Debug.Assert(this.queryResults != null, "this.queryResults != null -- otherwise ProcessExpand didn't set it");
return new RequestDescription(this.description, this.queryResults, this.expandPaths);
}
/// Processes the $skip argument of the request.
private void ProcessSkip()
{
int count;
if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringSkip, out count))
{
this.CheckSetQueryApplicable();
this.query = Skip(this.query, count);
}
}
///
/// Checks whether the specified argument should be processed and what
/// its value is.
///
/// Name of the query item, $top or $skip.
/// The value for the query item.
/// true if the argument should be processed; false otherwise.
private bool ProcessSkipOrTopArgument(string queryItem, out int count)
{
Debug.Assert(queryItem != null, "queryItem != null");
string itemText = this.service.Host.GetQueryStringItem(queryItem);
if (String.IsNullOrEmpty(itemText))
{
count = 0;
return false;
}
if (!this.orderApplied)
{
this.ApplyDefaultOrder();
}
if (!Int32.TryParse(itemText, NumberStyles.Integer, CultureInfo.InvariantCulture, out count))
{
throw DataServiceException.CreateSyntaxError(
Strings.RequestQueryProcessor_IncorrectArgumentFormat(queryItem, itemText));
}
return true;
}
/// Processes the $top argument of the request.
private void ProcessTop()
{
int count;
if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringTop, out count))
{
this.CheckSetQueryApplicable();
this.query = Take(this.query, count);
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- XmlSerializerSection.cs
- MetadataArtifactLoader.cs
- CategoryEditor.cs
- WindowsFont.cs
- Subtree.cs
- XmlSerializerAssemblyAttribute.cs
- FileSystemInfo.cs
- ReverseQueryOperator.cs
- NamespaceQuery.cs
- WmfPlaceableFileHeader.cs
- PocoEntityKeyStrategy.cs
- UriSectionData.cs
- EpmAttributeNameBuilder.cs
- MethodBuilderInstantiation.cs
- SqlRetyper.cs
- Accessible.cs
- EntityDataSourceView.cs
- ReadOnlyCollectionBuilder.cs
- SelectionItemProviderWrapper.cs
- RestHandlerFactory.cs
- IgnoreFileBuildProvider.cs
- CollectionCodeDomSerializer.cs
- SettingsSavedEventArgs.cs
- ActivityMarkupSerializer.cs
- HttpRequestCacheValidator.cs
- SoapCommonClasses.cs
- InplaceBitmapMetadataWriter.cs
- AttributedMetaModel.cs
- SpnegoTokenProvider.cs
- NativeMethods.cs
- NativeMethodsCLR.cs
- ScrollEvent.cs
- ThicknessKeyFrameCollection.cs
- ApplicationInterop.cs
- ipaddressinformationcollection.cs
- DataControlReferenceCollection.cs
- ArrayListCollectionBase.cs
- ListViewDeletedEventArgs.cs
- ImplicitInputBrush.cs
- LinkedDataMemberFieldEditor.cs
- Frame.cs
- UniformGrid.cs
- DataTemplate.cs
- SortKey.cs
- UpdatePanel.cs
- BindingObserver.cs
- Annotation.cs
- DataGridViewDataConnection.cs
- ButtonBaseAutomationPeer.cs
- webclient.cs
- PlanCompiler.cs
- PerformanceCounterManager.cs
- ProviderMetadataCachedInformation.cs
- EndOfStreamException.cs
- HtmlTableRowCollection.cs
- DataGridViewAutoSizeModeEventArgs.cs
- HtmlControl.cs
- PropertyManager.cs
- UrlPath.cs
- URLEditor.cs
- CngKey.cs
- WindowsFormsHost.cs
- ArraySegment.cs
- CompModSwitches.cs
- Misc.cs
- Environment.cs
- RefType.cs
- ImageKeyConverter.cs
- CheckBox.cs
- SettingsSavedEventArgs.cs
- ProxyWebPart.cs
- AnnotationStore.cs
- TableAutomationPeer.cs
- TablePatternIdentifiers.cs
- AssemblyBuilder.cs
- XPathNavigatorReader.cs
- DocumentSequence.cs
- State.cs
- TypeDelegator.cs
- HttpConfigurationSystem.cs
- OleDbError.cs
- SmiEventStream.cs
- DataRowExtensions.cs
- ManagementNamedValueCollection.cs
- CodeDOMUtility.cs
- WorkflowInspectionServices.cs
- CapacityStreamGeometryContext.cs
- XmlSchemaIdentityConstraint.cs
- BevelBitmapEffect.cs
- DateTimeOffsetConverter.cs
- TransformerInfo.cs
- DataGridViewCellCollection.cs
- PagePropertiesChangingEventArgs.cs
- RowToFieldTransformer.cs
- Classification.cs
- SocketAddress.cs
- WhitespaceRule.cs
- StackSpiller.Bindings.cs
- CollectionAdapters.cs
- HostedElements.cs