Skip to content
31 changes: 17 additions & 14 deletions src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ private SelectItem GenerateSelectItem(SelectTermToken tokenIn)

IEdmNavigationSource targetNavigationSource = null;
ODataPathSegment lastSegment = selectedPath.Last();
IEdmType targetElementType = lastSegment.TargetEdmType;
IEdmType targetElementType = lastSegment.EdmType;
IEdmCollectionType collection = targetElementType as IEdmCollectionType;
if (collection != null)
{
Expand Down Expand Up @@ -441,23 +441,25 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn)

ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar);

IEdmTypeReference elementTypeReference = hasDerivedTypeSegment ? derivedType.ToTypeReference() : null;
Copy link
Contributor

@habbes habbes Jan 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why does the elementTypeReference default to null when there's no derived type segment as opposed to defaulting to the type of the property being expanded? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's null since we can get it from the expanded property in these lines

BindingState state = new BindingState(config)
{
ImplicitRangeVariable =
NodeFactory.CreateImplicitRangeVariable(elementType != null ? elementType :
targetNavigationSource.EntityType().ToTypeReference(), targetNavigationSource)
};

Copy link
Member

@mikepizzo mikepizzo Feb 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that we are getting the type in SelectExpandBinder, but what is the contract with BindFilter, BindOrderby, etc? These private functions take an elementType which gets passed eventually to CreateBindingState where the logic is to fall back to the navigationSource, if it's not otherwise specified. But it's not clear in the various private methods that the elementType is nullable, and future code could make invalid assumptions about whether it may be null. If that private CreateBindingState method is only reached through these methods, would it be cleaner to compute the elementType from the targetNavigationSource here so it is never null in subsequent methods? Alternatively, maybe at least clarify in the description of the elementType parameter to the various methods (i.e., line 529) "The target element type if different from the type of the target navigation source." Logic is sound, I just want us to consider what is the cleaner/more intuitive contract for maintainability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikepizzo I have updated the description.


// $apply
ApplyClause applyOption = BindApply(tokenIn.ApplyOptions, this.ResourcePathNavigationSource, targetNavigationSource);
ApplyClause applyOption = BindApply(tokenIn.ApplyOptions, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference);

// $compute
ComputeClause computeOption = BindCompute(tokenIn.ComputeOption, this.ResourcePathNavigationSource, targetNavigationSource);
ComputeClause computeOption = BindCompute(tokenIn.ComputeOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference);

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);
FilterClause filterOption = BindFilter(tokenIn.FilterOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties, collapsed);

// $orderby
OrderByClause orderbyOption = BindOrderby(tokenIn.OrderByOptions, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed);
OrderByClause orderbyOption = BindOrderby(tokenIn.OrderByOptions, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties, collapsed);

// $search
SearchClause searchOption = BindSearch(tokenIn.SearchOption, this.ResourcePathNavigationSource, targetNavigationSource, null);
SearchClause searchOption = BindSearch(tokenIn.SearchOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference);

if (isRef)
{
Expand All @@ -470,7 +472,7 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn)
}

// $select & $expand
SelectExpandClause subSelectExpand = BindSelectExpand(tokenIn.ExpandOption, tokenIn.SelectOption, parsedPath, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed);
SelectExpandClause subSelectExpand = BindSelectExpand(tokenIn.ExpandOption, tokenIn.SelectOption, parsedPath, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties, collapsed);

// $levels
LevelsClause levelsOption = ParseLevels(tokenIn.LevelsOption, currentLevelEntityType, currentNavProp);
Expand All @@ -485,12 +487,13 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn)
/// <param name="applyToken">The apply tokens to visit.</param>
/// <param name="resourcePathNavigationSource">The navigation source at the resource path.</param>
/// <param name="targetNavigationSource">The target navigation source at the current level.</param>
/// <param name="elementType">The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type.</param>
/// <returns>The null or the built apply clause.</returns>
private ApplyClause BindApply(IEnumerable<QueryToken> applyToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource)
private ApplyClause BindApply(IEnumerable<QueryToken> applyToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource, IEdmTypeReference elementType = null)
{
if (applyToken != null && applyToken.Any())
{
MetadataBinder binder = BuildNewMetadataBinder(this.Configuration, resourcePathNavigationSource, targetNavigationSource, null);
MetadataBinder binder = BuildNewMetadataBinder(this.Configuration, resourcePathNavigationSource, targetNavigationSource, elementType);
ApplyBinder applyBinder = new ApplyBinder(binder.Bind, binder.BindingState);
return applyBinder.BindApply(applyToken);
}
Expand All @@ -504,7 +507,7 @@ private ApplyClause BindApply(IEnumerable<QueryToken> applyToken, IEdmNavigation
/// <param name="computeToken">The compute token to visit.</param>
/// <param name="resourcePathNavigationSource">The navigation source at the resource path.</param>
/// <param name="targetNavigationSource">The target navigation source at the current level.</param>
/// <param name="elementType">The target element type.</param>
/// <param name="elementType">The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type.</param>
/// <returns>The null or the built compute clause.</returns>
private ComputeClause BindCompute(ComputeToken computeToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource, IEdmTypeReference elementType = null)
{
Expand All @@ -524,7 +527,7 @@ private ComputeClause BindCompute(ComputeToken computeToken, IEdmNavigationSourc
/// <param name="filterToken">The filter token to visit.</param>
/// <param name="resourcePathNavigationSource">The navigation source at the resource path.</param>
/// <param name="targetNavigationSource">The target navigation source at the current level.</param>
/// <param name="elementType">The Edm element type.</param>
/// <param name="elementType">The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type.</param>
/// <param name="generatedProperties">The generated properties.</param>
/// <param name="collapsed">The collapsed boolean value.</param>
/// <returns>The null or the built filter clause.</returns>
Expand All @@ -547,7 +550,7 @@ private FilterClause BindFilter(QueryToken filterToken, IEdmNavigationSource res
/// <param name="orderByToken">The orderby token to visit.</param>
/// <param name="resourcePathNavigationSource">The navigation source at the resource path.</param>
/// <param name="targetNavigationSource">The target navigation source at the current level.</param>
/// <param name="elementType">The Edm element type.</param>
/// <param name="elementType">The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type.</param>
/// <param name="generatedProperties">The generated properties.</param>
/// <param name="collapsed">The collapsed boolean value.</param>
/// <returns>The null or the built filter clause.</returns>
Expand All @@ -571,7 +574,7 @@ private OrderByClause BindOrderby(IEnumerable<OrderByToken> orderByToken, IEdmNa
/// <param name="searchToken">The search token to visit.</param>
/// <param name="resourcePathNavigationSource">The navigation source at the resource path.</param>
/// <param name="targetNavigationSource">The target navigation source at the current level.</param>
/// <param name="elementType">The Edm element type.</param>
/// <param name="elementType">The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type.</param>
/// <returns>The null or the built search clause.</returns>
private SearchClause BindSearch(QueryToken searchToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource, IEdmTypeReference elementType)
{
Expand All @@ -593,7 +596,7 @@ private SearchClause BindSearch(QueryToken searchToken, IEdmNavigationSource res
/// <param name="segments">The parsed segments to visit.</param>
/// <param name="resourcePathNavigationSource">The navigation source at the resource path.</param>
/// <param name="targetNavigationSource">The target navigation source at the current level.</param>
/// <param name="elementType">The Edm element type.</param>
/// <param name="elementType">The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type.</param>
/// <param name="generatedProperties">The generated properties.</param>
/// <param name="collapsed">The collapsed boolean value.</param>
/// <returns>The null or the built select and expand clause.</returns>
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public ODataPathInfo(ODataPath odataPath)
}

this.targetNavigationSource = lastSegment.TargetEdmNavigationSource;
this.targetEdmType = lastSegment.TargetEdmType;
this.targetEdmType = lastSegment.EdmType;
if (this.targetEdmType != null)
{
IEdmCollectionType collectionType = this.targetEdmType as IEdmCollectionType;
Expand Down
Loading