/// <summary> /// Generate a select item <see cref="SelectItem"/> based on a <see cref="SelectTermToken"/>. /// for example: abc/efg($count=true;$filter=....;$top=1) /// </summary> /// <param name="tokenIn">the select term token to visit</param> /// <returns>the select item for this select term token.</returns> private SelectItem GenerateSelectItem(SelectTermToken tokenIn) { ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); ExceptionUtils.CheckArgumentNotNull(tokenIn.PathToProperty, "pathToProperty"); VerifySelectedPath(tokenIn); SelectItem newSelectItem; if (ProcessWildcardTokenPath(tokenIn, out newSelectItem)) { return(newSelectItem); } IList <ODataPathSegment> selectedPath = ProcessSelectTokenPath(tokenIn.PathToProperty); Debug.Assert(selectedPath.Count > 0); // Navigation property should be the last segment in select path. if (VerifySelectedNavigationProperty(selectedPath, tokenIn)) { return(new PathSelectItem(new ODataSelectPath(selectedPath))); } // We should use the "NavigationSource" at this level for the next level binding. IEdmNavigationSource targetNavigationSource = this.NavigationSource; ODataPathSegment lastSegment = selectedPath.Last(); IEdmType targetElementType = lastSegment.TargetEdmType; IEdmCollectionType collection = targetElementType as IEdmCollectionType; if (collection != null) { targetElementType = collection.ElementType.Definition; } IEdmTypeReference elementTypeReference = targetElementType.ToTypeReference(); // $compute ComputeClause compute = BindCompute(tokenIn.ComputeOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference); HashSet <EndPathToken> generatedProperties = GetGeneratedProperties(compute, null); // $filter FilterClause filter = BindFilter(tokenIn.FilterOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties); // $orderby OrderByClause orderBy = BindOrderby(tokenIn.OrderByOptions, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties); // $search SearchClause search = BindSearch(tokenIn.SearchOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference); // $select List <ODataPathSegment> parsedPath = new List <ODataPathSegment>(this.parsedSegments); parsedPath.AddRange(selectedPath); SelectExpandClause selectExpand = BindSelectExpand(null, tokenIn.SelectOption, parsedPath, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties); return(new PathSelectItem(new ODataSelectPath(selectedPath), targetNavigationSource, selectExpand, filter, orderBy, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, search, compute)); }
/// <summary> /// Validates the given tree against the user-specified limits. /// </summary> /// <param name="expandTree">The expand tree to validate.</param> internal void Validate(SelectExpandClause expandTree) { this.currentCount = 0; this.EnsureMaximumCountAndDepthAreNotExceeded(expandTree, /*currentDepth*/ 0); }
/// <summary> /// Create an Expand item using a nav prop, its entity set and a SelectExpandClause /// </summary> /// <param name="pathToNavigationProperty">the path to the navigation property for this expand item, including any type segments</param> /// <param name="navigationSource">the navigation source for this ExpandItem</param> /// <param name="selectExpandOption">This level select and any sub expands for this expand item.</param> /// <exception cref="System.ArgumentNullException">Throws if input pathToNavigationProperty is null.</exception> public ExpandedNavigationSelectItem(ODataExpandPath pathToNavigationProperty, IEdmNavigationSource navigationSource, SelectExpandClause selectExpandOption) : this(pathToNavigationProperty, navigationSource, selectExpandOption, null, null, null, null, null, null, null) { }
/// <summary> /// Constructs a new SelectBinder. /// </summary> /// <param name="model">The model used for binding.</param> /// <param name="edmType">The entity type that the $select is being applied to.</param> /// <param name="maxDepth">the maximum recursive depth.</param> /// <param name="expandClauseToDecorate">The already built expand clause to decorate</param> /// <param name="resolver">Resolver for uri parser.</param> public SelectBinder(IEdmModel model, IEdmStructuredType edmType, int maxDepth, SelectExpandClause expandClauseToDecorate, ODataUriResolver resolver, BindingState state) { ExceptionUtils.CheckArgumentNotNull(model, "tokenIn"); ExceptionUtils.CheckArgumentNotNull(edmType, "entityType"); ExceptionUtils.CheckArgumentNotNull(resolver, "resolver"); this.visitor = new SelectPropertyVisitor(model, edmType, maxDepth, expandClauseToDecorate, resolver, state); }
/// <summary> /// Generate an expand item (and a select item for the implicit nav prop if necessary) based on an ExpandTermToken /// </summary> /// <param name="tokenIn">the expandTerm token to visit</param> /// <returns>the expand item for this expand term token.</returns> private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) { ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); PathSegmentToken currentToken = tokenIn.PathToNavigationProp; IEdmStructuredType currentLevelEntityType = this.EdmType; List <ODataPathSegment> pathSoFar = new List <ODataPathSegment>(); PathSegmentToken firstNonTypeToken = currentToken; if (currentToken.IsNamespaceOrContainerQualified()) { pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentLevelEntityType, out firstNonTypeToken)); } IEdmProperty edmProperty = this.configuration.Resolver.ResolveProperty(currentLevelEntityType, firstNonTypeToken.Identifier); if (edmProperty == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentLevelEntityType.FullTypeName(), currentToken.Identifier)); } IEdmNavigationProperty currentNavProp = edmProperty as IEdmNavigationProperty; IEdmStructuralProperty currentComplexProp = edmProperty as IEdmStructuralProperty; if (currentNavProp == null && currentComplexProp == null) { throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, currentLevelEntityType.FullTypeName())); } if (currentComplexProp != null) { currentNavProp = ParseComplexTypesBeforeNavigation(currentComplexProp, ref firstNonTypeToken, pathSoFar); } // ensure that we're always dealing with proper V4 syntax if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null) { throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); } bool isRef = false; bool isCount = false; if (firstNonTypeToken.NextToken != null) { // lastly... make sure that, since we're on a NavProp, that the next token isn't null. if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.RefSegment) { isRef = true; } else if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.CountSegment) { isCount = true; } else { throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); } } // Add the segments in select and expand to parsed segments List <ODataPathSegment> parsedPath = new List <ODataPathSegment>(this.parsedSegments); parsedPath.AddRange(pathSoFar); IEdmNavigationSource targetNavigationSource = null; if (this.NavigationSource != null) { IEdmPathExpression bindingPath; targetNavigationSource = this.NavigationSource.FindNavigationTarget(currentNavProp, BindingPathHelper.MatchBindingPath, parsedPath, out bindingPath); } NavigationPropertySegment navSegment = new NavigationPropertySegment(currentNavProp, targetNavigationSource); pathSoFar.Add(navSegment); parsedPath.Add(navSegment); // Add the navigation property segment to parsed segments for future usage. ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar); // $apply ApplyClause applyOption = BindApply(tokenIn.ApplyOptions, this.ResourcePathNavigationSource, targetNavigationSource); // $compute ComputeClause computeOption = BindCompute(tokenIn.ComputeOption, this.ResourcePathNavigationSource, targetNavigationSource); var generatedProperties = GetGeneratedProperties(computeOption, applyOption); bool collapsed = applyOption?.Transformations.Any(t => t.Kind == TransformationNodeKind.Aggregate || t.Kind == TransformationNodeKind.GroupBy) ?? false; // $filter FilterClause filterOption = BindFilter(tokenIn.FilterOption, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed); // $orderby OrderByClause orderbyOption = BindOrderby(tokenIn.OrderByOptions, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed); // $search SearchClause searchOption = BindSearch(tokenIn.SearchOption, this.ResourcePathNavigationSource, targetNavigationSource, null); if (isRef) { return(new ExpandedReferenceSelectItem(pathToNavProp, targetNavigationSource, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, computeOption, applyOption)); } if (isCount) { return(new ExpandedCountSelectItem(pathToNavProp, targetNavigationSource, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption)); } // $select & $expand SelectExpandClause subSelectExpand = BindSelectExpand(tokenIn.ExpandOption, tokenIn.SelectOption, parsedPath, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed); // $levels LevelsClause levelsOption = ParseLevels(tokenIn.LevelsOption, currentLevelEntityType, currentNavProp); return(new ExpandedNavigationSelectItem(pathToNavProp, targetNavigationSource, subSelectExpand, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, levelsOption, computeOption, applyOption)); }
/// <summary> /// Build a property visitor to visit the select tree and decorate a SelectExpandClause /// </summary> /// <param name="model">The model used for binding.</param> /// <param name="edmType">The entity type that the $select is being applied to.</param> /// <param name="maxDepth">the maximum recursive depth.</param> /// <param name="expandClauseToDecorate">The already built expand clause to decorate</param> /// <param name="resolver">Resolver for uri parser.</param> /// <param name="state">The binding state of the visitor.</param> public SelectPropertyVisitor(IEdmModel model, IEdmStructuredType edmType, int maxDepth, SelectExpandClause expandClauseToDecorate, ODataUriResolver resolver, BindingState state) { this.model = model; this.edmType = edmType; this.maxDepth = maxDepth; this.expandClauseToDecorate = expandClauseToDecorate; this.resolver = resolver; this.state = state; }
/// <summary> /// Decorate an expand tree using a select token. /// </summary> /// <param name="subExpand">the already built sub expand</param> /// <param name="currentNavProp">the current navigation property</param> /// <param name="select">the select token to use</param> /// <returns>A new SelectExpand clause decorated with the select token.</returns> private SelectExpandClause DecorateExpandWithSelect(SelectExpandClause subExpand, IEdmNavigationProperty currentNavProp, SelectToken select) { SelectBinder selectBinder = new SelectBinder(this.Model, currentNavProp.ToEntityType(), this.Settings.SelectExpandLimit, subExpand, this.configuration.Resolver); return(selectBinder.Bind(select)); }
/// <summary> /// Initializes a new instance of the <see cref="ODataSerializerContext"/> class. /// </summary> /// <param name="resource">The resource whose property is being nested.</param> /// <param name="selectExpandClause">The <see cref="SelectExpandClause"/> for the property being nested.</param> /// <param name="edmProperty">The complex property being nested or the navigation property being expanded. /// If the resource property is the dynamic complex, the resource property is null. /// </param> /// <remarks>This constructor is used to construct the serializer context for writing nested and expanded properties.</remarks> public ODataSerializerContext(ResourceContext resource, SelectExpandClause selectExpandClause, IEdmProperty edmProperty) : this(resource, edmProperty, null, null) { SelectExpandClause = selectExpandClause; }
/// <summary> /// Gets a list of strings representing current selected property name in current level. /// </summary> /// <param name="selectExpandClause">The select expand clause used.</param> /// <returns>String list generated from selected items</returns> internal static List <string> GetCurrentLevelSelectList(this SelectExpandClause selectExpandClause) { return(selectExpandClause.SelectedItems.Select(GetSelectString).Where(i => i != null).ToList()); }
/// <summary> /// Get sub select and expand clause by property name, if the propertyname is in form of TypeCast/Property, the typeSegment would also be returned. /// </summary> /// <param name="clause">The root clause.</param> /// <param name="propertyName">The property name to navigate to.</param> /// <param name="subSelectExpand">The sub select and expand clause under sub property.</param> /// <param name="typeSegment">Type cast segment, if exists.</param> internal static void GetSubSelectExpandClause(this SelectExpandClause clause, string propertyName, out SelectExpandClause subSelectExpand, out TypeSegment typeSegment) { subSelectExpand = null; typeSegment = null; ExpandedNavigationSelectItem selectedItem = clause .SelectedItems .OfType <ExpandedNavigationSelectItem>() .FirstOrDefault( m => m.PathToNavigationProperty.LastSegment != null && m.PathToNavigationProperty.LastSegment.TranslateWith(PathSegmentToStringTranslator.Instance) == propertyName); if (selectedItem != null) { subSelectExpand = selectedItem.SelectAndExpand; typeSegment = selectedItem.PathToNavigationProperty.FirstSegment as TypeSegment; } }