/// <summary>Copy constructor.</summary> /// <param name="other">Another <see cref="SegmentInfo"/> to get a shallow copy of.</param> internal SegmentInfo(SegmentInfo other) { Debug.Assert(other != null, "other != null"); this.Identifier = other.Identifier; this.Key = other.Key; this.Operation = other.Operation; this.ProjectedProperty = other.ProjectedProperty; this.RequestExpression = other.RequestExpression; this.RequestEnumerable = other.RequestEnumerable; this.SingleResult = other.SingleResult; this.TargetResourceSet = other.TargetResourceSet; this.TargetKind = other.TargetKind; this.TargetSource = other.TargetSource; this.TargetResourceType = other.TargetResourceType; }
/// <summary> /// Gets the query root for the segment. /// </summary> /// <param name="segment">Segment to compose the query.</param> /// <param name="service">The data service instance.</param> /// <param name="isLastSegment">true if <paramref name="segment"/> is the last segment; false otherwise.</param> /// <param name="checkRights">true if we need to check rights for this segment; false otherwise.</param> private static void ComposeExpressionForEntitySet(SegmentInfo segment, IDataService service, bool isLastSegment, bool checkRights) { Debug.Assert( segment != null && segment.TargetResourceSet != null && segment.TargetSource == RequestTargetSource.EntitySet, "segment != null && segment.TargetContainer != null && segment.TargetSource == RequestTargetSource.EntitySet"); Debug.Assert(service != null, "service != null"); bool hasKeyValues = segment.HasKeyValues; if (ShouldRequestQuery(service, isLastSegment, false, hasKeyValues)) { segment.RequestExpression = service.Provider.GetQueryRootForResourceSet(segment.TargetResourceSet); Debug.Assert(segment.RequestExpression != null, "segment.RequestExpression != null"); } if (hasKeyValues) { ApplyKeyToExpression(segment); } if (checkRights) { DataServiceConfiguration.CheckResourceRightsForRead(segment.TargetResourceSet, segment.SingleResult); } if (segment.RequestExpression != null) { // We only need to invoke the query interceptor if we called get query root. segment.RequestExpression = DataServiceConfiguration.ComposeResourceContainer(service, segment.TargetResourceSet, segment.RequestExpression); } }
/// <summary> /// Create a new request description from the given request description and new entity as the result. /// </summary> /// <param name="description">Existing request description.</param> /// <param name="entity">entity that needs to be the result of the new request.</param> /// <param name="targetResourceType">The new target resource type for the new request description.</param> /// <returns>a new instance of request description containing information about the given entity.</returns> internal static RequestDescription CreateSingleResultRequestDescription(RequestDescription description, object entity, ResourceType targetResourceType) { // Create a new request description for the results that will be returned. SegmentInfo segmentInfo = new SegmentInfo { RequestExpression = Expression.Constant(entity), RequestEnumerable = new[] { entity }, TargetKind = description.TargetKind, TargetSource = description.TargetSource, SingleResult = true, ProjectedProperty = description.Property, TargetResourceType = targetResourceType, TargetResourceSet = description.LastSegmentInfo.TargetResourceSet, Identifier = description.LastSegmentInfo.Identifier }; #if DEBUG segmentInfo.AssertValid(); #endif IList<SegmentInfo> segmentInfos = description.SegmentInfos; segmentInfos[segmentInfos.Count - 1] = segmentInfo; RequestDescription resultDescription = new RequestDescription(segmentInfos, description.ResultUri); resultDescription.CopyFrom(description); return resultDescription; }
/// <summary> /// Creates a property segment /// </summary> /// <param name="previous">previous segment info.</param> /// <param name="property">property to create the segment for.</param> /// <returns>new segment for the given property.</returns> private SegmentInfo CreatePropertySegment(SegmentInfo previous, ResourceProperty property) { // Handle a strongly-typed property. SegmentInfo segment = new SegmentInfo() { Identifier = property.Name, ProjectedProperty = property }; segment.TargetResourceType = property.ResourceType; ResourcePropertyKind propertyKind = property.Kind; segment.SingleResult = (propertyKind != ResourcePropertyKind.ResourceSetReference); segment.TargetSource = RequestTargetSource.Property; if (previous.TargetKind == RequestTargetKind.Link && property.TypeKind != ResourceTypeKind.EntityType) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment(segment.Identifier, XmlConstants.UriLinkSegment)); } switch (propertyKind) { case ResourcePropertyKind.ComplexType: segment.TargetKind = RequestTargetKind.ComplexObject; break; case ResourcePropertyKind.Collection: segment.TargetKind = RequestTargetKind.Collection; break; case ResourcePropertyKind.ResourceReference: case ResourcePropertyKind.ResourceSetReference: segment.TargetKind = RequestTargetKind.Resource; segment.TargetResourceSet = this.providerWrapper.GetResourceSet(previous.TargetResourceSet, previous.TargetResourceType, property); if (segment.TargetResourceSet == null) { throw DataServiceException.CreateResourceNotFound(property.Name); } break; default: Debug.Assert(property.IsOfKind(ResourcePropertyKind.Primitive), "must be primitive type property"); segment.TargetKind = RequestTargetKind.Primitive; break; } return segment; }
/// <summary> /// Creates the next segment. /// </summary> /// <param name="previous">The previous segment.</param> /// <param name="segment">The the next segment.</param> /// <returns>The newly created next segment.</returns> private SegmentInfo CreateNextSegment(SegmentInfo previous, ODataPathSegment segment) { if (segment is ValueSegment) { return CreateValueSegment(previous); } SegmentInfo segmentInfo; if (segment is CountSegment) { segmentInfo = CreateCountSegment(previous); // DEVNOTE: Prior to refactor, $count was handled alongside properties. See more detailed comment in HandleCountSegment Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return segmentInfo; } // if its not one of the recognized special segments, then it must be a property, type-segment, or key value. Debug.Assert( previous.TargetKind == RequestTargetKind.ComplexObject || previous.TargetKind == RequestTargetKind.Resource || previous.TargetKind == RequestTargetKind.OpenProperty || previous.TargetKind == RequestTargetKind.Link, "previous.TargetKind(" + previous.TargetKind + ") can have properties"); CheckSegmentIsComposable(previous); // if the segment corresponds to a declared property, handle it // otherwise, fall back to type-segments, actions, and dynamic/open properties ResourceProperty projectedProperty; if (TryGetPropertyFromSegment(segment, out projectedProperty)) { CheckSingleResult(previous.SingleResult, previous.Identifier); if (projectedProperty.IsOfKind(ResourcePropertyKind.Stream)) { segmentInfo = CreateNamedStreamSegment(previous, projectedProperty); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return segmentInfo; } segmentInfo = this.CreatePropertySegment(previous, projectedProperty); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return segmentInfo; } // If the property resolution failed, and the previous segment was targeting an entity, then we should // try and resolve the identifier as type name. SegmentInfo typeNameSegment; if (this.TryCreateTypeNameSegment(previous, segment, out typeNameSegment)) { Debug.Assert(typeNameSegment.TargetSource == previous.TargetSource, "segment.TargetSource == previous.TargetSource"); return typeNameSegment; } OperationWrapper serviceAction = null; var actionSegment = segment as OperationSegment; if (actionSegment != null) { Debug.Assert(actionSegment.Operations.Count() == 1, "Operation segment should only ever have exactly one operation. Was a change made to how MetadataProviderEdmModel finds actions/service operations"); serviceAction = ((MetadataProviderEdmOperation)actionSegment.Operations.Single()).ServiceOperation; Debug.Assert(serviceAction != null, "serviceAction != null"); Debug.Assert(serviceAction.Kind == OperationKind.Action, "serviceAction.Kind == OperationKind.Action"); } if (serviceAction != null) { Debug.Assert(serviceAction.Kind == OperationKind.Action, "serviceAction.Kind == OperationKind.Action"); // Service Actions can bind to a set with any ResourceSetRights except ResourceSetRights.None. segmentInfo = this.CreateSegmentForServiceAction(previous, serviceAction); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.ServiceOperation, "segment.TargetSource == RequestTargetSource.ServiceOperation"); return segmentInfo; } CheckSingleResult(previous.SingleResult, previous.Identifier); segmentInfo = CreateOpenPropertySegment(previous, ((OpenPropertySegment)segment).PropertyName); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return segmentInfo; }
/// <summary>Creates the first <see cref="SegmentInfo"/> for a request.</summary> /// <param name="segment">The text of the segment.</param> /// <returns>A description of the information on the segment.</returns> private SegmentInfo CreateFirstSegment(ODataPathSegment segment) { Debug.Assert(segment != null, "identifier != null"); // Look for well-known system entry points. if (segment is MetadataSegment) { return new SegmentInfo { Identifier = XmlConstants.UriMetadataSegment, TargetKind = RequestTargetKind.Metadata }; } if (segment is BatchSegment) { return new SegmentInfo { Identifier = XmlConstants.UriBatchSegment, TargetKind = RequestTargetKind.Batch }; } if (segment is CountSegment) { // $count on root: throw throw DataServiceException.CreateResourceNotFound(Strings.RequestUriProcessor_CountOnRoot); } // Look for a service operation. OperationImportSegment serviceOperation = segment as OperationImportSegment; if (serviceOperation != null) { Debug.Assert(serviceOperation.OperationImports.Count() == 1, "Operation import segment should only ever have exactly one operation. Was a change made to how MetadataProviderEdmModel finds actions/service operations"); var operationImport = serviceOperation.OperationImports.Single(); var operation = ((MetadataProviderEdmOperationImport)operationImport).ServiceOperation; Debug.Assert(operation != null, "operation != null"); if (operation.Kind == OperationKind.ServiceOperation) { return CreateSegmentForServiceOperation(operation); } Debug.Assert(operation.Kind == OperationKind.Action, "serviceAction.Kind == OperationKind.Action"); return this.CreateSegmentForServiceAction(null /*previousSegment*/, operation); } var batchReferenceSegment = segment as BatchReferenceSegment; if (batchReferenceSegment != null) { SegmentInfo referencedSegmentInfo = this.crossReferenceCallback(batchReferenceSegment.ContentId); Debug.Assert(referencedSegmentInfo != null, "Could not find SegmentInfo for content-id: " + batchReferenceSegment.ContentId); referencedSegmentInfo.Identifier = batchReferenceSegment.ContentId; return referencedSegmentInfo; } // Look for an entity set. EntitySetSegment entitySetSegment = segment as EntitySetSegment; if (entitySetSegment != null) { var container = ((IResourceSetBasedEdmEntitySet)entitySetSegment.EntitySet).ResourceSet; Debug.Assert(container != null, "container != null"); SegmentInfo segmentInfo = new SegmentInfo { Identifier = container.Name, TargetResourceSet = container, TargetResourceType = container.ResourceType, TargetSource = RequestTargetSource.EntitySet, TargetKind = RequestTargetKind.Resource, SingleResult = false }; return segmentInfo; } WebUtil.CheckResourceExists(false, segment.ToString()); return null; }
/// <summary> /// Creates a new segment for an open property. /// </summary> /// <param name="previous">previous segment info.</param> /// <param name="identifier">name of the segment.</param> /// <returns>new open property segment.</returns> private static SegmentInfo CreateOpenPropertySegment(SegmentInfo previous, string identifier) { SegmentInfo segment = new SegmentInfo { Identifier = identifier }; // Handle an open type property. If the current leaf isn't an // object (which implies it's already an open type), then // it should be marked as an open type. if (previous.TargetResourceType != null) { WebUtil.CheckResourceExists(previous.TargetResourceType.IsOpenType, segment.Identifier); } // Open navigation properties are not supported on OpenTypes. We should throw on the following cases: // 1. the segment before $ref is always a navigation property pointing to a resource // 2. if the segment hasQuery, it is pointing to a resource // 3. if this is a POST operation, the target has to be either a set of links or an entity set if (previous.TargetKind == RequestTargetKind.Link) { throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(segment.Identifier)); } segment.TargetSource = RequestTargetSource.Property; segment.TargetResourceType = null; segment.TargetKind = RequestTargetKind.OpenProperty; segment.SingleResult = true; return segment; }
/// <summary> /// Handle $ref segment /// </summary> /// <param name="previous">previous segment info.</param> /// <returns>The links segment info</returns> private static SegmentInfo CreateEntityReferenceLinkSegment(SegmentInfo previous) { Debug.Assert(previous.TargetKind == RequestTargetKind.Resource, "Can we ever get here?"); WebUtil.CheckSyntaxValid(previous.TargetKind == RequestTargetKind.Resource); CheckSingleResult(previous.SingleResult, previous.Identifier); return new SegmentInfo(previous) { Identifier = XmlConstants.UriLinkSegment, TargetKind = RequestTargetKind.Link, }; }
/// <summary> /// Creates the next segment. /// </summary> /// <param name="previous">The previous segment.</param> /// <param name="segment">The the next segment.</param> /// <returns>The newly created next segment.</returns> private SegmentInfo CreateNextSegment(SegmentInfo previous, ODataPathSegment segment) { if (segment is ValueSegment) { return(CreateValueSegment(previous)); } SegmentInfo segmentInfo; if (segment is CountSegment) { segmentInfo = CreateCountSegment(previous); // DEVNOTE: Prior to refactor, $count was handled alongside properties. See more detailed comment in HandleCountSegment Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return(segmentInfo); } // if its not one of the recognized special segments, then it must be a property, type-segment, or key value. Debug.Assert( previous.TargetKind == RequestTargetKind.ComplexObject || previous.TargetKind == RequestTargetKind.Resource || previous.TargetKind == RequestTargetKind.OpenProperty || previous.TargetKind == RequestTargetKind.Link, "previous.TargetKind(" + previous.TargetKind + ") can have properties"); CheckSegmentIsComposable(previous); // if the segment corresponds to a declared property, handle it // otherwise, fall back to type-segments, actions, and dynamic/open properties ResourceProperty projectedProperty; if (TryGetPropertyFromSegment(segment, out projectedProperty)) { CheckSingleResult(previous.SingleResult, previous.Identifier); if (projectedProperty.IsOfKind(ResourcePropertyKind.Stream)) { segmentInfo = CreateNamedStreamSegment(previous, projectedProperty); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return(segmentInfo); } segmentInfo = this.CreatePropertySegment(previous, projectedProperty); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return(segmentInfo); } // If the property resolution failed, and the previous segment was targeting an entity, then we should // try and resolve the identifier as type name. SegmentInfo typeNameSegment; if (this.TryCreateTypeNameSegment(previous, segment, out typeNameSegment)) { Debug.Assert(typeNameSegment.TargetSource == previous.TargetSource, "segment.TargetSource == previous.TargetSource"); return(typeNameSegment); } OperationWrapper serviceAction = null; var actionSegment = segment as OperationSegment; if (actionSegment != null) { Debug.Assert(actionSegment.Operations.Count() == 1, "Operation segment should only ever have exactly one operation. Was a change made to how MetadataProviderEdmModel finds actions/service operations"); serviceAction = ((MetadataProviderEdmOperation)actionSegment.Operations.Single()).ServiceOperation; Debug.Assert(serviceAction != null, "serviceAction != null"); Debug.Assert(serviceAction.Kind == OperationKind.Action, "serviceAction.Kind == OperationKind.Action"); } if (serviceAction != null) { Debug.Assert(serviceAction.Kind == OperationKind.Action, "serviceAction.Kind == OperationKind.Action"); // Service Actions can bind to a set with any ResourceSetRights except ResourceSetRights.None. segmentInfo = this.CreateSegmentForServiceAction(previous, serviceAction); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.ServiceOperation, "segment.TargetSource == RequestTargetSource.ServiceOperation"); return(segmentInfo); } CheckSingleResult(previous.SingleResult, previous.Identifier); segmentInfo = CreateOpenPropertySegment(previous, ((OpenPropertySegment)segment).PropertyName); Debug.Assert(segmentInfo.TargetSource == RequestTargetSource.Property, "segment.TargetSource == RequestTargetSource.Property"); return(segmentInfo); }
/// <summary>Creates the first <see cref="SegmentInfo"/> for a request.</summary> /// <param name="segment">The text of the segment.</param> /// <returns>A description of the information on the segment.</returns> private SegmentInfo CreateFirstSegment(ODataPathSegment segment) { Debug.Assert(segment != null, "identifier != null"); // Look for well-known system entry points. if (segment is MetadataSegment) { return(new SegmentInfo { Identifier = XmlConstants.UriMetadataSegment, TargetKind = RequestTargetKind.Metadata }); } if (segment is BatchSegment) { return(new SegmentInfo { Identifier = XmlConstants.UriBatchSegment, TargetKind = RequestTargetKind.Batch }); } if (segment is CountSegment) { // $count on root: throw throw DataServiceException.CreateResourceNotFound(Strings.RequestUriProcessor_CountOnRoot); } // Look for a service operation. OperationImportSegment serviceOperation = segment as OperationImportSegment; if (serviceOperation != null) { Debug.Assert(serviceOperation.OperationImports.Count() == 1, "Operation import segment should only ever have exactly one operation. Was a change made to how MetadataProviderEdmModel finds actions/service operations"); var operationImport = serviceOperation.OperationImports.Single(); var operation = ((MetadataProviderEdmOperationImport)operationImport).ServiceOperation; Debug.Assert(operation != null, "operation != null"); if (operation.Kind == OperationKind.ServiceOperation) { return(CreateSegmentForServiceOperation(operation)); } Debug.Assert(operation.Kind == OperationKind.Action, "serviceAction.Kind == OperationKind.Action"); return(this.CreateSegmentForServiceAction(null /*previousSegment*/, operation)); } var batchReferenceSegment = segment as BatchReferenceSegment; if (batchReferenceSegment != null) { SegmentInfo referencedSegmentInfo = this.crossReferenceCallback(batchReferenceSegment.ContentId); Debug.Assert(referencedSegmentInfo != null, "Could not find SegmentInfo for content-id: " + batchReferenceSegment.ContentId); referencedSegmentInfo.Identifier = batchReferenceSegment.ContentId; return(referencedSegmentInfo); } // Look for an entity set. EntitySetSegment entitySetSegment = segment as EntitySetSegment; if (entitySetSegment != null) { var container = ((IResourceSetBasedEdmEntitySet)entitySetSegment.EntitySet).ResourceSet; Debug.Assert(container != null, "container != null"); SegmentInfo segmentInfo = new SegmentInfo { Identifier = container.Name, TargetResourceSet = container, TargetResourceType = container.ResourceType, TargetSource = RequestTargetSource.EntitySet, TargetKind = RequestTargetKind.Resource, SingleResult = false }; return(segmentInfo); } WebUtil.CheckResourceExists(false, segment.ToString()); return(null); }
/// <summary> /// Validates the binding parameter and reads the payload parameters for the given action. /// </summary> /// <param name="dataService">Data service instance.</param> /// <param name="actionSegment">The segment for the action whose parameters is being read.</param> /// <param name="previousSegment">The segment before the action.</param> /// <returns>A new Expression[] with parameter values.</returns> private static Expression[] ValidateBindingParameterAndReadPayloadParametersForAction(IDataService dataService, SegmentInfo actionSegment, SegmentInfo previousSegment) { Debug.Assert(dataService != null, "dataService != null"); Debug.Assert(actionSegment != null, "actionSegment != null"); OperationWrapper operation = actionSegment.Operation; Debug.Assert(operation != null, "operation != null"); Debug.Assert(operation.Kind == OperationKind.Action, "operation.Kind == OperationKind.Action"); int idx = 0; int parametersCount = operation.Parameters.Count; Expression[] operationParameters = new Expression[parametersCount]; if (previousSegment != null) { if (operation.BindingParameter == null) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_UnbindableOperationsMustBeCalledAtRootLevel(operation.Name)); } ResourceType previousSegmentType = previousSegment.TargetResourceType; // We throw in HandleOpenPropertySegment() when we see an open property on a POST operation. previousSegmentType should not be null here. Debug.Assert(previousSegmentType != null, "previousSegmentType != null"); ResourceType bindingParameterType = operation.BindingParameter.ParameterType; Debug.Assert(bindingParameterType != null, "bindingParameterType != null"); Debug.Assert( bindingParameterType.ResourceTypeKind == ResourceTypeKind.EntityType || bindingParameterType.ResourceTypeKind == ResourceTypeKind.EntityCollection, "The ServiceAction constructor requires the binding parameter type to be entity or entity collection."); // We need to make sure the item type of the binding parameter is assignable from the item type of the previous segment and // we need to make sure the cardinality of the binding parameter maches that of the previous segment. bool bindingParameterIsCollection; ResourceType bindingParameterItemType = RequestUriProcessor.GetItemTypeFromResourceType(bindingParameterType, out bindingParameterIsCollection); Debug.Assert( !bindingParameterIsCollection || bindingParameterType.ResourceTypeKind == ResourceTypeKind.EntityCollection || bindingParameterType.ResourceTypeKind == ResourceTypeKind.Collection, "!bindingParameterIsCollection || bindingParameterType.ResourceTypeKind == ResourceTypeKind.EntityCollection || bindingParameterType.ResourceTypeKind == ResourceTypeKind.Collection"); bool previousSegmentIsCollection; ResourceType previousSegmentItemType = RequestUriProcessor.GetItemTypeFromResourceType(previousSegmentType, out previousSegmentIsCollection); Debug.Assert( !previousSegmentIsCollection || previousSegmentType.ResourceTypeKind == ResourceTypeKind.EntityCollection || previousSegmentType.ResourceTypeKind == ResourceTypeKind.Collection, "!previousSegmentIsCollection || previousSegmentType.ResourceTypeKind == ResourceTypeKind.EntityCollection || previousSegmentType.ResourceTypeKind == ResourceTypeKind.Collection"); // If previousSegment is Collection, previousSegment.SingleResult is false but previousSegmentIsCollection is true because previousSegmentType is of Collection type. // If previousSegment is a ResourceSetReference, its TargetResourceType is the item type of the set rather than the entity collection type so previousSegmentIsCollection // is false. previousSegment.SingleResult is true and gives us the cardinality in that case. previousSegmentIsCollection = previousSegmentIsCollection || !previousSegment.SingleResult; if (bindingParameterIsCollection != previousSegmentIsCollection || !bindingParameterItemType.IsAssignableFrom(previousSegmentItemType)) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_BindingParameterNotAssignableFromPreviousSegment(operation.Name, previousSegment.Identifier)); } operationParameters[idx++] = previousSegment.RequestExpression; } else { // No previous segment - throw if the operation has a binding parameter if (operation.BindingParameter != null) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_MissingBindingParameter(operation.Name)); } } // Try to read parameters from the payload if: // 1) We are expecting parameters in the payload, i.e. we have more than 1 non-binding parameters. // 2) We don't expect parameters in the payload but content type is not null. In this case if the payload is empty, ODataParameterReader // will return no more parameters. Otherwise ODataParameterReader will throw when there is any unexpected parameter on the payload. if (idx < parametersCount || !string.IsNullOrEmpty(dataService.OperationContext.RequestMessage.ContentType)) { Dictionary<string, object> parameters = Deserializer.ReadPayloadParameters(actionSegment, dataService); for (; idx < parametersCount; idx++) { string parameterName = operation.Parameters[idx].Name; object parameterValue; if (!parameters.TryGetValue(parameterName, out parameterValue)) { Debug.Fail("ODataParameterReader should throw when there is any missing parameter."); } operationParameters[idx] = Expression.Constant(parameterValue, typeof(object)); } } return operationParameters; }
/// <summary> /// Composes the query expression for properties /// </summary> /// <param name="segment">Segment to compose the expression for.</param> /// <param name="previous">The previous segment.</param> /// <param name="service">The data service instance.</param> /// <param name="lastSegment">true if <paramref name="segment"/> is also the last segment; false otherwise.</param> /// <param name="checkRights">true if we need to check rights for this segment; false otherwise.</param> private static void ComposeExpressionForProperty(SegmentInfo segment, SegmentInfo previous, IDataService service, bool lastSegment, bool checkRights) { Debug.Assert(segment != null && segment.ProjectedProperty != null, "segment != null && segment.ProjectedProperty != null"); if (segment.ProjectedProperty.CanReflectOnInstanceTypeProperty) { segment.RequestExpression = segment.ProjectedProperty.Kind == ResourcePropertyKind.ResourceSetReference ? SelectMultiple(previous.RequestExpression, segment.ProjectedProperty) : SelectElement(previous.RequestExpression, segment.ProjectedProperty); } else { segment.RequestExpression = segment.ProjectedProperty.Kind == ResourcePropertyKind.ResourceSetReference ? SelectLateBoundPropertyMultiple( previous.RequestExpression, segment.ProjectedProperty) : SelectLateBoundProperty( previous.RequestExpression, segment.ProjectedProperty); } if (segment.HasKeyValues) { ApplyKeyToExpression(segment); } // Do security checks and authorization query composition. if (segment.TargetResourceSet != null) { if (checkRights) { DataServiceConfiguration.CheckResourceRightsForRead(segment.TargetResourceSet, segment.SingleResult); } if (ShouldRequestQuery(service, lastSegment, previous.TargetKind == RequestTargetKind.Link, segment.HasKeyValues)) { segment.RequestExpression = DataServiceConfiguration.ComposeResourceContainer(service, segment.TargetResourceSet, segment.RequestExpression); } } }
/// <summary> /// Compose the query expression for a type name segment. /// </summary> /// <param name="segment">The type name segment.</param> /// <param name="previous">The previous segment.</param> private static void ComposeExpressionForTypeNameSegment(SegmentInfo segment, SegmentInfo previous) { Debug.Assert(segment != null && segment.IsTypeIdentifierSegment, "segment != null && segment.IsTypeIdentifierSegment"); Debug.Assert(previous != null, "previous != null"); segment.RequestExpression = SelectDerivedResourceType(previous.RequestExpression, segment.TargetResourceType); if (segment.HasKeyValues) { ApplyKeyToExpression(segment); } }
/// <summary> /// Invokes the service action for the segment. /// </summary> /// <param name="segment">Segment info for the service action.</param> /// <param name="previousSegment">Previous segment.</param> /// <param name="service">Data service instance.</param> private static void ComposeExpressionForServiceAction(SegmentInfo segment, SegmentInfo previousSegment, IDataService service) { Debug.Assert( segment != null && segment.Operation != null && segment.Operation.Kind == OperationKind.Action, "segment != null && segment.Operation != null && segment.Operation.Kind == OperationKind.Action"); Expression[] parameterTokens = ValidateBindingParameterAndReadPayloadParametersForAction(service, segment, previousSegment); segment.RequestExpression = service.ActionProvider.CreateInvokable(segment.Operation, parameterTokens); Debug.Assert(segment.RequestExpression != null, "segment.RequestExpression != null"); }
/// <summary> /// Invokes the service operation for the segment. /// </summary> /// <param name="segment">The segment</param> /// <param name="service">The service instance</param> /// <param name="checkRights">true if we need to check rights for the operation; false otherwise.</param> /// <param name="lastSegment">the last segment of the request.</param> private static void ComposeExpressionForServiceOperation(SegmentInfo segment, IDataService service, bool checkRights, SegmentInfo lastSegment) { Debug.Assert( segment != null && segment.Operation != null && segment.Operation.Kind == OperationKind.ServiceOperation && segment.TargetSource == RequestTargetSource.ServiceOperation, "The segment must be a service operation segment."); Debug.Assert(service != null, "service != null"); bool lastSegmentIsServiceAction = false; if (checkRights) { segment.CheckSegmentRights(); } if (segment != lastSegment) { lastSegmentIsServiceAction = lastSegment.IsServiceActionSegment; if (segment.Operation.Method == XmlConstants.HttpMethodPost && lastSegmentIsServiceAction) { // An action cannot be composed from a WebInvoke service operation. throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_ActionComposedWithWebInvokeServiceOperationNotAllowed); } } if (!lastSegmentIsServiceAction && service.OperationContext.RequestMethod != segment.Operation.Method) { throw DataServiceException.CreateMethodNotAllowed(Strings.RequestUriProcessor_MethodNotAllowed, segment.Operation.Method); } object[] operationParameters = ReadOperationParameters(service.OperationContext.RequestMessage, segment.Operation); ConstantExpression methodResult = null; switch (segment.Operation.ResultKind) { case ServiceOperationResultKind.QueryWithMultipleResults: case ServiceOperationResultKind.QueryWithSingleResult: methodResult = service.Provider.InvokeServiceOperation(segment.Operation, operationParameters); WebUtil.CheckResourceExists(methodResult.Value != null, segment.Identifier); break; case ServiceOperationResultKind.DirectValue: case ServiceOperationResultKind.Enumeration: methodResult = service.Provider.InvokeServiceOperation(segment.Operation, operationParameters); Debug.Assert(methodResult != null, "methodResult != null"); WebUtil.CheckResourceExists(segment.SingleResult || methodResult.Value != null, segment.Identifier); // Enumerations shouldn't be null. break; default: Debug.Assert(segment.Operation.ResultKind == ServiceOperationResultKind.Void, "segment.Operation.ResultKind == ServiceOperationResultKind.Nothing"); service.Provider.InvokeServiceOperation(segment.Operation, operationParameters); break; } segment.RequestExpression = methodResult; if (segment.RequestExpression != null) { if (segment.HasKeyValues) { // key predicates should be applied after operation is invoked ApplyKeyToExpression(segment); } } }
/// <summary> /// Determines if the previous segment can be composed upon. /// </summary> /// <param name="previous">previous segment info.</param> /// <returns> /// <c>true</c> if the segment can be composed upon; otherwise, <c>false</c>. /// </returns> private static bool IsSegmentComposable(SegmentInfo previous) { OperationWrapper op = previous.Operation; return op == null || (op.ResultKind != ServiceOperationResultKind.Enumeration && op.ResultKind != ServiceOperationResultKind.DirectValue && (op.ResultKind != ServiceOperationResultKind.QueryWithSingleResult || op.ResultType.ResourceTypeKind == ResourceTypeKind.EntityType)); }
/// <summary> /// Handle $count segment /// </summary> /// <param name="previous">previous segment info.</param> /// <returns>The count segment info</returns> private static SegmentInfo CreateCountSegment(SegmentInfo previous) { CheckSegmentIsComposable(previous); if ((previous.TargetKind != RequestTargetKind.Resource || previous.SingleResult) && previous.TargetKind != RequestTargetKind.Collection) { throw DataServiceException.CreateResourceNotFound(Strings.RequestUriProcessor_CountNotSupported(previous.Identifier)); } var segment = new SegmentInfo(); segment.Identifier = XmlConstants.UriCountSegment; // DEVNOTE: prior to refactoring, $count was handled alongside properties // TODO: introduce a new target-source value or otherwise refactor this so that // $count is not treated like a property. Using the previous segment's kind // becomes problematic for service operations because the current segment // will not know the specific operation. segment.TargetSource = RequestTargetSource.Property; segment.SingleResult = true; segment.TargetKind = RequestTargetKind.PrimitiveValue; segment.TargetResourceType = previous.TargetResourceType; segment.TargetResourceSet = previous.TargetResourceSet; return segment; }
/// <summary> /// Tries to create a type name segment if the given identifier refers to a known type. /// </summary> /// <param name="previous">previous segment info.</param> /// <param name="segment">The segment being interpreted.</param> /// <param name="typeNameSegment">The type name segment, if one was created.</param> /// <returns>Whether or not a type segment was created for the identifier.</returns> private bool TryCreateTypeNameSegment(SegmentInfo previous, ODataPathSegment segment, out SegmentInfo typeNameSegment) { var typeSegment = segment as TypeSegment; if (typeSegment == null || previous.TargetResourceSet == null) { typeNameSegment = null; return(false); } ResourceType targetResourceType = MetadataProviderUtils.GetResourceType(typeSegment); // if the new type segment prevents any results from possibly being returned, then short-circuit and throw a 404. ResourceType previousResourceType = previous.TargetResourceType; Debug.Assert(previousResourceType != null, "previous.TargetResourceType != null"); if (!targetResourceType.IsAssignableFrom(previousResourceType) && !previousResourceType.IsAssignableFrom(targetResourceType)) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType(targetResourceType.FullName, previousResourceType.FullName)); } // Since we allow derived navigation properties or named streams in V1/V2, the server will generate edit links and navigation links with type segment in it. // Hence we need to be able to process type segment in the request even when the server MPV is set to V1/V2. But we do not want to expose new functionality // like filtering collections based on type, etc on V1/V2 servers. Hence only checking for MPV to be v3 or greater if the previous segment is a collection if (!previous.SingleResult) { VersionUtil.CheckMaxProtocolVersion(VersionUtil.Version4Dot0, this.maxProtocolVersion); } typeNameSegment = new SegmentInfo { Identifier = targetResourceType.FullName, Operation = previous.Operation, TargetKind = previous.TargetKind, TargetSource = previous.TargetSource, TargetResourceType = targetResourceType, SingleResult = previous.SingleResult, TargetResourceSet = previous.TargetResourceSet, ProjectedProperty = previous.ProjectedProperty, Key = previous.Key, RequestExpression = previous.RequestExpression, RequestEnumerable = previous.RequestEnumerable, IsTypeIdentifierSegment = true }; return(true); }
/// <summary> /// Create a $value segment /// </summary> /// <param name="previous">previous segment info.</param> /// <returns>new segement info for $value.</returns> private static SegmentInfo CreateValueSegment(SegmentInfo previous) { SegmentInfo segment; if (previous.TargetKind == RequestTargetKind.Primitive) { segment = new SegmentInfo(previous); } else { CheckSegmentIsComposable(previous); segment = new SegmentInfo { TargetSource = RequestTargetSource.Property, TargetResourceType = previous.TargetResourceType }; } segment.Identifier = XmlConstants.UriValueSegment; CheckSingleResult(previous.SingleResult, previous.Identifier); segment.SingleResult = true; if (previous.TargetKind == RequestTargetKind.Primitive) { segment.TargetKind = RequestTargetKind.PrimitiveValue; } else if (previous.TargetKind == RequestTargetKind.OpenProperty) { segment.TargetKind = RequestTargetKind.OpenPropertyValue; } else { // If the previous segment is an entity, we expect it to be an MLE. We cannot validate our assumption // until later when we get the actual instance of the entity because the type hierarchy can contain // a mix of MLE and non-MLE types. segment.TargetKind = RequestTargetKind.MediaResource; } return segment; }
/// <summary>Creates an <see cref="SegmentInfo"/> list for the given <paramref name="path"/>.</summary> /// <param name="path">Segments to process.</param> /// <returns>Segment information describing the given <paramref name="path"/>.</returns> internal IList <SegmentInfo> ConvertPath(ODataPath path) { Debug.Assert(path != null, "path != null"); SegmentInfo previous = null; List <SegmentInfo> segmentInfos = new List <SegmentInfo>(); bool crossReferencingUri = false; foreach (ODataPathSegment segment in path) { crossReferencingUri |= segment is BatchReferenceSegment; SegmentInfo segmentInfo; if (previous == null) { segmentInfo = this.CreateFirstSegment(segment); } else { ThrowIfMustBeLeafSegment(previous); var keySegment = segment as KeySegment; if (keySegment != null) { // All service operations other than those returning IQueryable MUST NOT have a key expression. if (!IsSegmentComposable(previous)) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(previous.Identifier)); } CheckSegmentIsComposable(previous); if (crossReferencingUri) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation); } previous.SingleResult = true; previous.Key = keySegment; continue; } if (segment is NavigationPropertyLinkSegment) { segmentInfo = CreateEntityReferenceLinkSegment(previous); #if DEBUG segmentInfo.AssertValid(); #endif segmentInfos.Add(segmentInfo); previous = segmentInfo; } segmentInfo = this.CreateNextSegment(previous, segment); } Debug.Assert(segmentInfo != null, "segment != null"); #if DEBUG segmentInfo.AssertValid(); #endif segmentInfos.Add(segmentInfo); // we need to copy the segment over (even if it was an escape marker) as decisions will be made about it the next time through the loop. previous = segmentInfo; } return(segmentInfos); }
/// <summary> /// Creates a named stream segment /// </summary> /// <param name="previous">previous segment info.</param> /// <param name="streamProperty">stream property to create the segment for.</param> /// <returns>new named stream segment.</returns> private static SegmentInfo CreateNamedStreamSegment(SegmentInfo previous, ResourceProperty streamProperty) { Debug.Assert(streamProperty.IsOfKind(ResourcePropertyKind.Stream), "streamProperty.IsOfKind(ResourcePropertyKind.Stream)"); // Handle Named Stream. SegmentInfo segment = new SegmentInfo() { Identifier = streamProperty.Name }; segment.TargetKind = RequestTargetKind.MediaResource; segment.SingleResult = true; segment.TargetResourceType = previous.TargetResourceType; segment.TargetSource = RequestTargetSource.Property; Debug.Assert(segment.Identifier != XmlConstants.UriValueSegment, "'$value' cannot be the name of a named stream."); return segment; }
public void RawValueWithMimeTypeShouldUseFormat() { this.host.AbsoluteServiceUri = new Uri("http://myservice.org/"); this.host.AbsoluteRequestUri = new Uri("http://myservice.org/FakeSet"); SegmentInfo segment = new SegmentInfo { TargetKind = RequestTargetKind.PrimitiveValue, TargetSource = RequestTargetSource.Property, ProjectedProperty = new ResourceProperty("Fake", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(string))) { MimeType = "fake/things" } }; var requestDescription = new RequestDescription(new[] { segment }, new Uri("http://temp.org")); var testSubject = this.ForNormalRequest(requestDescription); testSubject.WriterSettings.UseFormat.Should().BeTrue(); testSubject.WriterSettings.Format.Should().BeSameAs(ODataFormat.RawValue); }
/// <summary> /// Creates a segment for the given service action. /// </summary> /// <param name="previousSegment">The previous segment before the operation to be invoked.</param> /// <param name="serviceAction">The service action to create the segment for.</param> /// <returns>A fully populated PathSegment representing the service action</returns> private SegmentInfo CreateSegmentForServiceAction(SegmentInfo previousSegment, OperationWrapper serviceAction) { Debug.Assert(serviceAction != null && serviceAction.Kind == OperationKind.Action, "serviceAction != null && serviceAction.Kind == OperationKind.Action"); SegmentInfo segment = new SegmentInfo() { Identifier = serviceAction.Name, Operation = serviceAction }; WebUtil.DebugEnumIsDefined(serviceAction.ResultKind); Debug.Assert(segment.IsServiceActionSegment, "IsServiceActionSegment(segment)"); if (previousSegment != null && previousSegment.TargetKind == RequestTargetKind.Link) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment(serviceAction.Name, XmlConstants.UriLinkSegment)); } segment.TargetSource = RequestTargetSource.ServiceOperation; if (serviceAction.ResultKind != ServiceOperationResultKind.Void) { segment.TargetResourceSet = serviceAction.GetResultSet(this.providerWrapper, previousSegment == null ? null : previousSegment.TargetResourceSet); segment.TargetResourceType = serviceAction.ReturnType; segment.TargetKind = TargetKindFromType(segment.TargetResourceType); if (segment.TargetKind == RequestTargetKind.Resource && segment.TargetResourceSet == null) { // Service actions are either visible (ServiceActionRights.Invoke) or not (ServiceActionRight.None). The fact that // DataServiceActionProviderWrapper.TryResolveServiceAction() returns a non-null value means the action is visible. // If the result of the action is of entity type, we need to make sure the target set is visible or else the self // and edit links of the entities in the response payload would not be usable. Debug.Assert(serviceAction.IsVisible, "serviceAction.IsVisible"); throw DataServiceException.CreateForbidden(); } segment.SingleResult = serviceAction.ResultKind == ServiceOperationResultKind.DirectValue; Debug.Assert(serviceAction.ResultKind != ServiceOperationResultKind.QueryWithSingleResult, "QueryWithSingleResult is not applicable for Actions."); } else { segment.TargetResourceSet = null; segment.TargetResourceType = null; segment.TargetKind = RequestTargetKind.VoidOperation; } return segment; }
/// <summary> /// Creates a segment for a service operation /// </summary> /// <param name="serviceOperation">The service operation for the segment.</param> /// <returns>A fully populated PathSegment representing the service operation</returns> private static SegmentInfo CreateSegmentForServiceOperation(OperationWrapper serviceOperation) { Debug.Assert(serviceOperation != null, "serviceOperation != null"); WebUtil.DebugEnumIsDefined(serviceOperation.ResultKind); SegmentInfo segment = new SegmentInfo { Identifier = serviceOperation.Name, Operation = serviceOperation, TargetSource = RequestTargetSource.ServiceOperation, TargetResourceSet = serviceOperation.ResourceSet }; if (serviceOperation.ResultKind != ServiceOperationResultKind.Void) { segment.TargetResourceType = serviceOperation.ResultType; segment.TargetKind = TargetKindFromType(segment.TargetResourceType); // this service operation returns results // we should check service operation rights // SingleResult operations are service operations defined with [SingleResult] attribute, returns a DirectValue // or a service operation returning multiple results, but contains key predicates in the query portion. // For these operations, you MUST have ReadSingle defined // For multiple-result-operations(IQueryable/IEnumerable), you MUST have ReadMultiple defined. segment.SingleResult = (serviceOperation.ResultKind == ServiceOperationResultKind.QueryWithSingleResult || serviceOperation.ResultKind == ServiceOperationResultKind.DirectValue); } else { segment.TargetResourceType = null; segment.TargetKind = RequestTargetKind.VoidOperation; } return segment; }
/// <summary> /// Tries to create a type name segment if the given identifier refers to a known type. /// </summary> /// <param name="previous">previous segment info.</param> /// <param name="segment">The segment being interpreted.</param> /// <param name="typeNameSegment">The type name segment, if one was created.</param> /// <returns>Whether or not a type segment was created for the identifier.</returns> private bool TryCreateTypeNameSegment(SegmentInfo previous, ODataPathSegment segment, out SegmentInfo typeNameSegment) { var typeSegment = segment as TypeSegment; if (typeSegment == null || previous.TargetResourceSet == null) { typeNameSegment = null; return false; } ResourceType targetResourceType = MetadataProviderUtils.GetResourceType(typeSegment); // if the new type segment prevents any results from possibly being returned, then short-circuit and throw a 404. ResourceType previousResourceType = previous.TargetResourceType; Debug.Assert(previousResourceType != null, "previous.TargetResourceType != null"); if (!targetResourceType.IsAssignableFrom(previousResourceType) && !previousResourceType.IsAssignableFrom(targetResourceType)) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType(targetResourceType.FullName, previousResourceType.FullName)); } // Since we allow derived navigation properties or named streams in V1/V2, the server will generate edit links and navigation links with type segment in it. // Hence we need to be able to process type segment in the request even when the server MPV is set to V1/V2. But we do not want to expose new functionality // like filtering collections based on type, etc on V1/V2 servers. Hence only checking for MPV to be v3 or greater if the previous segment is a collection if (!previous.SingleResult) { VersionUtil.CheckMaxProtocolVersion(VersionUtil.Version4Dot0, this.maxProtocolVersion); } typeNameSegment = new SegmentInfo { Identifier = targetResourceType.FullName, Operation = previous.Operation, TargetKind = previous.TargetKind, TargetSource = previous.TargetSource, TargetResourceType = targetResourceType, SingleResult = previous.SingleResult, TargetResourceSet = previous.TargetResourceSet, ProjectedProperty = previous.ProjectedProperty, Key = previous.Key, RequestExpression = previous.RequestExpression, RequestEnumerable = previous.RequestEnumerable, IsTypeIdentifierSegment = true }; return true; }
/// <summary> /// Throws if the given segment must be a leaf, as a later segment is being created. /// </summary> /// <param name="previous">The previous segment which may need to be a leaf.</param> private static void ThrowIfMustBeLeafSegment(SegmentInfo previous) { if (previous.IsServiceActionSegment) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_MustBeLeafSegment(previous.Identifier)); } if (previous.TargetKind == RequestTargetKind.Batch || previous.TargetKind == RequestTargetKind.Metadata || previous.TargetKind == RequestTargetKind.PrimitiveValue || previous.TargetKind == RequestTargetKind.VoidOperation || previous.TargetKind == RequestTargetKind.OpenPropertyValue || previous.TargetKind == RequestTargetKind.MediaResource || previous.TargetKind == RequestTargetKind.Collection) { // Nothing can come after a $metadata, $value or $batch segment. // Nothing can come after a service operation with void return type. // Nothing can come after a collection property. throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_MustBeLeafSegment(previous.Identifier)); } }
private static void RunPayloadKindTest(RequestTargetKind requestTargetKind, RequestTargetSource requestTargetSource, ResourceType targetResourceType, bool singleResult, bool isLinkUri, ODataPayloadKind expectedKind) { var segment = new SegmentInfo { TargetKind = requestTargetKind, TargetSource = requestTargetSource, TargetResourceType = targetResourceType, SingleResult = singleResult, Identifier = "Fake", }; var operation = new ServiceOperation("Fake", ServiceOperationResultKind.Void, null, null, "GET", null); operation.SetReadOnly(); segment.Operation = new OperationWrapper(operation); segment.ProjectedProperty = new ResourceProperty("Fake", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(int))); SegmentInfo[] segmentInfos; if (isLinkUri) { segmentInfos = new[] { new SegmentInfo(), new SegmentInfo { TargetKind = RequestTargetKind.Link, }, segment }; } else { segmentInfos = new[] { new SegmentInfo { Identifier = "Fake", }, new SegmentInfo(), segment }; } var requestDescription = new RequestDescription(segmentInfos, new Uri("http://temp.org/")); requestDescription.ResponsePayloadKind.Should().Be(expectedKind); }
/// <summary> /// Checks that the previous segment can be composed upon. /// </summary> /// <param name="previous">previous segment info.</param> private static void CheckSegmentIsComposable(SegmentInfo previous) { if (!IsSegmentComposable(previous)) { // Enumerable and DirectValue results cannot be composed at all, and we don't allow we can't access properties in a single queryable complex either throw DataServiceException.ResourceNotFoundError( Strings.RequestUriProcessor_IEnumerableServiceOperationsCannotBeFurtherComposed(previous.Identifier)); } }
/// <summary> /// Initializes a new RequestDescription for a query specified by the /// request Uri. /// </summary> /// <param name="targetKind">The kind of target for the request.</param> /// <param name="targetSource">The source for this target.</param> /// <param name="resultUri">URI to the results requested (with no query component).</param> internal RequestDescription(RequestTargetKind targetKind, RequestTargetSource targetSource, Uri resultUri) { WebUtil.DebugEnumIsDefined(targetKind); Debug.Assert(resultUri != null, "resultUri != null"); Debug.Assert(resultUri.IsAbsoluteUri, "resultUri.IsAbsoluteUri(" + resultUri + ")"); SegmentInfo segment = new SegmentInfo { TargetKind = targetKind, TargetSource = targetSource, SingleResult = true }; this.segmentInfos = new[] { segment }; this.resultUri = resultUri; #if DEBUG this.maxFeatureVersion = VersionUtil.Version4Dot0; #endif this.ResponseVersion = VersionUtil.DataServiceDefaultResponseVersion; this.ActualResponseVersion = this.ResponseVersion; this.Preference = ClientPreference.None; }
/// <summary> /// Apply the key predicates extracted from the segment's query portion /// </summary> /// <param name="segment">The segment on which the query is extracted</param> private static void ApplyKeyToExpression(SegmentInfo segment) { Debug.Assert(segment != null, "segment!= null"); Debug.Assert(segment.SingleResult, "Segment should have single result set"); Debug.Assert(segment.HasKeyValues, "Key is not empty"); segment.RequestExpression = SelectResourceByKey(segment.RequestExpression, segment.TargetResourceType, segment.Key); }