Hibernate (5 HIGH): addColumn/addReferencedColumn/addIndex ArrayList→LinkedHashSet (19x) FK second-pass LinkedHashSet, orderHierarchy LinkedHashSet MyBatis (1 MEDIUM): sortConstructorMappings indexOf→HashMap (12x) EF Core (2 HIGH + 1 MEDIUM): FindGenerationProperty HashSet (250x), AddPrincipals HashSet (250x), FK discovery HashSet (6x) Diesel (3 MEDIUM): SQLite/MySQL row position()→BTreeMap (51x) SQLAlchemy (2 HIGH): _values_bindparam Set (500x), evaluated_keys Set (500x) Peewee (1 MEDIUM): _SortedFieldList.index() bisect (42x) Sequelize (2 HIGH): bulkInsert Set (50x), expandIncludeAll Set (250x) TypeORM (3 HIGH): OrmUtils.uniq Map (500x), diffColumns Set (125x), updatedColumns Set (100x) Doctrine ORM (1 HIGH + 2 MEDIUM): hydrator discriminator (26x), addSubClass (250x), SqlWalker partial (130x) GORM (1 MEDIUM): sortCallbacks getRIndex→map (194x) SQLite: SqliteTest unit proof 4/4 PASS (101x) Unit tests: all PASS — Hibernate/MyBatis/EfCore/Diesel/SQLAlchemy/Peewee/ Sequelize/TypeORM/Doctrine/GORM Whitepaper: 157 sites, 62 ecosystems; PDF 752K
43 lines
2 KiB
Diff
43 lines
2 KiB
Diff
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<T> GetPrincipals<T>()
|
||
where T : IReadOnlyProperty
|
||
{
|
||
var principals = new List<T> { (T)this };
|
||
- AddPrincipals((T)this, principals);
|
||
+ var visited = new HashSet<T>(ReferenceEqualityComparer.Instance) { (T)this };
|
||
+ AddPrincipals((T)this, principals, visited);
|
||
return principals;
|
||
}
|
||
|
||
- private static void AddPrincipals<T>(T property, List<T> visited)
|
||
+ // efcore-0002 fix: pass a HashSet<T> for O(1) duplicate detection.
|
||
+ // Previously List<T>: 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>(T property, List<T> principals, HashSet<T> 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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|