From 567a5cd3542efc67b747ad52bdd3dc763044cfb8 Mon Sep 17 00:00:00 2001 From: David Obando Date: Wed, 14 May 2014 15:34:27 -0700 Subject: [PATCH] Leaner faster hot paths - fix for workitem #2258 A performance improvement to the model building code. 1) Transformed the LINQ expressions into verbose code. 2) Using the IList interface to iterate with direct accessors to the collections. 3) Inverted the order of the boolean operations in the IF statement to call SequenceEqual() only when necessary. 4) Caching a dictionary of the column mappings so reduce the amount of traversals of the entity set mappings, entity type mappings, mapping fragments, and column mappings. This fixes a performance regression introduced in EF 6.0.2 by changeset 86d31f93dbe29c2df1c8f1cb56ec1c1e75d31941 which fixes workitem #1776. In the repro case we have, this fix made the model building process two orders of magnitude faster than it used to be after the regression was introduced, and twice as fast as EF5. EF5: 304 seconds. EF6 - after regression: 23944 seconds. EF6 - after this fix: 147 seconds. These changes affect functionality that is covered by test FK_name_uniquification_is_not_triggered_on_different_tables. Tests passing 100%. (cherry picked from commit b36023187e8d60c6aa635503c9d03ea00b1ff29e) --- .../Mapping/EntityMappingTransformer.cs | 89 ++++++++++++++++--- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/src/EntityFramework/ModelConfiguration/Configuration/Mapping/EntityMappingTransformer.cs b/src/EntityFramework/ModelConfiguration/Configuration/Mapping/EntityMappingTransformer.cs index c9f82b676a..11b4f93699 100644 --- a/src/EntityFramework/ModelConfiguration/Configuration/Mapping/EntityMappingTransformer.cs +++ b/src/EntityFramework/ModelConfiguration/Configuration/Mapping/EntityMappingTransformer.cs @@ -54,7 +54,7 @@ public static EdmProperty IncludeColumn( DebugCheck.NotNull(table); DebugCheck.NotNull(templateColumn); - var existingColumn = table.Properties.SingleOrDefault(isCompatible); + var existingColumn = table.Properties.FirstOrDefault(isCompatible); if (existingColumn == null) { @@ -499,7 +499,8 @@ var pkPropertyMapping in templateFragment.ColumnMappings.Where(pm => pm.ColumnPr } private static void UpdatePropertyMapping( - DbDatabaseMapping databaseMapping, + EdmModel database, + Dictionary> columnMappingIndex, ColumnMappingBuilder propertyMappingBuilder, EntityType fromTable, EntityType toTable, @@ -507,21 +508,82 @@ private static void UpdatePropertyMapping( { propertyMappingBuilder.ColumnProperty = TableOperations.CopyColumnAndAnyConstraints( - databaseMapping.Database, fromTable, toTable, propertyMappingBuilder.ColumnProperty, GetPropertyPathMatcher(databaseMapping, propertyMappingBuilder), useExisting); + database, fromTable, toTable, propertyMappingBuilder.ColumnProperty, GetPropertyPathMatcher(columnMappingIndex, propertyMappingBuilder), useExisting); propertyMappingBuilder.SyncNullabilityCSSpace(); } - private static Func GetPropertyPathMatcher(DbDatabaseMapping databaseMapping, ColumnMappingBuilder propertyMappingBuilder) + private static Func GetPropertyPathMatcher(Dictionary> columnMappingIndex, ColumnMappingBuilder propertyMappingBuilder) { return c => - databaseMapping.EntityContainerMappings.Single() - .EntitySetMappings.SelectMany(esm => esm.EntityTypeMappings) - .SelectMany(etm => etm.MappingFragments) - .SelectMany(etmf => etmf.ColumnMappings) - .Any( - pm => pm.PropertyPath.SequenceEqual(propertyMappingBuilder.PropertyPath) - && pm.ColumnProperty == c); + { + if (!columnMappingIndex.ContainsKey(c)) return false; + var columnMappingList = columnMappingIndex[c]; + // ReSharper disable once LoopCanBeConvertedToQuery + // ReSharper disable once ForCanBeConvertedToForeach + for (var iter = 0; iter < columnMappingList.Count; ++iter) + { + var columnMapping = columnMappingList[iter]; + if (columnMapping.PropertyPath.PathEqual(propertyMappingBuilder.PropertyPath)) + { + return true; + } + } + return false; + }; + } + + private static bool PathEqual(this IList listA, IList listB) + { + if (listA == null || listB == null) return false; + if (listA.Count != listB.Count) return false; + // ReSharper disable once LoopCanBeConvertedToQuery + for (var iter = 0; iter < listA.Count; ++iter) + { + if (listA[iter] != listB[iter]) return false; + } + return true; + } + + private static Dictionary> GetColumnMappingIndex(DbDatabaseMapping databaseMapping) + { + var columnMappingIndex = new Dictionary>(); + var entitySetMappings = databaseMapping.EntityContainerMappings.Single().EntitySetMappings; + if (entitySetMappings == null) return columnMappingIndex; + var entitySetMappingsList = entitySetMappings.ToList(); + // ReSharper disable ForCanBeConvertedToForeach + for (var entitySetMappingsListIterator = 0; entitySetMappingsListIterator < entitySetMappingsList.Count; ++entitySetMappingsListIterator) + { + var entityTypeMappings = entitySetMappingsList[entitySetMappingsListIterator].EntityTypeMappings as IList; + if (entityTypeMappings == null) continue; + for (var entityTypeMappingsIterator = 0; entityTypeMappingsIterator < entityTypeMappings.Count; ++entityTypeMappingsIterator) + { + var mappingFragments = entityTypeMappings[entityTypeMappingsIterator].MappingFragments as IList; + if (mappingFragments == null) continue; + for (var mappingFragmentsIterator = 0; mappingFragmentsIterator < mappingFragments.Count; ++mappingFragmentsIterator) + { + var columnMappings = mappingFragments[mappingFragmentsIterator].ColumnMappings as IList; + if (columnMappings == null) continue; + // ReSharper disable once LoopCanBeConvertedToQuery + for (var columnMappingsIterator = 0; columnMappingsIterator < columnMappings.Count; ++columnMappingsIterator) + { + var columnMapping = columnMappings[columnMappingsIterator]; + IList columnMappingList = null; + if (columnMappingIndex.ContainsKey(columnMapping.ColumnProperty)) + { + columnMappingList = columnMappingIndex[columnMapping.ColumnProperty]; + } + else + { + columnMappingIndex.Add(columnMapping.ColumnProperty, columnMappingList = new List()); + } + columnMappingList.Add(columnMapping); + } + } + } + } + // ReSharper enable ForCanBeConvertedToForeach + return columnMappingIndex; } public static void UpdatePropertyMappings( @@ -533,8 +595,9 @@ public static void UpdatePropertyMappings( // move the column from the fromTable to the table in fragment if (fromTable != fragment.Table) { + var columnMappingIndex = GetColumnMappingIndex(databaseMapping); fragment.ColumnMappings.Each( - pm => UpdatePropertyMapping(databaseMapping, pm, fromTable, fragment.Table, useExisting)); + pm => UpdatePropertyMapping(databaseMapping.Database, columnMappingIndex, pm, fromTable, fragment.Table, useExisting)); } } @@ -549,7 +612,7 @@ public static void MovePropertyMapping( // move the column from the formTable to the table in fragment if (requiresUpdate && fromFragment.Table != toFragment.Table) { - UpdatePropertyMapping(databaseMapping, propertyMappingBuilder, fromFragment.Table, toFragment.Table, useExisting); + UpdatePropertyMapping(databaseMapping.Database, GetColumnMappingIndex(databaseMapping), propertyMappingBuilder, fromFragment.Table, toFragment.Table, useExisting); } // move the propertyMapping