private void AddNode(ProjectionNode node) { this.nodes.Add(node); if (node is ExpandedProjectionNode) { this.hasExpandedPropertyOnDerivedType = this.hasExpandedPropertyOnDerivedType || !System.Data.Services.Providers.ResourceType.CompareReferences(this.ResourceType, node.TargetResourceType); } }
private static void VerifyPropertyMismatchAndExpandSelectMismatchScenario(ProjectionNode existingNode, ResourceProperty property, System.Data.Services.Providers.ResourceType targetResourceType, bool expandNode) { if (property != existingNode.Property) { throw DataServiceException.CreateBadRequestError(System.Data.Services.Strings.RequestQueryProcessor_CannotSpecifyOpenPropertyAndDeclaredPropertyAtTheSameTime(existingNode.PropertyName, (property == null) ? targetResourceType.FullName : existingNode.TargetResourceType.FullName, (property == null) ? existingNode.TargetResourceType.FullName : targetResourceType.FullName)); } if (!System.Data.Services.Providers.ResourceType.CompareReferences(targetResourceType, existingNode.TargetResourceType) && expandNode) { throw DataServiceException.CreateBadRequestError(System.Data.Services.Strings.RequestQueryProcessor_SelectAndExpandCannotBeSpecifiedTogether(existingNode.PropertyName)); } }
internal ExpandedProjectionNode AddProjectionNode(string propertyName, ResourceProperty property, System.Data.Services.Providers.ResourceType targetResourceType, bool lastPathSegment) { ProjectionNode existingNode = this.FindNode(propertyName); if ((existingNode == null) || !ApplyPropertyToExistingNode(existingNode, property, targetResourceType)) { if (!lastPathSegment) { throw DataServiceException.CreateBadRequestError(System.Data.Services.Strings.RequestQueryProcessor_ProjectedPropertyWithoutMatchingExpand(base.PropertyName)); } existingNode = new ProjectionNode(propertyName, property, targetResourceType); this.AddNode(existingNode); } return (existingNode as ExpandedProjectionNode); }
internal ExpandedProjectionNode AddProjectionNode(string propertyName, ResourceProperty property, System.Data.Services.Providers.ResourceType targetResourceType, bool lastPathSegment) { ProjectionNode existingNode = this.FindNode(propertyName); if ((existingNode == null) || !ApplyPropertyToExistingNode(existingNode, property, targetResourceType)) { if (!lastPathSegment) { throw DataServiceException.CreateBadRequestError(System.Data.Services.Strings.RequestQueryProcessor_ProjectedPropertyWithoutMatchingExpand(base.PropertyName)); } existingNode = new ProjectionNode(propertyName, property, targetResourceType); this.AddNode(existingNode); } return(existingNode as ExpandedProjectionNode); }
private static bool ApplyPropertyToExistingNode(ProjectionNode existingNode, ResourceProperty property, System.Data.Services.Providers.ResourceType targetResourceType) { if (((property == null) || (existingNode.Property == null)) || (property == existingNode.Property)) { ExpandedProjectionNode node = existingNode as ExpandedProjectionNode; if (targetResourceType.IsAssignableFrom(existingNode.TargetResourceType)) { VerifyPropertyMismatchAndExpandSelectMismatchScenario(existingNode, property, targetResourceType, node != null); existingNode.TargetResourceType = targetResourceType; return(true); } if (existingNode.TargetResourceType.IsAssignableFrom(targetResourceType)) { VerifyPropertyMismatchAndExpandSelectMismatchScenario(existingNode, property, targetResourceType, node != null); return(true); } } return(false); }
private static List <ProjectionNode> SortNodes(List <ProjectionNode> existingNodes, List <System.Data.Services.Providers.ResourceType> resourceTypesInMetadataOrder) { List <ProjectionNode> list = new List <ProjectionNode>(existingNodes.Count); using (List <System.Data.Services.Providers.ResourceType> .Enumerator enumerator = resourceTypesInMetadataOrder.GetEnumerator()) { System.Data.Services.Providers.ResourceType resourceType; while (enumerator.MoveNext()) { resourceType = enumerator.Current; using (IEnumerator <ResourceProperty> enumerator2 = resourceType.Properties.GetEnumerator()) { Func <ProjectionNode, bool> predicate = null; ResourceProperty property; while (enumerator2.MoveNext()) { property = enumerator2.Current; if (predicate == null) { predicate = node => (node.Property == property) && (node.TargetResourceType == resourceType); } ProjectionNode item = existingNodes.FirstOrDefault <ProjectionNode>(predicate); if (item != null) { list.Add(item); existingNodes.Remove(item); } } continue; } } } List <ProjectionNode> collection = (from node in existingNodes where node.Property == null select node).ToList <ProjectionNode>(); collection.Sort((Comparison <ProjectionNode>)((x, y) => string.Compare(x.PropertyName, y.PropertyName, StringComparison.Ordinal))); list.AddRange(collection); return(list); }
/// <summary>Applies expansions and projections to the specified <paramref name="source"/>.</summary> /// <param name="source"><see cref="IQueryable"/> object to expand and apply projections to.</param> /// <param name="projection">The root node of the tree which describes /// the projections and expansions to be applied to the <paramref name="source"/>.</param> /// <returns> /// An <see cref="IQueryable"/> object, with the results including /// the expansions and projections specified in <paramref name="projection"/>. /// </returns> /// <remarks> /// The returned <see cref="IQueryable"/> may implement the <see cref="IExpandedResult"/> interface /// to provide enumerable objects for the expansions; otherwise, the expanded /// information is expected to be found directly in the enumerated objects. If paging is /// requested by providing a non-empty list in <paramref name="projection"/>.OrderingInfo then /// it is expected that the topmost <see cref="IExpandedResult"/> would have a $skiptoken property /// which will be an <see cref="IExpandedResult"/> in itself and each of it's sub-properties will /// be named SkipTokenPropertyXX where XX represents numbers in increasing order starting from 0. Each of /// SkipTokenPropertyXX properties will be used to generated the $skiptoken to support paging. /// If projections are required, the provider may choose to return <see cref="IQueryable"/> /// which returns instances of <see cref="IProjectedResult"/>. In that case property values are determined /// by calling the <see cref="IProjectedResult.GetProjectedPropertyValue"/> method instead of /// accessing properties of the returned object directly. /// If both expansion and projections are required, the provider may choose to return <see cref="IQueryable"/> /// of <see cref="IExpandedResult"/> which in turn returns <see cref="IProjectedResult"/> from its /// <see cref="IExpandedResult.ExpandedElement"/> property. /// </remarks> public abstract IQueryable ApplyProjections( IQueryable source, ProjectionNode projection);
/// <summary>Adds a new child node to this node.</summary> /// <param name="node">The child node to add.</param> internal void AddNode(ProjectionNode node) { Debug.Assert(node != null, "node != null"); Debug.Assert(this.FindNode(node.PropertyName) == null, "Trying to add a duplicate node."); this.nodes.Add(node); }
private static bool ApplyPropertyToExistingNode(ProjectionNode existingNode, ResourceProperty property, System.Data.Services.Providers.ResourceType targetResourceType) { if (((property == null) || (existingNode.Property == null)) || (property == existingNode.Property)) { ExpandedProjectionNode node = existingNode as ExpandedProjectionNode; if (targetResourceType.IsAssignableFrom(existingNode.TargetResourceType)) { VerifyPropertyMismatchAndExpandSelectMismatchScenario(existingNode, property, targetResourceType, node != null); existingNode.TargetResourceType = targetResourceType; return true; } if (existingNode.TargetResourceType.IsAssignableFrom(targetResourceType)) { VerifyPropertyMismatchAndExpandSelectMismatchScenario(existingNode, property, targetResourceType, node != null); return true; } } return false; }
/// <summary>Applies expansions and projections to the specified <paramref name="source"/>.</summary> /// <param name="source"><see cref="IQueryable"/> object to expand and apply projections to.</param> /// <param name="projection">The root node of the tree which describes /// the projections and expansions to be applied to the <paramref name="source"/>.</param> /// <returns> /// An <see cref="IQueryable"/> object, with the results including /// the expansions and projections specified in <paramref name="projection"/>. /// </returns> /// <remarks> /// The returned <see cref="IQueryable"/> may implement the <see cref="IExpandedResult"/> interface /// to provide enumerable objects for the expansions; otherwise, the expanded /// information is expected to be found directly in the enumerated objects. If paging is /// requested by providing a non-empty list in <paramref name="projection"/>.OrderingInfo then /// it is expected that the topmost <see cref="IExpandedResult"/> would have a $skiptoken property /// which will be an <see cref="IExpandedResult"/> in itself and each of it's sub-properties will /// be named SkipTokenPropertyXX where XX represents numbers in increasing order starting from 0. Each of /// SkipTokenPropertyXX properties will be used to generated the $skiptoken to support paging. /// If projections are required, the provider may choose to return <see cref="IQueryable"/> /// which returns instances of <see cref="IProjectedResult"/>. In that case property values are determined /// by calling the <see cref="IProjectedResult.GetProjectedPropertyValue"/> method instead of /// accessing properties of the returned object directly. /// If both expansion and projections are required, the provider may choose to return <see cref="IQueryable"/> /// of <see cref="IExpandedResult"/> which in turn returns <see cref="IProjectedResult"/> from its /// <see cref="IExpandedResult.ExpandedElement"/> property. /// </remarks> public override IQueryable ApplyProjections( IQueryable source, ProjectionNode projection) { Debug.Assert(projection is RootProjectionNode, "We always get the special root node."); RootProjectionNode rootNode = (RootProjectionNode)projection; Debug.Assert(rootNode.OrderingInfo != null, "We always get non-null OrderingInfo"); bool useBasicExpandProvider = ShouldUseBasicExpandProvider(rootNode); // We need the $skiptoken for top level result if it is paged, hence we need to use ApplyExpansions in that case if (useBasicExpandProvider || rootNode.OrderingInfo.IsPaged || rootNode.ProjectionsSpecified) { return new BasicExpandProvider(this.ProviderWrapper, true, true).ApplyProjections(source, projection); } // This pass-through implementation is appropriate for providers that fault-in on demand. return BasicExpandProvider.ApplyOrderSkipTakeOnTopLevelResultBeforeProjections( source, rootNode.OrderingInfo, rootNode.SkipCount, rootNode.TakeCount); }
/// <summary>Removes duplicates from the tree caused by wildcards and sorts the projected properties.</summary> /// <remarks> /// Examples /// $select=Orders, Orders/ID - get rid of the Orders/ID /// $select=Orders, Orders/* - get rid of the Orders/* /// $select=Orders/*, Orders/ID - get rid of the Orders/ID /// $select=Orders/*, Orders/OrderItems&$expand=Orders - get rid of the Orders/OrderItems (it's redundant to *) /// $select=Orders/*, Orders/OrderItems&$expand=Orders/OrderItems - leave as is, the Orders/OrderItems are expanded /// /// The sorting order is the same as the order in which the properties are enumerated on the owning type. /// This is to preserve the same order as if no projections occured. /// </remarks> internal void ApplyWildcardsAndSort() { // If this segment was marked to include entire subtree // simply remove all children which are not expanded // and propagate the information to all expanded children. if (this.projectSubtree) { for (int j = this.nodes.Count - 1; j >= 0; j--) { ExpandedProjectionNode expandedNode = this.nodes[j] as ExpandedProjectionNode; if (expandedNode != null) { expandedNode.projectSubtree = true; expandedNode.ApplyWildcardsAndSort(); } else { this.nodes.RemoveAt(j); } } this.projectAllImmediateProperties = false; return; } for (int j = this.nodes.Count - 1; j >= 0; j--) { ExpandedProjectionNode expandedNode = this.nodes[j] as ExpandedProjectionNode; // If this node was marked to include all immediate properties, // remove all children which are not expanded. // That means they are either simple properties or nav. properties which // are not going to be expanded anyway. if (this.ProjectAllImmediateProperties && expandedNode == null) { this.nodes.RemoveAt(j); } else if (expandedNode != null) { expandedNode.ApplyWildcardsAndSort(); } } if (this.nodes.Count > 0) { // Sort the subsegments such that they have the same order as the properties // on the owning resource type. ResourceType resourceType = this.ResourceType; List <ProjectionNode> existingNodes = this.nodes; this.nodes = new List <ProjectionNode>(existingNodes.Count); foreach (ResourceProperty property in resourceType.Properties) { Debug.Assert( existingNodes.Where(node => node.Property == property).Count() <= 1, "Can't have more than one projection segment for a given property."); ProjectionNode projectionNode = existingNodes.FirstOrDefault( node => node.Property == property); if (projectionNode != null) { this.nodes.Add(projectionNode); } } // And then append any open properties sorted alphabetically // We sort these since we don't want client to be able to influence // the server behavior unless abo----ely necessary. List <ProjectionNode> openPropertyProjectionNodes = existingNodes.Where(node => node.Property == null).ToList(); openPropertyProjectionNodes.Sort(new Comparison <ProjectionNode>((x, y) => { return(String.Compare(x.PropertyName, y.PropertyName, StringComparison.Ordinal)); })); this.nodes.AddRange(openPropertyProjectionNodes); Debug.Assert(this.nodes.Count == existingNodes.Count, "We didn't sort all the properties."); } }