66using System ;
77using System . Collections . Generic ;
88using System . Linq ;
9+ using System . Security . Cryptography . X509Certificates ;
910using Microsoft . OData . Edm ;
1011using Microsoft . OData . Edm . Csdl ;
1112using Microsoft . OData . Edm . Vocabularies ;
@@ -93,38 +94,40 @@ internal static bool NavigationRestrictionsAllowsNavigability(
9394 /// Generates the operation id from a navigation property path.
9495 /// </summary>
9596 /// <param name="path">The target <see cref="ODataPath"/>.</param>
97+ /// <param name="context">The OData context.</param>
9698 /// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued non-indexed or single-valued navigation property.</param>
9799 /// <returns>The operation id generated from the given navigation property path.</returns>
98- internal static string GenerateNavigationPropertyPathOperationId ( ODataPath path , string prefix = null )
100+ internal static string GenerateNavigationPropertyPathOperationId ( ODataPath path , ODataContext context , string prefix = null )
99101 {
100- IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path ) ;
102+ IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path , context ) ;
101103
102104 if ( ! items . Any ( ) )
103105 return null ;
104106
105- int lastItemIndex = items . Count - 1 ;
107+ int lastItemIndex = items [ items . Count - 1 ] . StartsWith ( "-" ) ? items . Count - 2 : items . Count - 1 ;
106108
107109 if ( ! string . IsNullOrEmpty ( prefix ) )
108110 {
109- items [ lastItemIndex ] = prefix + Utils . UpperFirstChar ( items . Last ( ) ) ;
111+ items [ lastItemIndex ] = prefix + Utils . UpperFirstChar ( items [ lastItemIndex ] ) ;
110112 }
111113 else
112114 {
113- items [ lastItemIndex ] = Utils . UpperFirstChar ( items . Last ( ) ) ;
115+ items [ lastItemIndex ] = Utils . UpperFirstChar ( items [ lastItemIndex ] ) ;
114116 }
115117
116- return string . Join ( "." , items ) ;
118+ return GenerateNavigationPropertyPathOperationId ( items ) ;
117119 }
118120
119121 /// <summary>
120122 /// Generates the operation id from a complex property path.
121123 /// </summary>
122124 /// <param name="path">The target <see cref="ODataPath"/>.</param>
125+ /// <param name="context">The OData context.</param>
123126 /// <param name="prefix">Optional: Identifier indicating whether it is a collection-valued or single-valued complex property.</param>
124127 /// <returns>The operation id generated from the given complex property path.</returns>
125- internal static string GenerateComplexPropertyPathOperationId ( ODataPath path , string prefix = null )
128+ internal static string GenerateComplexPropertyPathOperationId ( ODataPath path , ODataContext context , string prefix = null )
126129 {
127- IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path ) ;
130+ IList < string > items = RetrieveNavigationPropertyPathsOperationIdSegments ( path , context ) ;
128131
129132 if ( ! items . Any ( ) )
130133 return null ;
@@ -141,15 +144,29 @@ internal static string GenerateComplexPropertyPathOperationId(ODataPath path, st
141144 items . Add ( Utils . UpperFirstChar ( lastSegment ? . Identifier ) ) ;
142145 }
143146
144- return string . Join ( "." , items ) ;
147+ return GenerateNavigationPropertyPathOperationId ( items ) ;
148+ }
149+
150+ /// <summary>
151+ /// Generates a navigation property operation id from a list of string values.
152+ /// </summary>
153+ /// <param name="items">The list of string values.</param>
154+ /// <returns>The generated navigation property operation id.</returns>
155+ private static string GenerateNavigationPropertyPathOperationId ( IList < string > items )
156+ {
157+ if ( ! items . Any ( ) )
158+ return null ;
159+
160+ return string . Join ( "." , items ) . Replace ( ".-" , "-" ) ; // Format any hashed value appropriately (this will be the last value)
145161 }
146162
147163 /// <summary>
148164 /// Retrieves the segments of an operation id generated from a navigation property path.
149165 /// </summary>
150166 /// <param name="path">The target <see cref="ODataPath"/>.</param>
167+ /// <param name="context">The OData context.</param>
151168 /// <returns>The segments of an operation id generated from the given navigation property path.</returns>
152- internal static IList < string > RetrieveNavigationPropertyPathsOperationIdSegments ( ODataPath path )
169+ internal static IList < string > RetrieveNavigationPropertyPathsOperationIdSegments ( ODataPath path , ODataContext context )
153170 {
154171 Utils . CheckArgumentNull ( path , nameof ( path ) ) ;
155172
@@ -173,6 +190,8 @@ s is ODataOperationSegment ||
173190 Utils . CheckArgumentNull ( segments , nameof ( segments ) ) ;
174191
175192 string previousTypeCastSegmentId = null ;
193+ string pathHash = string . Empty ;
194+
176195 foreach ( var segment in segments )
177196 {
178197 if ( segment is ODataNavigationPropertySegment navPropSegment )
@@ -189,25 +208,38 @@ s is ODataOperationSegment ||
189208 previousTypeCastSegmentId = "As" + Utils . UpperFirstChar ( schemaElement . Name ) ;
190209 items . Add ( previousTypeCastSegmentId ) ;
191210 }
192- else if ( segment is ODataOperationSegment operationSegment )
193- {
194- // Navigation property generated via composable function
195- items . Add ( operationSegment . Identifier ) ;
211+ else if ( segment is ODataOperationSegment operationSegment )
212+ {
213+ // Navigation property generated via composable function
214+ if ( operationSegment . Operation is IEdmFunction function && context . Model . IsOperationOverload ( function ) )
215+ {
216+ // Hash the segment to avoid duplicate operationIds
217+ pathHash = string . IsNullOrEmpty ( pathHash )
218+ ? operationSegment . GetPathHash ( context . Settings )
219+ : ( pathHash + operationSegment . GetPathHash ( context . Settings ) ) . GetHashSHA256 ( ) . Substring ( 0 , 4 ) ;
220+ }
221+
222+ items . Add ( operationSegment . Identifier ) ;
196223 }
197- else if ( segment is ODataKeySegment keySegment && keySegment . IsAlternateKey )
198- {
199- // We'll consider alternate keys in the operation id to eliminate potential duplicates with operation id of primary path
200- if ( segment == segments . Last ( ) )
201- {
202- items . Add ( "By" + string . Join ( "" , keySegment . Identifier . Split ( ',' ) . Select ( static x => Utils . UpperFirstChar ( x ) ) ) ) ;
203- }
204- else
205- {
206- items . Add ( keySegment . Identifier ) ;
207- }
224+ else if ( segment is ODataKeySegment keySegment && keySegment . IsAlternateKey )
225+ {
226+ // We'll consider alternate keys in the operation id to eliminate potential duplicates with operation id of primary path
227+ if ( segment == segments . Last ( ) )
228+ {
229+ items . Add ( "By" + string . Join ( "" , keySegment . Identifier . Split ( ',' ) . Select ( static x => Utils . UpperFirstChar ( x ) ) ) ) ;
230+ }
231+ else
232+ {
233+ items . Add ( keySegment . Identifier ) ;
234+ }
208235 }
209236 }
210237
238+ if ( ! string . IsNullOrEmpty ( pathHash ) )
239+ {
240+ items . Add ( "-" + pathHash ) ;
241+ }
242+
211243 return items ;
212244 }
213245
@@ -320,9 +352,10 @@ internal static string GenerateComplexPropertyPathTagName(ODataPath path, ODataC
320352 /// Generates the operation id prefix from an OData type cast path.
321353 /// </summary>
322354 /// <param name="path">The target <see cref="ODataPath"/>.</param>
355+ /// <param name="context">The OData context.</param>
323356 /// <param name="includeListOrGetPrefix">Optional: Whether to include the List or Get prefix to the generated operation id.</param>
324357 /// <returns>The operation id prefix generated from the OData type cast path.</returns>
325- internal static string GenerateODataTypeCastPathOperationIdPrefix ( ODataPath path , bool includeListOrGetPrefix = true )
358+ internal static string GenerateODataTypeCastPathOperationIdPrefix ( ODataPath path , ODataContext context , bool includeListOrGetPrefix = true )
326359 {
327360 // Get the segment before the last OData type cast segment
328361 ODataTypeCastSegment typeCastSegment = path . Segments . OfType < ODataTypeCastSegment > ( ) ? . Last ( ) ;
@@ -352,7 +385,7 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
352385 if ( secondLastSegment is ODataComplexPropertySegment complexSegment )
353386 {
354387 string listOrGet = includeListOrGetPrefix ? ( complexSegment . Property . Type . IsCollection ( ) ? "List" : "Get" ) : null ;
355- operationId = GenerateComplexPropertyPathOperationId ( path , listOrGet ) ;
388+ operationId = GenerateComplexPropertyPathOperationId ( path , context , listOrGet ) ;
356389 }
357390 else if ( secondLastSegment is ODataNavigationPropertySegment navPropSegment )
358391 {
@@ -362,27 +395,27 @@ internal static string GenerateODataTypeCastPathOperationIdPrefix(ODataPath path
362395 prefix = navPropSegment ? . NavigationProperty . TargetMultiplicity ( ) == EdmMultiplicity . Many ? "List" : "Get" ;
363396 }
364397
365- operationId = GenerateNavigationPropertyPathOperationId ( path , prefix ) ;
398+ operationId = GenerateNavigationPropertyPathOperationId ( path , context , prefix ) ;
366399 }
367400 else if ( secondLastSegment is ODataKeySegment keySegment )
368401 {
369- if ( isIndexedCollValuedNavProp )
370- {
371- operationId = GenerateNavigationPropertyPathOperationId ( path , "Get" ) ;
402+ if ( isIndexedCollValuedNavProp )
403+ {
404+ operationId = GenerateNavigationPropertyPathOperationId ( path , context , "Get" ) ;
405+ }
406+ else
407+ {
408+ string entityTypeName = keySegment . EntityType . Name ;
409+ string getPrefix = includeListOrGetPrefix ? "Get" : null ;
410+ string operationName = $ "{ getPrefix } { Utils . UpperFirstChar ( entityTypeName ) } ";
411+ if ( keySegment . IsAlternateKey )
412+ {
413+ string alternateKeyName = string . Join ( "" , keySegment . Identifier . Split ( ',' ) . Select ( static x => Utils . UpperFirstChar ( x ) ) ) ;
414+ operationName = $ "{ operationName } By{ alternateKeyName } ";
415+ }
416+ operationId = ( entitySet != null ) ? entitySet . Name : singleton . Name ;
417+ operationId += $ ".{ entityTypeName } .{ operationName } ";
372418 }
373- else
374- {
375- string entityTypeName = keySegment . EntityType . Name ;
376- string getPrefix = includeListOrGetPrefix ? "Get" : null ;
377- string operationName = $ "{ getPrefix } { Utils . UpperFirstChar ( entityTypeName ) } ";
378- if ( keySegment . IsAlternateKey )
379- {
380- string alternateKeyName = string . Join ( "" , keySegment . Identifier . Split ( ',' ) . Select ( static x => Utils . UpperFirstChar ( x ) ) ) ;
381- operationName = $ "{ operationName } By{ alternateKeyName } ";
382- }
383- operationId = ( entitySet != null ) ? entitySet . Name : singleton . Name ;
384- operationId += $ ".{ entityTypeName } .{ operationName } ";
385- }
386419 }
387420 else if ( secondLastSegment is ODataNavigationSourceSegment )
388421 {
0 commit comments