Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize the performance of InternalConfigurationRootExtensions.GetChildrenImplementation #74736

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(thi
{
using ReferenceCountedProviders? reference = (root as ConfigurationManager)?.GetProvidersReference();
IEnumerable<IConfigurationProvider> providers = reference?.Providers ?? root.Providers;

IEnumerable<IConfigurationSection> children = providers
.Aggregate(Enumerable.Empty<string>(),
(seed, source) => source.GetChildKeys(seed, path))
.SelectMany(p => p.GetChildKeys(Enumerable.Empty<string>(), path))
.OrderBy(key => key, ConfigurationKeyComparer.Instance)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));

if (reference is null)
{
return children;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -971,5 +971,130 @@ public void ProviderWithNullReloadToken()
// Assert
Assert.NotNull(config);
}

[Fact]
public void ConfigurationGetChildrenKeysOrdered()
{
// Arrange
var dic1 = new Dictionary<string, string>()
{
{"Services:2", "Service2"}
};
var dic2 = new Dictionary<string, string>()
{
{"Services:10", "Service10"},
};
var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };

var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.Add(memConfigSrc1);
configurationBuilder.Add(memConfigSrc2);

var config = configurationBuilder.Build();

// Act
var keys = config.GetSection("Services").GetChildren().Select(c => c.Key).ToList();

// Assert
Assert.Equal(2, keys.Count);
Assert.Equal("2", keys[0]);
Assert.Equal("10", keys[1]);
}

[Theory]
[ClassData(typeof(OverrideGetChildKeysConfigurationProviders))]
public void ConfigurationGetChildrenNotOptimizedConfigurationProvider(IConfigurationProvider provider)
{
// Arrange
var dic2 = new Dictionary<string, string>()
{
{"key2", "val"},
};
var src1 = new FixedProviderConfigurationSource(provider);
var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.Add(src1);
configurationBuilder.Add(memConfigSrc2);
var configSorted = configurationBuilder.Build();

// Act
var keysSorted = configSorted.GetChildren().Select(c => c.Key).ToList();

// Assert
Assert.Equal(3, keysSorted.Count);

// The keys should be sorted by the 2nd IConfigurationProvider
// because it inherits from helper class ConfigurationProvider.
Assert.Equal("key1", keysSorted[0]);
Assert.Equal("key2", keysSorted[1]);
Assert.Equal("key3", keysSorted[2]);
}

private class OverrideGetChildKeysConfigurationProviders : TheoryData<IConfigurationProvider>
{
public OverrideGetChildKeysConfigurationProviders()
{
Add(new InheritHelperClassButOverrideMethodGetChildKeysConfigurationProvider());
Add(new NotInheritHelperClassConfigurationProvider());
}

public static IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath)
{
if (parentPath != null)
{
throw new ArgumentException("Not support parentPath");
}

foreach (var key in earlierKeys)
{
yield return key;
}

// For the IConfigurationProvider implementation that not inherit from helper class ConfigurationProvider,
// the returned keys might not be sorted.
yield return "key3";
yield return "key1";
}
}

private class FixedProviderConfigurationSource : IConfigurationSource
{
private readonly IConfigurationProvider provider;
public FixedProviderConfigurationSource(IConfigurationProvider provider)
{
this.provider = provider;
}

public IConfigurationProvider Build(IConfigurationBuilder builder) => provider;
}

private class InheritHelperClassButOverrideMethodGetChildKeysConfigurationProvider : ConfigurationProvider
{
public override IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath)
=> OverrideGetChildKeysConfigurationProviders.GetChildKeys(earlierKeys, parentPath);
}

private class NotInheritHelperClassConfigurationProvider : IConfigurationProvider
{
public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath)
=> OverrideGetChildKeysConfigurationProviders.GetChildKeys(earlierKeys, parentPath);

public Primitives.IChangeToken GetReloadToken() => new ConfigurationReloadToken();

public void Load()
{
}

public void Set(string key, string? value)
{
}

public bool TryGet(string key, out string? value)
{
value = "val";
return true;
}
}
}
}