77namespace Microsoft . OData . UriParser
88{
99 using System ;
10+ using System . Collections . Generic ;
1011 using System . Diagnostics ;
11- using System . Linq ;
12- using Microsoft . OData . Edm ;
1312 using Microsoft . OData ;
14- using Microsoft . OData . Metadata ;
1513 using Microsoft . OData . Core ;
14+ using Microsoft . OData . Edm ;
15+ using Microsoft . OData . Metadata ;
1616
1717 /// <summary>
1818 /// Helper methods for metadata binding.
@@ -66,14 +66,14 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
6666 IEdmEnumType enumType = targetTypeReference . Definition as IEdmEnumType ;
6767 if ( enumType . ContainsMember ( memberName , StringComparison . Ordinal ) )
6868 {
69- string literalText = ODataUriUtils . ConvertToUriLiteral ( constantNode . Value , default ( ODataVersion ) ) ;
69+ string literalText = ODataUriUtils . ConvertToUriLiteral ( constantNode . Value , default ) ;
7070 return new ConstantNode ( new ODataEnumValue ( memberName , enumType . ToString ( ) ) , literalText , targetTypeReference ) ;
7171 }
7272
7373 // If the member name is an integral value, we should try to convert it to the enum member name and find the enum member with the matching integral value
7474 if ( long . TryParse ( memberName , out long memberIntegralValue ) && enumType . TryParse ( memberIntegralValue , out IEdmEnumMember enumMember ) )
7575 {
76- string literalText = ODataUriUtils . ConvertToUriLiteral ( enumMember . Name , default ( ODataVersion ) ) ;
76+ string literalText = ODataUriUtils . ConvertToUriLiteral ( enumMember . Name , default ) ;
7777 return new ConstantNode ( new ODataEnumValue ( enumMember . Name , enumType . ToString ( ) ) , literalText , targetTypeReference ) ;
7878 }
7979
@@ -111,8 +111,8 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
111111 var targetDecimalType = ( IEdmDecimalTypeReference ) targetTypeReference ;
112112 return decimalType . Precision == targetDecimalType . Precision &&
113113 decimalType . Scale == targetDecimalType . Scale ?
114- ( SingleValueNode ) candidate :
115- ( SingleValueNode ) ( new ConvertNode ( candidate , targetTypeReference ) ) ;
114+ candidate :
115+ new ConvertNode ( candidate , targetTypeReference ) ;
116116 }
117117 else
118118 {
@@ -134,6 +134,138 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
134134 }
135135 }
136136
137+ /// <summary>
138+ /// Converts a collection node's element type to <paramref name="targetTypeReference"/> when possible,
139+ /// materializing a new <see cref="CollectionConstantNode"/> for constant collections;
140+ /// leaves non-constant/open or non-convertible collections unchanged.
141+ /// </summary>
142+ /// <param name="source">Source collection node.</param>
143+ /// <param name="targetTypeReference">Desired collection type (must be a collection).</param>
144+ /// <returns>Converted collection node or original source.</returns>
145+ internal static CollectionNode ConvertToTypeIfNeeded ( CollectionNode source , IEdmTypeReference targetTypeReference )
146+ {
147+ Debug . Assert ( source != null , "source != null" ) ;
148+
149+ if ( targetTypeReference == null )
150+ {
151+ return source ;
152+ }
153+
154+ IEdmCollectionTypeReference sourceCollectionType = source . CollectionType ;
155+ if ( sourceCollectionType == null ) // Open collection? Leave as is
156+ {
157+ return source ;
158+ }
159+
160+ if ( ! targetTypeReference . IsCollection ( ) )
161+ {
162+ throw new ODataException ( Error . Format ( SRResources . MetadataBinder_CannotConvertToType , source . CollectionType . FullName ( ) , targetTypeReference . FullName ( ) ) ) ;
163+ }
164+
165+ IEdmCollectionTypeReference targetCollectionType = targetTypeReference . AsCollection ( ) ;
166+
167+ if ( sourceCollectionType . IsEquivalentTo ( targetCollectionType ) )
168+ {
169+ IEdmTypeReference sourceElemType = sourceCollectionType . ElementType ( ) ;
170+ IEdmTypeReference targetElemType = targetCollectionType . ElementType ( ) ;
171+ if ( source is CollectionConstantNode colConstantNode
172+ && sourceElemType . IsTypeDefinition ( )
173+ && targetElemType . IsPrimitive ( )
174+ && sourceElemType . AsPrimitive ( ) . PrimitiveKind ( ) == targetElemType . AsPrimitive ( ) . PrimitiveKind ( ) )
175+ {
176+ List < ConstantNode > convertedNodes = ConvertNodes ( colConstantNode . Collection , targetElemType ) ;
177+
178+ return new CollectionConstantNode ( convertedNodes , BuildCollectionLiteral ( convertedNodes , targetElemType ) , targetCollectionType ) ;
179+ }
180+
181+ return source ;
182+ }
183+
184+ IEdmTypeReference sourceElementType = sourceCollectionType . ElementType ( ) ;
185+ IEdmTypeReference targetElementType = targetCollectionType . ElementType ( ) ;
186+
187+ if ( ! TypePromotionUtils . CanConvertTo ( null , sourceElementType , targetElementType ) )
188+ {
189+ throw new ODataException ( Error . Format ( SRResources . MetadataBinder_CannotConvertToType , sourceElementType . FullName ( ) , targetElementType . FullName ( ) ) ) ;
190+ }
191+
192+ if ( source is CollectionConstantNode collectionConstantNode )
193+ {
194+ List < ConstantNode > convertedNodes = ConvertNodes ( collectionConstantNode . Collection , targetElementType ) ;
195+
196+ return new CollectionConstantNode ( convertedNodes , BuildCollectionLiteral ( convertedNodes , targetElementType ) , targetCollectionType ) ;
197+ }
198+
199+ // Non-constant collections: leave as-is (conversion implicit)
200+ return source ;
201+ }
202+
203+ /// <summary>
204+ /// Converts each constant value to <paramref name="targetElementType"/>, applying enum/numeric coercion; preserves null items.
205+ /// </summary>
206+ /// <param name="nodes">Original constant value nodes.</param>
207+ /// <param name="targetElementType">The target primitive type.</param>
208+ /// <returns>List of converted constant nodes.</returns>
209+ private static List < ConstantNode > ConvertNodes ( IList < ConstantNode > nodes , IEdmTypeReference targetElementType )
210+ {
211+ List < ConstantNode > convertedNodes = new List < ConstantNode > ( nodes . Count ) ;
212+
213+ for ( int i = 0 ; i < nodes . Count ; i ++ )
214+ {
215+ ConstantNode item = nodes [ i ] ;
216+ if ( item == null )
217+ {
218+ // Preserve null
219+ convertedNodes . Add ( new ConstantNode ( null , "null" , targetElementType ) ) ;
220+ continue ;
221+ }
222+
223+ ConstantNode convertedNode = ConvertToTypeIfNeeded ( item , targetElementType ) as ConstantNode ;
224+
225+ // If ConvertToTypeIfNeeded returned a ConvertNode, force materialization into a ConstantNode
226+ if ( convertedNode == null )
227+ {
228+ // Try to keep original literal text if meaningful
229+ string literal = item . LiteralText ?? ODataUriUtils . ConvertToUriLiteral ( item . Value , ODataVersion . V4 ) ;
230+ convertedNode = new ConstantNode ( item . Value , literal , targetElementType ) ;
231+ }
232+
233+ convertedNodes . Add ( convertedNode ) ;
234+ }
235+
236+ return convertedNodes ;
237+ }
238+
239+ /// <summary>
240+ /// Builds a bracketed collection literal (e.g. [1,2,3]) from constant nodes, quoting/escaping strings and preserving nulls.
241+ /// </summary>
242+ /// <param name="nodes">Constant nodes representing items.</param>
243+ /// <param name="typeReference">Element type for string quoting rules.</param>
244+ /// <returns>OData collection literal text.</returns>
245+ private static string BuildCollectionLiteral ( List < ConstantNode > nodes , IEdmTypeReference typeReference )
246+ {
247+ List < string > list = new List < string > ( ) ;
248+ for ( int i = 0 ; i < nodes . Count ; i ++ )
249+ {
250+ ConstantNode node = nodes [ i ] ;
251+ if ( node == null || node . Value == null )
252+ {
253+ list . Add ( "null" ) ;
254+ continue ;
255+ }
256+
257+ string literal = node . LiteralText ?? ODataUriUtils . ConvertToUriLiteral ( node . Value , ODataVersion . V4 ) ;
258+ if ( typeReference . IsString ( ) && ! ( literal . Length > 1 && literal [ 0 ] == '\' ' && literal [ ^ 1 ] == '\' ' ) )
259+ {
260+ literal = $ "'{ literal . Replace ( "'" , "''" , StringComparison . Ordinal ) } '";
261+ }
262+
263+ list . Add ( literal ) ;
264+ }
265+
266+ return "[" + string . Join ( "," , list ) + "]" ;
267+ }
268+
137269 /// <summary>
138270 /// Retrieves type associated to a segment.
139271 /// </summary>
0 commit comments