# UNDF: UNDF-2026-000000058 diff --git a/src/EFCore/Metadata/IReadOnlyProperty.cs b/src/EFCore/Metadata/IReadOnlyProperty.cs index xxxxxxx..xxxxxxx 100644 --- a/src/EFCore/Metadata/IReadOnlyProperty.cs +++ b/src/EFCore/Metadata/IReadOnlyProperty.cs @@ -229,19 +229,27 @@ namespace Microsoft.EntityFrameworkCore.Metadata; IReadOnlyList GetPrincipals() where T : IReadOnlyProperty { var principals = new List { (T)this }; - AddPrincipals((T)this, principals); + var visited = new HashSet(ReferenceEqualityComparer.Instance) { (T)this }; + AddPrincipals((T)this, principals, visited); return principals; } - private static void AddPrincipals(T property, List visited) + // efcore-0002 fix: pass a HashSet for O(1) duplicate detection. + // Previously List: visited.Contains() is O(N) per call, invoked inside + // a recursive traversal over foreignKey.Properties × FK chains, giving + // O(P²) cost where P = principal chain length. HashSet reduces to O(P). + private static void AddPrincipals(T property, List principals, HashSet visited) where T : IReadOnlyProperty { foreach (var foreignKey in property.GetContainingForeignKeys()) { for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) { if (ReferenceEquals(property, foreignKey.Properties[propertyIndex])) { var principal = (T)foreignKey.PrincipalKey.Properties[propertyIndex]; - if (!visited.Contains(principal)) + if (visited.Add(principal)) { - visited.Add(principal); - - AddPrincipals(principal, visited); + principals.Add(principal); + AddPrincipals(principal, principals, visited); } } } } }