Skip to content

Commit 46a0d5a

Browse files
authored
Fix Auto-Expand Regression (#2585)
1 parent 256f591 commit 46a0d5a

3 files changed

Lines changed: 116 additions & 29 deletions

File tree

src/Microsoft.AspNet.OData.Shared/Query/AutoSelectExpandHelper.cs

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//------------------------------------------------------------------------------
77

88
using System;
9+
using System.Collections;
910
using System.Collections.Generic;
1011
using System.Diagnostics.Contracts;
1112
using System.Linq;
@@ -38,11 +39,7 @@ public static bool HasAutoSelectProperty(this IEdmModel edmModel, IEdmStructured
3839
throw Error.ArgumentNull(nameof(structuredType));
3940
}
4041

41-
List<IEdmStructuredType> structuredTypes = new List<IEdmStructuredType>();
42-
structuredTypes.Add(structuredType);
43-
structuredTypes.AddRange(edmModel.FindAllDerivedTypes(structuredType));
44-
45-
foreach (IEdmStructuredType edmStructuredType in structuredTypes)
42+
foreach (IEdmStructuredType edmStructuredType in new SelfAndDerivedEnumerator(structuredType, edmModel))
4643
{
4744
// for top type, let's retrieve its properties and the properties from base type of top type if has.
4845
// for derived type, let's retrieve the declared properties.
@@ -92,11 +89,8 @@ private static bool HasAutoExpandProperty(this IEdmModel edmModel, IEdmStructure
9289
}
9390
visited.Add(structuredType);
9491

95-
List<IEdmStructuredType> structuredTypes = new List<IEdmStructuredType>();
96-
structuredTypes.Add(structuredType);
97-
structuredTypes.AddRange(edmModel.FindAllDerivedTypes(structuredType));
9892

99-
foreach (IEdmStructuredType edmStructuredType in structuredTypes)
93+
foreach (IEdmStructuredType edmStructuredType in new SelfAndDerivedEnumerator(structuredType, edmModel))
10094
{
10195
// for top type, let's retrieve its properties and the properties from base type of top type if has.
10296
// for derived type, let's retrieve the declared properties.
@@ -144,7 +138,7 @@ private static bool HasAutoExpandProperty(this IEdmModel edmModel, IEdmStructure
144138
/// <param name="pathProperty">The property from path, it can be null.</param>
145139
/// <param name="querySettings">The query settings.</param>
146140
/// <returns>The auto select paths.</returns>
147-
public static IList<SelectModelPath> GetAutoSelectPaths(this IEdmModel edmModel, IEdmStructuredType structuredType,
141+
public static IEnumerable<SelectModelPath> GetAutoSelectPaths(this IEdmModel edmModel, IEdmStructuredType structuredType,
148142
IEdmProperty pathProperty, ModelBoundQuerySettings querySettings = null)
149143
{
150144
if (edmModel == null)
@@ -157,13 +151,8 @@ public static IList<SelectModelPath> GetAutoSelectPaths(this IEdmModel edmModel,
157151
throw Error.ArgumentNull(nameof(structuredType));
158152
}
159153

160-
List<SelectModelPath> autoSelectProperties = new List<SelectModelPath>();
161-
162-
List<IEdmStructuredType> structuredTypes = new List<IEdmStructuredType>();
163-
structuredTypes.Add(structuredType);
164-
structuredTypes.AddRange(edmModel.FindAllDerivedTypes(structuredType));
165-
166-
foreach (IEdmStructuredType edmStructuredType in structuredTypes)
154+
List<SelectModelPath> autoSelectProperties = null;
155+
foreach (IEdmStructuredType edmStructuredType in new SelfAndDerivedEnumerator(structuredType, edmModel))
167156
{
168157
// for top type, let's retrieve its properties and the properties from base type of top type if has.
169158
// for derived type, let's retrieve the declared properties.
@@ -175,6 +164,11 @@ public static IList<SelectModelPath> GetAutoSelectPaths(this IEdmModel edmModel,
175164
{
176165
if (IsAutoSelect(property, pathProperty, edmStructuredType, edmModel, querySettings))
177166
{
167+
if (autoSelectProperties == null)
168+
{
169+
autoSelectProperties = new List<SelectModelPath>(1);
170+
}
171+
178172
if (edmStructuredType == structuredType)
179173
{
180174
autoSelectProperties.Add(new SelectModelPath(new[] { property }));
@@ -187,7 +181,7 @@ public static IList<SelectModelPath> GetAutoSelectPaths(this IEdmModel edmModel,
187181
}
188182
}
189183

190-
return autoSelectProperties;
184+
return autoSelectProperties ?? Enumerable.Empty<SelectModelPath>();
191185
}
192186

193187
/// <summary>
@@ -199,7 +193,7 @@ public static IList<SelectModelPath> GetAutoSelectPaths(this IEdmModel edmModel,
199193
/// <param name="isSelectPresent">Is $select presented.</param>
200194
/// <param name="querySettings">The query settings.</param>
201195
/// <returns>The auto expand paths.</returns>
202-
public static IList<ExpandModelPath> GetAutoExpandPaths(this IEdmModel edmModel, IEdmStructuredType structuredType,
196+
public static IEnumerable<ExpandModelPath> GetAutoExpandPaths(this IEdmModel edmModel, IEdmStructuredType structuredType,
203197
IEdmProperty property, bool isSelectPresent = false, ModelBoundQuerySettings querySettings = null)
204198
{
205199
if (edmModel == null)
@@ -290,11 +284,7 @@ private static void GetAutoExpandPaths(this IEdmModel edmModel, IEdmStructuredTy
290284
}
291285
visited.Add(structuredType);
292286

293-
List<IEdmStructuredType> structuredTypes = new List<IEdmStructuredType>();
294-
structuredTypes.Add(structuredType);
295-
structuredTypes.AddRange(edmModel.FindAllDerivedTypes(structuredType));
296-
297-
foreach (IEdmStructuredType edmStructuredType in structuredTypes)
287+
foreach (IEdmStructuredType edmStructuredType in new SelfAndDerivedEnumerator(structuredType, edmModel))
298288
{
299289
IEnumerable<IEdmProperty> properties;
300290

@@ -347,5 +337,103 @@ private static void GetAutoExpandPaths(this IEdmModel edmModel, IEdmStructuredTy
347337
}
348338
}
349339
}
340+
341+
/// <summary>
342+
/// This is a helper that allows us to avoid inefficiencies in the previous pattern:
343+
/// var structuredTypes = new List&lt;IEdmStructuredType&gt;();
344+
/// structuredTypes.Add(structuredType);
345+
/// structuredTypes.AddRange(edmModel.FindAllDerivedTypes(structuredType));
346+
///
347+
/// Specifically, the allocation of the list and the resizing driven by the "AddRange" call are
348+
/// avoided by leveraging a struct with a simple state machine for enumerating over a type
349+
/// and its derived types.
350+
/// </summary>
351+
private struct SelfAndDerivedEnumerator : IEnumerator<IEdmStructuredType>
352+
{
353+
private enum Stage : byte
354+
{
355+
Initial,
356+
Self,
357+
Derived,
358+
}
359+
360+
private readonly IEnumerator<IEdmStructuredType> derivedEnumerator;
361+
private readonly IEdmStructuredType structuredType;
362+
363+
private Stage stage;
364+
365+
public SelfAndDerivedEnumerator(IEdmStructuredType structuredType, IEdmModel edmModel)
366+
{
367+
if (structuredType == null)
368+
{
369+
throw new ArgumentNullException(nameof(structuredType));
370+
}
371+
372+
if (edmModel == null)
373+
{
374+
throw new ArgumentNullException(nameof(edmModel));
375+
}
376+
377+
this.stage = Stage.Initial;
378+
this.derivedEnumerator = edmModel.FindAllDerivedTypes(structuredType).GetEnumerator();
379+
this.structuredType = structuredType;
380+
}
381+
382+
public IEdmStructuredType Current
383+
{
384+
get
385+
{
386+
switch (stage)
387+
{
388+
case Stage.Self:
389+
return this.structuredType;
390+
391+
case Stage.Derived:
392+
return this.derivedEnumerator.Current;
393+
394+
default:
395+
throw new InvalidOperationException("Enumeration is an invalid state");
396+
}
397+
}
398+
}
399+
400+
object IEnumerator.Current => this.Current;
401+
402+
public void Dispose()
403+
{
404+
this.derivedEnumerator.Dispose();
405+
}
406+
407+
public SelfAndDerivedEnumerator GetEnumerator()
408+
{
409+
return this;
410+
}
411+
412+
public bool MoveNext()
413+
{
414+
switch (this.stage)
415+
{
416+
case Stage.Initial:
417+
this.stage = Stage.Self;
418+
return true;
419+
420+
case Stage.Self:
421+
this.stage = Stage.Derived;
422+
goto case Stage.Derived;
423+
424+
case Stage.Derived:
425+
return this.derivedEnumerator.MoveNext();
426+
427+
default:
428+
return false;
429+
}
430+
}
431+
432+
public void Reset()
433+
{
434+
this.stage = Stage.Initial;
435+
this.derivedEnumerator.Reset();
436+
}
437+
}
350438
}
351439
}

src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -838,8 +838,7 @@ private string GetAutoSelectRawValue()
838838
var selectRawValue = RawValues.Select;
839839
if (String.IsNullOrEmpty(selectRawValue))
840840
{
841-
IList<SelectModelPath> autoSelectProperties = null;
842-
841+
IEnumerable<SelectModelPath> autoSelectProperties = null;
843842
if (Context.TargetStructuredType != null && Context.TargetProperty != null)
844843
{
845844
autoSelectProperties = Context.Model.GetAutoSelectPaths(Context.TargetStructuredType, Context.TargetProperty);
@@ -874,7 +873,7 @@ private string GetAutoExpandRawValue()
874873
{
875874
var expandRawValue = RawValues.Expand;
876875

877-
IList<ExpandModelPath> autoExpandNavigationProperties = null;
876+
IEnumerable<ExpandModelPath> autoExpandNavigationProperties = null;
878877
if (Context.TargetStructuredType != null && Context.TargetProperty != null)
879878
{
880879
autoExpandNavigationProperties = Context.Model.GetAutoExpandPaths(Context.TargetStructuredType, Context.TargetProperty, !string.IsNullOrEmpty(RawValues.Select));

src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ private void GetAutoSelectExpandItems(
379379
return;
380380
}
381381

382-
IList<SelectModelPath> autoSelectProperties = model.GetAutoSelectPaths(baseEntityType, null, modelBoundQuerySettings);
382+
IEnumerable<SelectModelPath> autoSelectProperties = model.GetAutoSelectPaths(baseEntityType, null, modelBoundQuerySettings);
383383
foreach (var autoSelectProperty in autoSelectProperties)
384384
{
385385
ODataSelectPath odataSelectPath = BuildSelectPath(autoSelectProperty, navigationSource);
@@ -393,7 +393,7 @@ private void GetAutoSelectExpandItems(
393393
return;
394394
}
395395

396-
IList<ExpandModelPath> autoExpandNavigationProperties = model.GetAutoExpandPaths(baseEntityType, null, !isAllSelected, modelBoundQuerySettings);
396+
IEnumerable<ExpandModelPath> autoExpandNavigationProperties = model.GetAutoExpandPaths(baseEntityType, null, !isAllSelected, modelBoundQuerySettings);
397397
foreach (ExpandModelPath itemPath in autoExpandNavigationProperties)
398398
{
399399
string navigationPath = itemPath.NavigationPropertyPath;

0 commit comments

Comments
 (0)