2
2
3
3
using System . Collections . Generic ;
4
4
using System . Diagnostics . CodeAnalysis ;
5
+ using System . Diagnostics . Contracts ;
5
6
using System . Linq ;
6
7
using System . Web . Http ;
7
8
using System . Web . OData . Properties ;
@@ -20,6 +21,7 @@ public class SelectExpandQueryOption
20
21
{
21
22
private SelectExpandClause _selectExpandClause ;
22
23
private ODataQueryOptionParser _queryOptionParser ;
24
+ private int _levelsMaxLiteralExpansionDepth = ODataValidationSettings . DefaultMaxExpansionDepth ;
23
25
24
26
/// <summary>
25
27
/// Initializes a new instance of the <see cref="SelectExpandQueryOption"/> class.
@@ -59,6 +61,16 @@ public SelectExpandQueryOption(string select, string expand, ODataQueryContext c
59
61
_queryOptionParser = queryOptionParser ;
60
62
}
61
63
64
+ internal SelectExpandQueryOption (
65
+ string select ,
66
+ string expand ,
67
+ ODataQueryContext context ,
68
+ SelectExpandClause selectExpandClause )
69
+ : this ( select , expand , context )
70
+ {
71
+ _selectExpandClause = selectExpandClause ;
72
+ }
73
+
62
74
// This constructor is intended for unit testing only.
63
75
internal SelectExpandQueryOption ( string select , string expand , ODataQueryContext context )
64
76
{
@@ -125,6 +137,32 @@ public SelectExpandClause SelectExpandClause
125
137
}
126
138
}
127
139
140
+ /// <summary>
141
+ /// Gets or sets the number of levels that a top level $expand=NavigationProperty($levels=max)
142
+ /// will be expanded.
143
+ /// This value will decrease by one with each nesting level in the $expand clause.
144
+ /// For example, with a property value 5, the following query $expand=A($expand=B($expand=C($levels=max)))
145
+ /// will be interpreted as $expand=A($expand=B($expand=C($levels=3))).
146
+ /// If the query gets validated, the <see cref="ODataValidationSettings.MaxExpansionDepth"/> value
147
+ /// must be greater than or equal to this value.
148
+ /// </summary>
149
+ public int LevelsMaxLiteralExpansionDepth
150
+ {
151
+ get
152
+ {
153
+ return _levelsMaxLiteralExpansionDepth ;
154
+ }
155
+ set
156
+ {
157
+ if ( value < 0 )
158
+ {
159
+ throw Error . ArgumentMustBeGreaterThanOrEqualTo ( "LevelsMaxLiteralExpansionDepth" , value , 0 ) ;
160
+ }
161
+
162
+ _levelsMaxLiteralExpansionDepth = value ;
163
+ }
164
+ }
165
+
128
166
/// <summary>
129
167
/// Applies the $select and $expand query options to the given <see cref="IQueryable"/> using the given
130
168
/// <see cref="ODataQuerySettings"/>.
@@ -207,5 +245,155 @@ public void Validate(ODataValidationSettings validationSettings)
207
245
Validator . Validate ( this , validationSettings ) ;
208
246
}
209
247
}
248
+
249
+ internal SelectExpandClause ProcessLevels ( )
250
+ {
251
+ bool levelsEncountered ;
252
+ return ProcessLevels ( SelectExpandClause , LevelsMaxLiteralExpansionDepth , out levelsEncountered ) ;
253
+ }
254
+
255
+ // Process $levels in SelectExpandClause.
256
+ private static SelectExpandClause ProcessLevels (
257
+ SelectExpandClause selectExpandClause ,
258
+ int levelsMaxLiteralExpansionDepth ,
259
+ out bool levelsEncountered )
260
+ {
261
+ levelsEncountered = false ;
262
+
263
+ if ( selectExpandClause == null )
264
+ {
265
+ return null ;
266
+ }
267
+
268
+ // Process $levels in SelectItems of SelectExpandClause.
269
+ IEnumerable < SelectItem > selectItems = ProcessLevels (
270
+ selectExpandClause . SelectedItems ,
271
+ levelsMaxLiteralExpansionDepth ,
272
+ out levelsEncountered ) ;
273
+
274
+ if ( levelsEncountered )
275
+ {
276
+ return new SelectExpandClause ( selectItems , selectExpandClause . AllSelected ) ;
277
+ }
278
+ else
279
+ {
280
+ // Return the original SelectExpandClause if no $levels is found.
281
+ return selectExpandClause ;
282
+ }
283
+ }
284
+
285
+ // Process $levels in SelectedItems.
286
+ private static IEnumerable < SelectItem > ProcessLevels (
287
+ IEnumerable < SelectItem > selectItems ,
288
+ int levelsMaxLiteralExpansionDepth ,
289
+ out bool levelsEncountered )
290
+ {
291
+ levelsEncountered = false ;
292
+ IList < SelectItem > items = new List < SelectItem > ( ) ;
293
+
294
+ foreach ( SelectItem selectItem in selectItems )
295
+ {
296
+ ExpandedNavigationSelectItem item = selectItem as ExpandedNavigationSelectItem ;
297
+
298
+ if ( item == null )
299
+ {
300
+ // There is no $levels in non-ExpandedNavigationSelectItem.
301
+ items . Add ( selectItem ) ;
302
+ }
303
+ else
304
+ {
305
+ bool levelsEncouteredInExpand ;
306
+ // Process $levels in ExpandedNavigationSelectItem.
307
+ ExpandedNavigationSelectItem expandItem = ProcessLevels (
308
+ item ,
309
+ levelsMaxLiteralExpansionDepth ,
310
+ out levelsEncouteredInExpand ) ;
311
+ levelsEncountered = levelsEncountered || levelsEncouteredInExpand ;
312
+
313
+ if ( expandItem != null )
314
+ {
315
+ items . Add ( expandItem ) ;
316
+ }
317
+ }
318
+ }
319
+
320
+ return items ;
321
+ }
322
+
323
+ // Process $levels in ExpandedNavigationSelectItem.
324
+ private static ExpandedNavigationSelectItem ProcessLevels (
325
+ ExpandedNavigationSelectItem expandItem ,
326
+ int levelsMaxLiteralExpansionDepth ,
327
+ out bool levelsEncounteredInExpand )
328
+ {
329
+ // Call ProcessLevels on SelectExpandClause recursively.
330
+ SelectExpandClause selectExpandClause = ProcessLevels (
331
+ expandItem . SelectAndExpand ,
332
+ levelsMaxLiteralExpansionDepth - 1 ,
333
+ out levelsEncounteredInExpand ) ;
334
+
335
+ if ( expandItem . LevelsOption == null )
336
+ {
337
+ if ( levelsEncounteredInExpand )
338
+ {
339
+ return new ExpandedNavigationSelectItem (
340
+ expandItem . PathToNavigationProperty ,
341
+ expandItem . NavigationSource ,
342
+ selectExpandClause ) ;
343
+ }
344
+ else
345
+ {
346
+ // Return the original ExpandedNavigationSelectItem if no $levels is found.
347
+ return expandItem ;
348
+ }
349
+ }
350
+
351
+ // There is $levels in current ExpandedNavigationSelectItem.
352
+ levelsEncounteredInExpand = true ;
353
+ int level = expandItem . LevelsOption . IsMaxLevel ?
354
+ levelsMaxLiteralExpansionDepth :
355
+ ( int ) expandItem . LevelsOption . Level ;
356
+
357
+ if ( level <= 0 )
358
+ {
359
+ // Do not expand if $levels is equal to 0.
360
+ return null ;
361
+ }
362
+
363
+ // Initialize current SelectExpandClause with processed SelectExpandClause.
364
+ SelectExpandClause currentSelectExpandClause = selectExpandClause ;
365
+ ExpandedNavigationSelectItem item = null ;
366
+
367
+ // Construct new ExpandedNavigationSelectItem with recursive expansion.
368
+ while ( level > 0 )
369
+ {
370
+ // Construct a new ExpandedNavigationSelectItem with current SelectExpandClause.
371
+ item = new ExpandedNavigationSelectItem (
372
+ expandItem . PathToNavigationProperty ,
373
+ expandItem . NavigationSource ,
374
+ currentSelectExpandClause ) ;
375
+
376
+ // Update current SelectExpandClause with the new ExpandedNavigationSelectItem.
377
+ if ( selectExpandClause . AllSelected )
378
+ {
379
+ currentSelectExpandClause = new SelectExpandClause (
380
+ new [ ] { item } . Concat ( selectExpandClause . SelectedItems ) ,
381
+ selectExpandClause . AllSelected ) ;
382
+ }
383
+ else
384
+ {
385
+ // PathSelectItem is needed for the expanded item if AllSelected is false.
386
+ PathSelectItem pathSelectItem = new PathSelectItem (
387
+ new ODataSelectPath ( expandItem . PathToNavigationProperty ) ) ;
388
+ currentSelectExpandClause = new SelectExpandClause (
389
+ new SelectItem [ ] { item , pathSelectItem } . Concat ( selectExpandClause . SelectedItems ) ,
390
+ selectExpandClause . AllSelected ) ;
391
+ }
392
+
393
+ level -- ;
394
+ }
395
+
396
+ return item ;
397
+ }
210
398
}
211
399
}
0 commit comments