Skip to content

Commit 880d84b

Browse files
authored
Reduce lookups needed in ConcurrentDictionaries in ContentNavigationServiceBase (#19603)
Reduce lookups needed in ConcurrentDictionaries and sort using List.Sort, make key removal O(1) by using hashsets and avoid duplicates, remove unneeded .ToList() and other minor tweaks
1 parent f9c2b95 commit 880d84b

File tree

10 files changed

+76
-57
lines changed

10 files changed

+76
-57
lines changed

src/Umbraco.Core/Composing/TypeFinderConfig.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ public IEnumerable<string> AssembliesAcceptingLoadExceptions
2525

2626
var s = _settings.AssembliesAcceptingLoadExceptions;
2727
return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
28-
? Array.Empty<string>()
29-
: s.Split(',').Select(x => x.Trim()).ToArray();
28+
? []
29+
: s.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
3030
}
3131
}
3232
}

src/Umbraco.Core/Extensions/StringExtensions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,8 +509,7 @@ public static Guid EncodeAsGuid(this string input)
509509
var convertToHex = input.ConvertToHex();
510510
var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32;
511511
var hex = convertToHex[..hexLength].PadLeft(32, '0');
512-
Guid output = Guid.Empty;
513-
return Guid.TryParse(hex, out output) ? output : Guid.Empty;
512+
return Guid.TryParse(hex, out Guid output) ? output : Guid.Empty;
514513
}
515514

516515
/// <summary>

src/Umbraco.Core/Factories/UserSettingsFactory.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ private PasswordSettingsModel CreatePasswordSettingsModel() =>
3636

3737
private IEnumerable<ConsentLevelModel> CreateConsentLevelModels() =>
3838
Enum.GetValues<TelemetryLevel>()
39-
.ToList()
4039
.Select(level => new ConsentLevelModel
4140
{
4241
Level = level,

src/Umbraco.Core/Logging/LoggingConfiguration.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public LoggingConfiguration(string logDirectory, string logFileNameFormat, strin
5858
public string LogFileNameFormat { get; }
5959

6060
/// <inheritdoc/>
61-
public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries)
62-
.Select(x => x.Trim())
61+
public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
6362
.Select(GetValue)
6463
.ToArray();
6564

src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ public BlockEditorData<TValue, TLayout> Convert(TValue? value)
7171

7272
// this method is only meant to have any effect when migrating block editor values
7373
// from the original format to the new, variant enabled format
74-
private void AmendExpose(TValue value)
75-
=> value.Expose = value.ContentData.Select(cd => new BlockItemVariation(cd.Key, null, null)).ToList();
74+
private static void AmendExpose(TValue value)
75+
=> value.Expose = value.ContentData.ConvertAll(cd => new BlockItemVariation(cd.Key, null, null));
7676

7777
// this method is only meant to have any effect when migrating block editor values
7878
// from the original format to the new, variant enabled format
79-
private bool ConvertOriginalBlockFormat(List<BlockItemData> blockItemDatas)
79+
private static bool ConvertOriginalBlockFormat(List<BlockItemData> blockItemDatas)
8080
{
8181
var converted = false;
8282
foreach (BlockItemData blockItemData in blockItemDatas)

src/Umbraco.Core/Routing/DomainUtilities.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,14 @@ private static bool ContentHasAssignedDomains(IPublishedContent? content, IDomai
256256
// if a culture is specified, then try to get domains for that culture
257257
// (else cultureDomains will be null)
258258
// do NOT specify a default culture, else it would pick those domains
259-
IReadOnlyCollection<DomainAndUri>? cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null);
259+
IReadOnlyList<DomainAndUri>? cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null);
260260
IReadOnlyCollection<DomainAndUri> considerForBaseDomains = domainsAndUris;
261261
if (cultureDomains != null)
262262
{
263263
if (cultureDomains.Count == 1)
264264
{
265265
// only 1, return
266-
return cultureDomains.First();
266+
return cultureDomains[0];
267267
}
268268

269269
// else restrict to those domains, for base lookup
@@ -272,11 +272,11 @@ private static bool ContentHasAssignedDomains(IPublishedContent? content, IDomai
272272

273273
// look for domains that would be the base of the uri
274274
// we need to order so example.com/foo matches before example.com/
275-
IReadOnlyCollection<DomainAndUri> baseDomains = SelectByBase(considerForBaseDomains.OrderByDescending(d => d.Uri.ToString()).ToList(), uri, culture);
275+
List<DomainAndUri> baseDomains = SelectByBase(considerForBaseDomains.OrderByDescending(d => d.Uri.ToString()).ToArray(), uri, culture);
276276
if (baseDomains.Count > 0)
277277
{
278278
// found, return
279-
return baseDomains.First();
279+
return baseDomains[0];
280280
}
281281

282282
// if nothing works, then try to run the filter to select a domain
@@ -296,7 +296,7 @@ private static bool IsBaseOf(DomainAndUri domain, Uri uri)
296296
private static bool MatchesCulture(DomainAndUri domain, string? culture)
297297
=> culture == null || domain.Culture.InvariantEquals(culture);
298298

299-
private static IReadOnlyCollection<DomainAndUri> SelectByBase(IReadOnlyCollection<DomainAndUri> domainsAndUris, Uri uri, string? culture)
299+
private static List<DomainAndUri> SelectByBase(DomainAndUri[] domainsAndUris, Uri uri, string? culture)
300300
{
301301
// look for domains that would be the base of the uri
302302
// ie current is www.example.com/foo/bar, look for domain www.example.com
@@ -314,7 +314,7 @@ private static IReadOnlyCollection<DomainAndUri> SelectByBase(IReadOnlyCollectio
314314
return baseDomains;
315315
}
316316

317-
private static IReadOnlyCollection<DomainAndUri>? SelectByCulture(IReadOnlyCollection<DomainAndUri> domainsAndUris, string? culture, string? defaultCulture)
317+
private static List<DomainAndUri>? SelectByCulture(DomainAndUri[] domainsAndUris, string? culture, string? defaultCulture)
318318
{
319319
// we try our best to match cultures, but may end with a bogus domain
320320
if (culture is not null)
@@ -434,13 +434,18 @@ internal static bool ExistsDomainInPath(IEnumerable<Domain> domains, string path
434434

435435
private static Domain? FindDomainInPath(IEnumerable<Domain>? domains, string path, int? rootNodeId, bool isWildcard)
436436
{
437+
if (domains is null)
438+
{
439+
return null;
440+
}
441+
437442
var stopNodeId = rootNodeId ?? -1;
438443

439444
return path.Split(Constants.CharArrays.Comma)
440445
.Reverse()
441446
.Select(s => int.Parse(s, CultureInfo.InvariantCulture))
442447
.TakeWhile(id => id != stopNodeId)
443-
.Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == isWildcard))
448+
.Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == isWildcard))
444449
.FirstOrDefault(domain => domain is not null);
445450
}
446451

src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ internal abstract class ContentNavigationServiceBase<TContentType, TContentTypeS
1717
private Lazy<Dictionary<string, Guid>> _contentTypeAliasToKeyMap;
1818
private ConcurrentDictionary<Guid, NavigationNode> _navigationStructure = new();
1919
private ConcurrentDictionary<Guid, NavigationNode> _recycleBinNavigationStructure = new();
20-
private IList<Guid> _roots = new List<Guid>();
21-
private IList<Guid> _recycleBinRoots = new List<Guid>();
20+
private HashSet<Guid> _roots = [];
21+
private HashSet<Guid> _recycleBinRoots = [];
2222

2323
protected ContentNavigationServiceBase(ICoreScopeProvider coreScopeProvider, INavigationRepository navigationRepository, TContentTypeService typeService)
2424
{
@@ -321,7 +321,7 @@ protected Task HandleRebuildAsync(int readLock, Guid objectTypeKey, bool trashed
321321
return Task.CompletedTask;
322322
}
323323

324-
private bool TryGetParentKeyFromStructure(ConcurrentDictionary<Guid, NavigationNode> structure, Guid childKey, out Guid? parentKey)
324+
private static bool TryGetParentKeyFromStructure(ConcurrentDictionary<Guid, NavigationNode> structure, Guid childKey, out Guid? parentKey)
325325
{
326326
if (structure.TryGetValue(childKey, out NavigationNode? childNode))
327327
{
@@ -335,25 +335,32 @@ private bool TryGetParentKeyFromStructure(ConcurrentDictionary<Guid, NavigationN
335335
}
336336

337337
private bool TryGetRootKeysFromStructure(
338-
IList<Guid> input,
338+
HashSet<Guid> input,
339339
out IEnumerable<Guid> rootKeys,
340340
Guid? contentTypeKey = null)
341341
{
342-
// Apply contentTypeKey filter
343-
IEnumerable<Guid> filteredKeys = contentTypeKey.HasValue
344-
? input.Where(key => _navigationStructure[key].ContentTypeKey == contentTypeKey.Value)
345-
: input;
342+
var keysWithSortOrder = new List<(Guid Key, int SortOrder)>(input.Count);
343+
foreach (Guid key in input)
344+
{
345+
NavigationNode navigationNode = _navigationStructure[key];
346+
347+
// Apply contentTypeKey filter
348+
if (contentTypeKey.HasValue && navigationNode.ContentTypeKey != contentTypeKey.Value)
349+
{
350+
continue;
351+
}
352+
353+
keysWithSortOrder.Add((key, navigationNode.SortOrder));
354+
}
346355

347-
// TODO can we make this more efficient?
348356
// Sort by SortOrder
349-
rootKeys = filteredKeys
350-
.OrderBy(key => _navigationStructure[key].SortOrder)
351-
.ToList();
357+
keysWithSortOrder.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder));
358+
rootKeys = keysWithSortOrder.ConvertAll(keyWithSortOrder => keyWithSortOrder.Key);
352359

353360
return true;
354361
}
355362

356-
private bool TryGetChildrenKeysFromStructure(
363+
private static bool TryGetChildrenKeysFromStructure(
357364
ConcurrentDictionary<Guid, NavigationNode> structure,
358365
Guid parentKey,
359366
out IEnumerable<Guid> childrenKeys,
@@ -367,12 +374,12 @@ private bool TryGetChildrenKeysFromStructure(
367374
}
368375

369376
// Keep children keys ordered based on their SortOrder
370-
childrenKeys = GetOrderedChildren(parentNode, structure, contentTypeKey).ToList();
377+
childrenKeys = GetOrderedChildren(parentNode, structure, contentTypeKey);
371378

372379
return true;
373380
}
374381

375-
private bool TryGetDescendantsKeysFromStructure(
382+
private static bool TryGetDescendantsKeysFromStructure(
376383
ConcurrentDictionary<Guid, NavigationNode> structure,
377384
Guid parentKey,
378385
out IEnumerable<Guid> descendantsKeys,
@@ -393,7 +400,7 @@ private bool TryGetDescendantsKeysFromStructure(
393400
return true;
394401
}
395402

396-
private bool TryGetAncestorsKeysFromStructure(
403+
private static bool TryGetAncestorsKeysFromStructure(
397404
ConcurrentDictionary<Guid, NavigationNode> structure,
398405
Guid childKey,
399406
out IEnumerable<Guid> ancestorsKeys,
@@ -421,7 +428,7 @@ private bool TryGetAncestorsKeysFromStructure(
421428
return true;
422429
}
423430

424-
private bool TryGetSiblingsKeysFromStructure(
431+
private static bool TryGetSiblingsKeysFromStructure(
425432
ConcurrentDictionary<Guid, NavigationNode> structure,
426433
Guid key,
427434
out IEnumerable<Guid> siblingsKeys,
@@ -463,14 +470,14 @@ private bool TryGetSiblingsKeysFromStructure(
463470
return true;
464471
}
465472

466-
private void GetDescendantsRecursively(
473+
private static void GetDescendantsRecursively(
467474
ConcurrentDictionary<Guid, NavigationNode> structure,
468475
NavigationNode node,
469476
List<Guid> descendants,
470477
Guid? contentTypeKey = null)
471478
{
472479
// Get all children regardless of contentType
473-
var childrenKeys = GetOrderedChildren(node, structure).ToList();
480+
IReadOnlyList<Guid> childrenKeys = GetOrderedChildren(node, structure);
474481
foreach (Guid childKey in childrenKeys)
475482
{
476483
// Apply contentTypeKey filter
@@ -487,7 +494,7 @@ private void GetDescendantsRecursively(
487494
}
488495
}
489496

490-
private bool TryRemoveNodeFromParentInStructure(ConcurrentDictionary<Guid, NavigationNode> structure, Guid key, out NavigationNode? nodeToRemove)
497+
private static bool TryRemoveNodeFromParentInStructure(ConcurrentDictionary<Guid, NavigationNode> structure, Guid key, out NavigationNode? nodeToRemove)
491498
{
492499
if (structure.TryGetValue(key, out nodeToRemove) is false)
493500
{
@@ -507,7 +514,7 @@ private void AddDescendantsToRecycleBinRecursively(NavigationNode node)
507514
{
508515
_recycleBinRoots.Add(node.Key);
509516
_roots.Remove(node.Key);
510-
var childrenKeys = GetOrderedChildren(node, _navigationStructure).ToList();
517+
IReadOnlyList<Guid> childrenKeys = GetOrderedChildren(node, _navigationStructure);
511518

512519
foreach (Guid childKey in childrenKeys)
513520
{
@@ -530,7 +537,7 @@ private void AddDescendantsToRecycleBinRecursively(NavigationNode node)
530537

531538
private void RemoveDescendantsRecursively(NavigationNode node)
532539
{
533-
var childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure).ToList();
540+
IReadOnlyList<Guid> childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure);
534541
foreach (Guid childKey in childrenKeys)
535542
{
536543
if (_recycleBinNavigationStructure.TryGetValue(childKey, out NavigationNode? childNode) is false)
@@ -551,7 +558,7 @@ private void RestoreNodeAndDescendantsRecursively(NavigationNode node)
551558
}
552559

553560
_recycleBinRoots.Remove(node.Key);
554-
var childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure).ToList();
561+
IReadOnlyList<Guid> childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure);
555562

556563
foreach (Guid childKey in childrenKeys)
557564
{
@@ -570,24 +577,35 @@ private void RestoreNodeAndDescendantsRecursively(NavigationNode node)
570577
}
571578
}
572579

573-
private IEnumerable<Guid> GetOrderedChildren(
580+
private static IReadOnlyList<Guid> GetOrderedChildren(
574581
NavigationNode node,
575582
ConcurrentDictionary<Guid, NavigationNode> structure,
576583
Guid? contentTypeKey = null)
577584
{
578-
IEnumerable<Guid> children = node
579-
.Children
580-
.Where(structure.ContainsKey);
585+
if (node.Children.Count < 1)
586+
{
587+
return [];
588+
}
581589

582-
// Apply contentTypeKey filter
583-
if (contentTypeKey.HasValue)
590+
var childrenWithSortOrder = new List<(Guid ChildNodeKey, int SortOrder)>(node.Children.Count);
591+
foreach (Guid childNodeKey in node.Children)
584592
{
585-
children = children.Where(childKey => structure[childKey].ContentTypeKey == contentTypeKey.Value);
593+
if (!structure.TryGetValue(childNodeKey, out NavigationNode? childNode))
594+
{
595+
continue;
596+
}
597+
598+
// Apply contentTypeKey filter
599+
if (contentTypeKey.HasValue && childNode.ContentTypeKey != contentTypeKey.Value)
600+
{
601+
continue;
602+
}
603+
604+
childrenWithSortOrder.Add((childNodeKey, childNode.SortOrder));
586605
}
587606

588-
return children
589-
.OrderBy(childKey => structure[childKey].SortOrder)
590-
.ToList();
607+
childrenWithSortOrder.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder));
608+
return childrenWithSortOrder.ConvertAll(childWithSortOrder => childWithSortOrder.ChildNodeKey);
591609
}
592610

593611
private bool TryGetContentTypeKey(string contentTypeAlias, out Guid? contentTypeKey)
@@ -613,7 +631,7 @@ private bool TryGetContentTypeKey(string contentTypeAlias, out Guid? contentType
613631
return true;
614632
}
615633

616-
private static void BuildNavigationDictionary(ConcurrentDictionary<Guid, NavigationNode> nodesStructure, IList<Guid> roots, IEnumerable<INavigationModel> entities)
634+
private static void BuildNavigationDictionary(ConcurrentDictionary<Guid, NavigationNode> nodesStructure, HashSet<Guid> roots, IEnumerable<INavigationModel> entities)
617635
{
618636
var entityList = entities.ToList();
619637
var idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key);

src/Umbraco.Core/Strings/Css/StylesheetHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ public static IEnumerable<StylesheetRule> ParseRules(string? input)
3939
// Only match first selector when chained together
4040
Styles = string.Join(
4141
Environment.NewLine,
42-
match.Groups["Styles"].Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None)
43-
.Select(x => x.Trim()).ToArray()),
42+
match.Groups["Styles"].Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.TrimEntries)),
4443
});
4544
}
4645
}

src/Umbraco.Core/Strings/Css/StylesheetRule.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ public override string ToString()
3030
// instead of using string interpolation (for increased performance)
3131
foreach (var style in
3232
Styles?.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries) ??
33-
Array.Empty<string>())
33+
[])
3434
{
35-
sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine);
35+
sb.Append('\t').Append(style.StripNewLines().Trim()).Append(';').Append(Environment.NewLine);
3636
}
3737
}
3838

39-
sb.Append("}");
39+
sb.Append('}');
4040

4141
return sb.ToString();
4242
}

src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public RegisterModel Build()
6868
UsernameIsEmail = _usernameIsEmail,
6969
MemberProperties = _lookupProperties
7070
? GetMemberPropertiesViewModel(memberType)
71-
: Enumerable.Empty<MemberPropertyModel>().ToList(),
71+
: [],
7272
AutomaticLogIn = _automaticLogIn
7373
};
7474
return model;

0 commit comments

Comments
 (0)