/// <summary> /// Combine together the select clauses of two ExpandTermTokens /// </summary> /// <param name="existingToken">the already existing expand term token</param> /// <param name="newToken">the new expand term token to be added</param> /// <returns>A new select term containing each of the selected entries.</returns> private static SelectToken CombineSelects(ExpandTermToken existingToken, ExpandTermToken newToken) { if (existingToken.SelectOption == null) { return(newToken.SelectOption); } if (newToken.SelectOption == null) { return(existingToken.SelectOption); } List <PathSegmentToken> newSelects = existingToken.SelectOption.Properties.ToList(); newSelects.AddRange(newToken.SelectOption.Properties); return(new SelectToken(newSelects.Distinct(new PathSegmentTokenEqualityComparer()))); }
/// <summary> /// add a new expandTermToken into an exisiting token, adding any additional levels and trees along the way. /// </summary> /// <param name="existingToken">the exisiting (already expanded) token</param> /// <param name="newToken">the new (already expanded) token</param> /// <returns>the combined token, or, if the two are mutually exclusive, the same tokens</returns> public ExpandTermToken CombineTerms(ExpandTermToken existingToken, ExpandTermToken newToken) { Debug.Assert(new PathSegmentTokenEqualityComparer().Equals(existingToken.PathToNavigationProp, newToken.PathToNavigationProp), "Paths should be equal."); List <ExpandTermToken> childNodes = CombineChildNodes(existingToken, newToken).ToList(); SelectToken combinedSelects = CombineSelects(existingToken, newToken); return(new ExpandTermToken( existingToken.PathToNavigationProp, existingToken.FilterOption, existingToken.OrderByOptions, existingToken.TopOption, existingToken.SkipOption, existingToken.CountQueryOption, existingToken.LevelsOption, existingToken.SearchOption, combinedSelects, new ExpandToken(childNodes))); }
/// <summary> /// Combine the child nodes of twoExpandTermTokens into one list of tokens /// </summary> /// <param name="existingToken">the existing token to to</param> /// <param name="newToken">the new token containing terms to add</param> /// <returns>a combined list of the all child nodes of the two tokens.</returns> public IEnumerable <ExpandTermToken> CombineChildNodes(ExpandTermToken existingToken, ExpandTermToken newToken) { if (existingToken.ExpandOption == null && newToken.ExpandOption == null) { return(new List <ExpandTermToken>()); } var childNodes = new Dictionary <PathSegmentToken, ExpandTermToken>(new PathSegmentTokenEqualityComparer()); if (existingToken.ExpandOption != null) { AddChildOptionsToDictionary(existingToken, childNodes); } if (newToken.ExpandOption != null) { AddChildOptionsToDictionary(newToken, childNodes); } return(childNodes.Values); }
/// <summary> /// Building off of a PathSegmentToken whose value is star, only nested level options is allowed. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <returns>An expand term token based on the path token, and all available expand options.</returns> private List <ExpandTermToken> BuildStarExpandTermToken(PathSegmentToken pathToken) { List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); long?levelsOption = null; bool isRefExpand = (pathToken.Identifier == UriQueryConstants.RefSegment); // Based on the specification, // For star in expand, this will be supported, // $expand=* // $expand=EntitySet($expand=* ) // $expand=*/$ref // $expand=*,EntitySet // $expand=EntitySet, * // $expand=*/$ref,EntitySet // Parenthesized set of expand options for star expand option supported are $level per specification. // And this will throw exception, // $expand= * /$count // Parenthesized set of expand options for star expand option which will also cause exception are $filter, $select, $orderby, $skip, $top, $count, $search, and $expand per specification. // And level is not supported with "*/$ref". // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Only level option is supported by expand. while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; switch (text) { case ExpressionConstants.QueryOptionLevels: { if (!isRefExpand) { levelsOption = ParseInnerLevel(); } else { // no option is allowed when expand with star per specification throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStarRef(this.lexer.ExpressionText)); } break; } default: { throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStar(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. var entityType = this.parentStructuredType as IEdmEntityType; if (entityType == null) { throw new ODataException(ODataErrorStrings.UriExpandParser_ParentEntityIsNull(this.lexer.ExpressionText)); } foreach (var navigationProperty in entityType.NavigationProperties()) { var tmpPathToken = default(PathSegmentToken); // create path token for each navigation properties. if (pathToken.Identifier.Equals(UriQueryConstants.RefSegment)) { tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken.NextToken); tmpPathToken = new NonSystemToken(UriQueryConstants.RefSegment, null, tmpPathToken); } else { tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken); } ExpandTermToken currentToken = new ExpandTermToken(tmpPathToken, null, null, null, null, null, levelsOption, null, null, null, null, null); expandTermTokenList.Add(currentToken); } return(expandTermTokenList); }
/// <summary> /// Building of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param> /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns> internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) { return(BuildStarExpandTermToken(pathToken)); } QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; long? levelsOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ExpandToken expandOption = null; ComputeToken computeOption = null; IEnumerable <QueryToken> applyOptions = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: // inner $filter filterOption = ParseInnerFilter(); break; case ExpressionConstants.QueryOptionOrderby: // inner $orderby orderByOptions = ParseInnerOrderBy(); break; case ExpressionConstants.QueryOptionTop: // inner $top topOption = ParseInnerTop(); break; case ExpressionConstants.QueryOptionSkip: // innner $skip skipOption = ParseInnerSkip(); break; case ExpressionConstants.QueryOptionCount: // inner $count countOption = ParseInnerCount(); break; case ExpressionConstants.QueryOptionSearch: // inner $search searchOption = ParseInnerSearch(); break; case ExpressionConstants.QueryOptionLevels: // inner $level levelsOption = ParseInnerLevel(); break; case ExpressionConstants.QueryOptionSelect: // inner $select selectOption = ParseInnerSelect(pathToken); break; case ExpressionConstants.QueryOptionExpand: // inner $expand expandOption = ParseInnerExpand(pathToken); break; case ExpressionConstants.QueryOptionCompute: // inner $compute computeOption = ParseInnerCompute(); break; case ExpressionConstants.QueryOptionApply: // inner $apply applyOptions = ParseInnerApply(); break; default: { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption, computeOption, applyOptions); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <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; 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 { 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, targetNavigationSource); // $compute ComputeClause computeOption = BindCompute(tokenIn.ComputeOption, 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, targetNavigationSource, null, generatedProperties, collapsed); // $orderby OrderByClause orderbyOption = BindOrderby(tokenIn.OrderByOptions, targetNavigationSource, null, generatedProperties, collapsed); // $search SearchClause searchOption = BindSearch(tokenIn.SearchOption, targetNavigationSource, null); if (isRef) { return(new ExpandedReferenceSelectItem(pathToNavProp, targetNavigationSource, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, computeOption, applyOption)); } // $select & $expand SelectExpandClause subSelectExpand = BindSelectExpand(tokenIn.ExpandOption, tokenIn.SelectOption, parsedPath, 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> /// Adds the expand token to the dictionary or combines it with an existing or combines it with another existing token with an equivalent path. /// </summary> /// <param name="combinedTerms">The combined terms dictionary.</param> /// <param name="expandedTerm">The expanded term to add or combine.</param> private void AddOrCombine(IDictionary <PathSegmentToken, ExpandTermToken> combinedTerms, ExpandTermToken expandedTerm) { ExpandTermToken existingTerm; if (combinedTerms.TryGetValue(expandedTerm.PathToNavigationProp, out existingTerm)) { combinedTerms[expandedTerm.PathToNavigationProp] = CombineTerms(expandedTerm, existingTerm); } else { combinedTerms.Add(expandedTerm.PathToNavigationProp, expandedTerm); } }
/// <summary> /// Building off of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param> /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns> internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) { return(BuildStarExpandTermToken(pathToken)); } QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; long? levelsOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ExpandToken expandOption = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: { // advance to the equal sign this.lexer.NextToken(); string filterText = this.ReadQueryOption(); UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier); filterOption = filterParser.ParseFilter(filterText); break; } case ExpressionConstants.QueryOptionOrderby: { // advance to the equal sign this.lexer.NextToken(); string orderByText = this.ReadQueryOption(); UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); orderByOptions = orderbyParser.ParseOrderBy(orderByText); break; } case ExpressionConstants.QueryOptionTop: { // advance to the equal sign this.lexer.NextToken(); string topText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long top; if (!long.TryParse(topText, out top) || top < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText)); } topOption = top; break; } case ExpressionConstants.QueryOptionSkip: { // advance to the equal sign this.lexer.NextToken(); string skipText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long skip; if (!long.TryParse(skipText, out skip) || skip < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText)); } skipOption = skip; break; } case ExpressionConstants.QueryOptionCount: { // advance to the equal sign this.lexer.NextToken(); string countText = this.ReadQueryOption(); switch (countText) { case ExpressionConstants.KeywordTrue: { countOption = true; break; } case ExpressionConstants.KeywordFalse: { countOption = false; break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText)); } } break; } case ExpressionConstants.QueryOptionLevels: { levelsOption = ResolveLevelOption(); break; } case ExpressionConstants.QueryOptionSearch: { // advance to the equal sign this.lexer.NextToken(); string searchText = this.ReadQueryOption(); SearchParser searchParser = new SearchParser(this.MaxSearchDepth); searchOption = searchParser.ParseSearch(searchText); break; } case ExpressionConstants.QueryOptionSelect: { // advance to the equal sign this.lexer.NextToken(); string selectText = this.ReadQueryOption(); SelectExpandParser innerSelectParser = new SelectExpandParser(selectText, this.maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier); selectOption = innerSelectParser.ParseSelect(); break; } case ExpressionConstants.QueryOptionExpand: { // advance to the equal sign this.lexer.NextToken(); string expandText = this.ReadQueryOption(); // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. IEdmStructuredType targetEntityType = null; if (this.resolver != null && this.parentEntityType != null) { var parentProperty = this.resolver.ResolveProperty(parentEntityType, pathToken.Identifier) as IEdmNavigationProperty; // it is a navigation property, need to find the type. // Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)", // find navigation property Trips of Friends, then get Entity type of Trips. if (parentProperty != null) { targetEntityType = parentProperty.ToEntityType(); } } SelectExpandParser innerExpandParser = new SelectExpandParser( resolver, expandText, targetEntityType, this.maxRecursionDepth - 1, this.enableCaseInsensitiveBuiltinIdentifier, this.enableNoDollarQueryOptions); expandOption = innerExpandParser.ParseExpand(); break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <summary> /// Visits an ExpandTermToken /// </summary> /// <param name="tokenIn">The ExpandTermToken to visit</param> /// <returns>A QueryNode bound to this ExpandTermToken</returns> public virtual T Visit(ExpandTermToken tokenIn) { throw new NotImplementedException(); }
/// <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; 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 { throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); } } // Add the segments in select and expand to parsed segments this.parsedSegments.AddRange(pathSoFar); IEdmNavigationSource targetNavigationSource = null; if (this.NavigationSource != null) { IEdmPathExpression bindingPath; targetNavigationSource = this.NavigationSource.FindNavigationTarget(currentNavProp, BindingPathHelper.MatchBindingPath, this.parsedSegments, out bindingPath); } NavigationPropertySegment navSegment = new NavigationPropertySegment(currentNavProp, targetNavigationSource); pathSoFar.Add(navSegment); this.parsedSegments.Add(navSegment); // Add the navigation property segment to parsed segments for future usage. ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar); // call MetadataBinder to build the filter clause FilterClause filterOption = null; if (tokenIn.FilterOption != null) { MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource); FilterBinder filterBinder = new FilterBinder(binder.Bind, binder.BindingState); filterOption = filterBinder.BindFilter(tokenIn.FilterOption); } // call MetadataBinder again to build the orderby clause OrderByClause orderbyOption = null; if (tokenIn.OrderByOptions != null) { MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource); OrderByBinder orderByBinder = new OrderByBinder(binder.Bind); orderbyOption = orderByBinder.BindOrderBy(binder.BindingState, tokenIn.OrderByOptions); } SearchClause searchOption = null; if (tokenIn.SearchOption != null) { MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource); SearchBinder searchBinder = new SearchBinder(binder.Bind); searchOption = searchBinder.BindSearch(tokenIn.SearchOption); } if (isRef) { return(new ExpandedReferenceSelectItem(pathToNavProp, targetNavigationSource, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption)); } SelectExpandClause subSelectExpand; if (tokenIn.ExpandOption != null) { subSelectExpand = this.GenerateSubExpand(tokenIn); } else { subSelectExpand = BuildDefaultSubExpand(); } subSelectExpand = this.DecorateExpandWithSelect(subSelectExpand, currentNavProp, tokenIn.SelectOption); LevelsClause levelsOption = this.ParseLevels(tokenIn.LevelsOption, currentLevelEntityType, currentNavProp); return(new ExpandedNavigationSelectItem(pathToNavProp, targetNavigationSource, subSelectExpand, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, levelsOption)); }
/// <summary> /// Generate a SubExpand based on the current nav property and the curren token /// </summary> /// <param name="tokenIn">the current token</param> /// <returns>a new SelectExpand clause bound to the current token and nav prop</returns> private SelectExpandClause GenerateSubExpand(ExpandTermToken tokenIn) { SelectExpandBinder nextLevelBinder = new SelectExpandBinder(this.Configuration, new ODataPathInfo(new ODataPath(this.parsedSegments))); return(nextLevelBinder.BindSubLevel(tokenIn.ExpandOption)); }