@@ -93,38 +93,40 @@ internal static bool NavigationRestrictionsAllowsNavigability(
9393 /// Generates the operation id from a navigation property path.
9494 /// </summary>
9595 /// <param name="path">The target <see cref="ODataPath"/>.</param>
96+ /// <param name="context">The OData context.</param>
9697 /// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued non-indexed or single-valued navigation property.</param>
9798 /// <returns>The operation id generated from the given navigation property path.</returns>
98- internal static string GenerateNavigationPropertyPathOperationId ( ODataPath path , string prefix = null )
99+ internal static string GenerateNavigationPropertyPathOperationId ( ODataPath path , ODataContext context , string prefix = null )
99100 {
100- IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path ) ;
101+ IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path , context ) ;
101102
102103 if ( ! items . Any ( ) )
103104 return null ;
104105
105- int lastItemIndex = items . Count - 1 ;
106+ int lastItemIndex = items [ ^ 1 ] . StartsWith ( '-' ) ? items . Count - 2 : items . Count - 1 ;
106107
107108 if ( ! string . IsNullOrEmpty ( prefix ) )
108109 {
109- items [ lastItemIndex ] = prefix + Utils . UpperFirstChar ( items . Last ( ) ) ;
110+ items [ lastItemIndex ] = prefix + Utils . UpperFirstChar ( items [ lastItemIndex ] ) ;
110111 }
111112 else
112113 {
113- items [ lastItemIndex ] = Utils . UpperFirstChar ( items . Last ( ) ) ;
114+ items [ lastItemIndex ] = Utils . UpperFirstChar ( items [ lastItemIndex ] ) ;
114115 }
115116
116- return string . Join ( "." , items ) ;
117+ return GenerateNavigationPropertyPathOperationId ( items ) ;
117118 }
118119
119120 /// <summary>
120121 /// Generates the operation id from a complex property path.
121122 /// </summary>
122123 /// <param name="path">The target <see cref="ODataPath"/>.</param>
124+ /// <param name="context">The OData context.</param>
123125 /// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued or single-valued complex property.</param>
124126 /// <returns>The operation id generated from the given complex property path.</returns>
125- internal static string GenerateComplexPropertyPathOperationId ( ODataPath path , string prefix = null )
127+ internal static string GenerateComplexPropertyPathOperationId ( ODataPath path , ODataContext context , string prefix = null )
126128 {
127- IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path ) ;
129+ IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path , context ) ;
128130
129131 if ( ! items . Any ( ) )
130132 return null ;
@@ -141,15 +143,29 @@ internal static string GenerateComplexPropertyPathOperationId(ODataPath path, st
141143 items . Add ( Utils . UpperFirstChar ( lastSegment ? . Identifier ) ) ;
142144 }
143145
144- return string . Join ( "." , items ) ;
146+ return GenerateNavigationPropertyPathOperationId ( items ) ;
147+ }
148+
149+ /// <summary>
150+ /// Generates a navigation property operation id from a list of string values.
151+ /// </summary>
152+ /// <param name="items">The list of string values.</param>
153+ /// <returns>The generated navigation property operation id.</returns>
154+ private static string GenerateNavigationPropertyPathOperationId ( IList < string > items )
155+ {
156+ if ( ! items . Any ( ) )
157+ return null ;
158+
159+ return string . Join ( "." , items ) . Replace ( ".-" , "-" , StringComparison . OrdinalIgnoreCase ) ; // Format any hashed value appropriately (this will be the last value)
145160 }
146161
147162 /// <summary>
148163 /// Retrieves the segments of an operation id generated from a navigation property path.
149164 /// </summary>
150165 /// <param name="path">The target <see cref="ODataPath"/>.</param>
166+ /// <param name="context">The OData context.</param>
151167 /// <returns>The segments of an operation id generated from the given navigation property path.</returns>
152- internal static IList < string > RetrieveNavigationPropertyPathsOperationIdSegments ( ODataPath path )
168+ internal static IList < string > RetrieveNavigationPropertyPathsOperationIdSegments ( ODataPath path , ODataContext context )
153169 {
154170 Utils . CheckArgumentNull ( path , nameof ( path ) ) ;
155171
@@ -173,6 +189,8 @@ s is ODataOperationSegment ||
173189 Utils . CheckArgumentNull ( segments , nameof ( segments ) ) ;
174190
175191 string previousTypeCastSegmentId = null ;
192+ string pathHash = string . Empty ;
193+
176194 foreach ( var segment in segments )
177195 {
178196 if ( segment is ODataNavigationPropertySegment navPropSegment )
@@ -192,6 +210,14 @@ s is ODataOperationSegment ||
192210 else if ( segment is ODataOperationSegment operationSegment )
193211 {
194212 // Navigation property generated via composable function
213+ if ( operationSegment . Operation is IEdmFunction function && context . Model . IsOperationOverload ( function ) )
214+ {
215+ // Hash the segment to avoid duplicate operationIds
216+ pathHash = string . IsNullOrEmpty ( pathHash )
217+ ? operationSegment . GetPathHash ( context . Settings )
218+ : ( pathHash + operationSegment . GetPathHash ( context . Settings ) ) . GetHashSHA256 ( ) [ ..4 ] ;
219+ }
220+
195221 items . Add ( operationSegment . Identifier ) ;
196222 }
197223 else if ( segment is ODataKeySegment keySegment && keySegment . IsAlternateKey )
@@ -208,6 +234,11 @@ s is ODataOperationSegment ||
208234 }
209235 }
210236
237+ if ( ! string . IsNullOrEmpty ( pathHash ) )
238+ {
239+ items . Add ( "-" + pathHash ) ;
240+ }
241+
211242 return items ;
212243 }
213244
@@ -320,9 +351,10 @@ internal static string GenerateComplexPropertyPathTagName(ODataPath path, ODataC
320351 /// Generates the operation id prefix from an OData type cast path.
321352 /// </summary>
322353 /// <param name="path">The target <see cref="ODataPath"/>.</param>
354+ /// <param name="context">The OData context.</param>
323355 /// <param name="includeListOrGetPrefix">Optional: Whether to include the List or Get prefix to the generated operation id.</param>
324356 /// <returns>The operation id prefix generated from the OData type cast path.</returns>
325- internal static string GenerateODataTypeCastPathOperationIdPrefix ( ODataPath path , bool includeListOrGetPrefix = true )
357+ internal static string GenerateODataTypeCastPathOperationIdPrefix ( ODataPath path , ODataContext context , bool includeListOrGetPrefix = true )
326358 {
327359 // Get the segment before the last OData type cast segment
328360 ODataTypeCastSegment typeCastSegment = path . Segments . OfType < ODataTypeCastSegment > ( ) ? . Last ( ) ;
@@ -352,7 +384,7 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
352384 if ( secondLastSegment is ODataComplexPropertySegment complexSegment )
353385 {
354386 string listOrGet = includeListOrGetPrefix ? ( complexSegment . Property . Type . IsCollection ( ) ? "List" : "Get" ) : null ;
355- operationId = GenerateComplexPropertyPathOperationId ( path , listOrGet ) ;
387+ operationId = GenerateComplexPropertyPathOperationId ( path , context , listOrGet ) ;
356388 }
357389 else if ( secondLastSegment is ODataNavigationPropertySegment navPropSegment )
358390 {
@@ -362,13 +394,13 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
362394 prefix = navPropSegment ? . NavigationProperty . TargetMultiplicity ( ) == EdmMultiplicity . Many ? "List" : "Get" ;
363395 }
364396
365- operationId = GenerateNavigationPropertyPathOperationId ( path , prefix ) ;
397+ operationId = GenerateNavigationPropertyPathOperationId ( path , context , prefix ) ;
366398 }
367399 else if ( secondLastSegment is ODataKeySegment keySegment )
368400 {
369401 if ( isIndexedCollValuedNavProp )
370402 {
371- operationId = GenerateNavigationPropertyPathOperationId ( path , "Get" ) ;
403+ operationId = GenerateNavigationPropertyPathOperationId ( path , context , "Get" ) ;
372404 }
373405 else
374406 {
0 commit comments