From d8e9f61507c4be7321051e17ed57794c0c743272 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 24 Oct 2023 16:30:09 -0700 Subject: [PATCH] Use OrderedDictionary or Dictionary instead of SortedDictionary in RuntimeModel (#32124) Fixes #31142 Fixes #19361 --- .../Initialization/InitializationTests.cs | 2 +- .../Internal/SnapshotModelProcessor.cs | 2 +- .../CSharpRuntimeModelCodeGenerator.cs | 122 ++- ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 18 +- .../Extensions/RelationalModelExtensions.cs | 4 +- .../RelationalRuntimeModelConvention.cs | 8 +- .../SequenceUniquificationConvention.cs | 7 +- .../Metadata/Internal/DbFunction.cs | 22 +- .../Metadata/Internal/RelationalModel.cs | 81 +- .../Metadata/Internal/Sequence.cs | 14 +- .../Internal/MigrationsModelDiffer.cs | 31 +- .../Operations/AddCheckConstraintOperation.cs | 1 - src/EFCore/Infrastructure/AnnotatableBase.cs | 10 +- .../Conventions/RuntimeModelConvention.cs | 78 +- src/EFCore/Metadata/Internal/Model.cs | 3 +- src/EFCore/Metadata/Internal/Property.cs | 6 +- .../Metadata/Internal/PropertyListComparer.cs | 1 - .../Metadata/Internal/PropertyNameComparer.cs | 20 +- src/EFCore/Metadata/RuntimeAnnotatableBase.cs | 360 +++++++ src/EFCore/Metadata/RuntimeComplexProperty.cs | 8 +- src/EFCore/Metadata/RuntimeComplexType.cs | 9 +- src/EFCore/Metadata/RuntimeElementType.cs | 2 +- src/EFCore/Metadata/RuntimeEntityType.cs | 147 +-- src/EFCore/Metadata/RuntimeForeignKey.cs | 2 +- src/EFCore/Metadata/RuntimeIndex.cs | 8 +- src/EFCore/Metadata/RuntimeKey.cs | 14 +- src/EFCore/Metadata/RuntimeModel.cs | 120 ++- src/EFCore/Metadata/RuntimeNavigation.cs | 6 +- src/EFCore/Metadata/RuntimePropertyBase.cs | 2 +- src/EFCore/Metadata/RuntimeSkipNavigation.cs | 12 +- src/EFCore/Metadata/RuntimeTrigger.cs | 10 +- src/EFCore/Metadata/RuntimeTypeBase.cs | 98 +- .../RuntimeTypeMappingConfiguration.cs | 2 +- src/EFCore/Storage/ExecutionStrategy.cs | 5 +- src/Shared/HashHelpers.cs | 117 +++ src/Shared/IDictionaryDebugView.cs | 70 ++ src/Shared/OrderedDictionary.KeyCollection.cs | 160 ++++ .../OrderedDictionary.ValueCollection.cs | 174 ++++ src/Shared/OrderedDictionary.cs | 893 ++++++++++++++++++ .../Design/CSharpMigrationsGeneratorTest.cs | 2 +- .../Design/MigrationScaffolderTest.cs | 2 + .../CSharpRuntimeModelCodeGeneratorTest.cs | 430 +++++++-- .../Internal/MigrationsModelDifferTestBase.cs | 2 + .../SqlServerOptionsExtensionTest.cs | 7 +- .../InternalEntryEntrySubscriberTest.cs | 4 +- .../Metadata/Internal/EntityTypeTest.cs | 6 +- 46 files changed, 2662 insertions(+), 440 deletions(-) create mode 100644 src/EFCore/Metadata/RuntimeAnnotatableBase.cs create mode 100644 src/Shared/HashHelpers.cs create mode 100644 src/Shared/IDictionaryDebugView.cs create mode 100644 src/Shared/OrderedDictionary.KeyCollection.cs create mode 100644 src/Shared/OrderedDictionary.ValueCollection.cs create mode 100644 src/Shared/OrderedDictionary.cs diff --git a/benchmark/EFCore.Benchmarks/Initialization/InitializationTests.cs b/benchmark/EFCore.Benchmarks/Initialization/InitializationTests.cs index 67b645fecff..64544580a66 100644 --- a/benchmark/EFCore.Benchmarks/Initialization/InitializationTests.cs +++ b/benchmark/EFCore.Benchmarks/Initialization/InitializationTests.cs @@ -66,6 +66,6 @@ public virtual void BuildModel_AdventureWorks() AdventureWorksContextBase.ConfigureModel(builder); // ReSharper disable once UnusedVariable - var model = builder.Model; + var model = builder.FinalizeModel(); } } diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index a53268548e6..75415561617 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -147,7 +147,7 @@ private static void UpdateSequences(IReadOnlyModel model, string version) .Select(a => new Sequence(model, a.Name)); #pragma warning restore CS0618 // Type or member is obsolete - var sequencesDictionary = new SortedDictionary<(string, string?), ISequence>(); + var sequencesDictionary = new Dictionary<(string, string?), ISequence>(); foreach (var sequence in sequences) { sequencesDictionary[(sequence.Name, sequence.ModelSchema)] = sequence; diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 01cdde9a365..905bafcc527 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -173,9 +173,11 @@ void RunInitialization() } } - model.Customize(); - _instance = model; -}") + model.Customize();") + .Append(" _instance = (") + .Append(className) + .AppendLine(")model.FinalizeModel();") + .AppendLine("}") .AppendLine() .Append("private static ").Append(className).AppendLine(" _instance;") .AppendLine("public static IModel Instance => _instance;") @@ -225,6 +227,32 @@ private string CreateModelBuilder( using (mainBuilder.Indent()) { + AddNamespace(typeof(Guid), namespaces); + mainBuilder + .AppendLine($"private {className}()") + .IncrementIndent() + .Append(": base(skipDetectChanges: ") + .Append(_code.Literal(((IRuntimeModel)model).SkipDetectChanges)) + .Append(", modelId: ") + .Append(_code.Literal(model.ModelId)) + .Append(", entityTypeCount: ") + .Append(_code.Literal(model.GetEntityTypes().Count())); + + var typeConfigurationCount = model.GetTypeMappingConfigurations().Count(); + if (typeConfigurationCount > 0) + { + mainBuilder + .Append(", typeConfigurationCount: ") + .Append(_code.Literal(typeConfigurationCount)); + } + + mainBuilder + .AppendLine(")") + .DecrementIndent() + .AppendLine("{") + .AppendLine("}") + .AppendLine(); + mainBuilder .AppendLine("partial void Initialize()") .AppendLine("{"); @@ -692,6 +720,82 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator .Append(_code.UnknownLiteral(discriminatorValue)); } + var derivedTypesCount = entityType.GetDirectlyDerivedTypes().Count(); + if (derivedTypesCount != 0) + { + mainBuilder.AppendLine(",") + .Append("derivedTypesCount: ") + .Append(_code.Literal(derivedTypesCount)); + } + + mainBuilder.AppendLine(",") + .Append("propertyCount: ") + .Append(_code.Literal(entityType.GetDeclaredProperties().Count())); + + var complexPropertyCount = entityType.GetDeclaredComplexProperties().Count(); + if (complexPropertyCount != 0) + { + mainBuilder.AppendLine(",") + .Append("complexPropertyCount: ") + .Append(_code.Literal(complexPropertyCount)); + } + + var navigationCount = entityType.GetDeclaredNavigations().Count(); + if (navigationCount != 0) + { + mainBuilder.AppendLine(",") + .Append("navigationCount: ") + .Append(_code.Literal(navigationCount)); + } + + var skipNavigationCount = entityType.GetDeclaredSkipNavigations().Count(); + if (skipNavigationCount != 0) + { + mainBuilder.AppendLine(",") + .Append("skipNavigationCount: ") + .Append(_code.Literal(skipNavigationCount)); + } + + var servicePropertyCount = entityType.GetDeclaredServiceProperties().Count(); + if (servicePropertyCount != 0) + { + mainBuilder.AppendLine(",") + .Append("servicePropertyCount: ") + .Append(_code.Literal(servicePropertyCount)); + } + + var foreignKeyCount = entityType.GetDeclaredForeignKeys().Count(); + if (foreignKeyCount != 0) + { + mainBuilder.AppendLine(",") + .Append("foreignKeyCount: ") + .Append(_code.Literal(foreignKeyCount)); + } + + var unnamedIndexCount = entityType.GetDeclaredIndexes().Count(i => i.Name == null); + if (unnamedIndexCount != 0) + { + mainBuilder.AppendLine(",") + .Append("unnamedIndexCount: ") + .Append(_code.Literal(unnamedIndexCount)); + } + + var namedIndexCount = entityType.GetDeclaredIndexes().Count(i => i.Name != null); + if (namedIndexCount != 0) + { + mainBuilder.AppendLine(",") + .Append("namedIndexCount: ") + .Append(_code.Literal(namedIndexCount)); + } + + var keyCount = entityType.GetDeclaredKeys().Count(); + if (keyCount != 0) + { + mainBuilder.AppendLine(",") + .Append("keyCount: ") + .Append(_code.Literal(keyCount)); + } + mainBuilder .AppendLine(");") .AppendLine() @@ -1264,6 +1368,18 @@ private void CreateComplexProperty( .Append(_code.Literal(true)); } + mainBuilder.AppendLine(",") + .Append("propertyCount: ") + .Append(_code.Literal(complexType.GetDeclaredProperties().Count())); + + var complexPropertyCount = complexType.GetDeclaredComplexProperties().Count(); + if (complexPropertyCount != 0) + { + mainBuilder.AppendLine(",") + .Append("complexPropertyCount: ") + .Append(_code.Literal(complexPropertyCount)); + } + mainBuilder .AppendLine(");") .AppendLine() diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 757a4dd166a..92e6893d092 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -66,15 +66,15 @@ public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGenerator if (annotations.TryGetAndRemove( RelationalAnnotationNames.DbFunctions, - out SortedDictionary functions)) + out IReadOnlyDictionary functions)) { - parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); + parameters.Namespaces.Add(typeof(Dictionary<,>).Namespace!); parameters.Namespaces.Add(typeof(BindingFlags).Namespace!); var functionsVariable = Dependencies.CSharpHelper.Identifier("functions", parameters.ScopeVariables, capitalize: false); parameters.MainBuilder - .Append("var ").Append(functionsVariable).AppendLine(" = new SortedDictionary();"); + .Append("var ").Append(functionsVariable).AppendLine(" = new Dictionary();"); - foreach (var function in functions.Values) + foreach (var function in functions.OrderBy(t => t.Key).Select(t => t.Value)) { Create(function, functionsVariable, parameters); } @@ -84,12 +84,12 @@ public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGenerator if (annotations.TryGetAndRemove( RelationalAnnotationNames.Sequences, - out SortedDictionary<(string, string?), ISequence> sequences)) + out IReadOnlyDictionary<(string, string?), ISequence> sequences)) { - parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); + parameters.Namespaces.Add(typeof(Dictionary<,>).Namespace!); var sequencesVariable = Dependencies.CSharpHelper.Identifier("sequences", parameters.ScopeVariables, capitalize: false); var mainBuilder = parameters.MainBuilder; - mainBuilder.Append("var ").Append(sequencesVariable).Append(" = new SortedDictionary<(string, string"); + mainBuilder.Append("var ").Append(sequencesVariable).Append(" = new Dictionary<(string, string"); if (parameters.UseNullableReferenceTypes) { @@ -98,9 +98,9 @@ public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGenerator mainBuilder.AppendLine("), ISequence>();"); - foreach (var sequencePair in sequences) + foreach (var sequence in sequences.OrderBy(t => t.Key).Select(t => t.Value)) { - Create(sequencePair.Value, sequencesVariable, parameters); + Create(sequence, sequencesVariable, parameters); } GenerateSimpleAnnotation(RelationalAnnotationNames.Sequences, sequencesVariable, parameters); diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index 23543aa18f6..6dad8ae906f 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -313,7 +313,7 @@ public static IEnumerable GetSequences(this IReadOnlyModel mo /// The for the method that is mapped to the function. /// The function or if the method is not mapped. public static IDbFunction? FindDbFunction(this IModel model, MethodInfo method) - => (IDbFunction?)((IReadOnlyModel)model).FindDbFunction(method); + => DbFunction.FindDbFunction(model, method); /// /// Finds a function that is mapped to the method represented by the given name. @@ -349,7 +349,7 @@ public static IEnumerable GetSequences(this IReadOnlyModel mo /// The model name of the function. /// The function or if the method is not mapped. public static IDbFunction? FindDbFunction(this IModel model, string name) - => (IDbFunction?)((IReadOnlyModel)model).FindDbFunction(name); + => DbFunction.FindDbFunction(model, name); /// /// Creates an mapped to the given method. diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 72bc9ce7713..ca6c54b58dc 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -62,8 +62,8 @@ protected override void ProcessModelAnnotations( if (annotations.TryGetValue(RelationalAnnotationNames.DbFunctions, out var functions)) { - var runtimeFunctions = new SortedDictionary(StringComparer.Ordinal); - foreach (var (key, dbFunction) in (SortedDictionary)functions!) + var runtimeFunctions = new Dictionary(StringComparer.Ordinal); + foreach (var (key, dbFunction) in (Dictionary)functions!) { var runtimeFunction = Create(dbFunction, runtimeModel); runtimeFunctions[key] = runtimeFunction; @@ -87,8 +87,8 @@ protected override void ProcessModelAnnotations( if (annotations.TryGetValue(RelationalAnnotationNames.Sequences, out var sequences)) { - var runtimeSequences = new SortedDictionary<(string, string?), ISequence>(); - foreach (var (key, value) in (SortedDictionary<(string, string?), ISequence>)sequences!) + var runtimeSequences = new Dictionary<(string, string?), ISequence>(); + foreach (var (key, value) in (Dictionary<(string, string?), ISequence>)sequences!) { var runtimeSequence = Create(value, runtimeModel); runtimeSequences[key] = runtimeSequence; diff --git a/src/EFCore.Relational/Metadata/Conventions/SequenceUniquificationConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SequenceUniquificationConvention.cs index cd76351ef7f..d901df57937 100644 --- a/src/EFCore.Relational/Metadata/Conventions/SequenceUniquificationConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/SequenceUniquificationConvention.cs @@ -44,14 +44,13 @@ public virtual void ProcessModelFinalizing( IConventionContext context) { var model = modelBuilder.Metadata; - var modelSequences = - (SortedDictionary<(string Name, string? Schema), ISequence>?)model[RelationalAnnotationNames.Sequences]; - + var modelSequences = + (IReadOnlyDictionary<(string Name, string? Schema), ISequence>?)model[RelationalAnnotationNames.Sequences]; if (modelSequences != null) { var maxLength = model.GetMaxIdentifierLength(); var toReplace = modelSequences - .Where(s => s.Key.Name.Length > maxLength).ToList(); + .Where(s => s.Key.Name.Length > maxLength).OrderBy(s => s.Key).ToList(); foreach (var ((name, schema), sequence) in toReplace) { diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index 2de7962e182..0eed55bcd60 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -193,8 +193,8 @@ public override bool IsReadOnly /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static IEnumerable GetDbFunctions(IReadOnlyModel model) - => ((SortedDictionary?)model[RelationalAnnotationNames.DbFunctions]) - ?.Values + => ((Dictionary?)model[RelationalAnnotationNames.DbFunctions]) + ?.OrderBy(t => t.Key).Select(t => t.Value) ?? Enumerable.Empty(); /// @@ -203,8 +203,8 @@ public static IEnumerable GetDbFunctions(IReadOnlyModel model) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public static IReadOnlyDbFunction? FindDbFunction(IReadOnlyModel model, MethodInfo methodInfo) - => model[RelationalAnnotationNames.DbFunctions] is SortedDictionary functions + public static IDbFunction? FindDbFunction(IReadOnlyModel model, MethodInfo methodInfo) + => model[RelationalAnnotationNames.DbFunctions] is Dictionary functions && functions.TryGetValue(GetFunctionName(methodInfo), out var dbFunction) ? dbFunction : null; @@ -215,8 +215,8 @@ public static IEnumerable GetDbFunctions(IReadOnlyModel model) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public static IReadOnlyDbFunction? FindDbFunction(IReadOnlyModel model, string name) - => model[RelationalAnnotationNames.DbFunctions] is SortedDictionary functions + public static IDbFunction? FindDbFunction(IReadOnlyModel model, string name) + => model[RelationalAnnotationNames.DbFunctions] is Dictionary functions && functions.TryGetValue(name, out var dbFunction) ? dbFunction : null; @@ -256,9 +256,9 @@ public static DbFunction AddDbFunction( return function; } - private static SortedDictionary GetOrCreateFunctions(IMutableModel model) - => (SortedDictionary)( - model[RelationalAnnotationNames.DbFunctions] ??= new SortedDictionary(StringComparer.Ordinal)); + private static Dictionary GetOrCreateFunctions(IMutableModel model) + => (Dictionary)( + model[RelationalAnnotationNames.DbFunctions] ??= new Dictionary(StringComparer.Ordinal)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -270,7 +270,7 @@ private static SortedDictionary GetOrCreateFunctions(IMutab IMutableModel model, MethodInfo methodInfo) { - if (model[RelationalAnnotationNames.DbFunctions] is SortedDictionary functions) + if (model[RelationalAnnotationNames.DbFunctions] is Dictionary functions) { var name = GetFunctionName(methodInfo); if (functions.TryGetValue(name, out var function)) @@ -296,7 +296,7 @@ private static SortedDictionary GetOrCreateFunctions(IMutab IMutableModel model, string name) { - if (model[RelationalAnnotationNames.DbFunctions] is SortedDictionary functions + if (model[RelationalAnnotationNames.DbFunctions] is Dictionary functions && functions.TryGetValue(name, out var function)) { functions.Remove(name); diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 816cf4b74ba..b7f13f63b9f 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -44,8 +44,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary DefaultTables { get; } - = new(); + public virtual Dictionary DefaultTables { get; } = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -53,8 +52,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary<(string, string?), Table> Tables { get; } - = new(); + public virtual Dictionary<(string, string?), Table> Tables { get; } = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -62,8 +60,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary<(string, string?), View> Views { get; } - = new(); + public virtual Dictionary<(string, string?), View> Views { get; } = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -71,8 +68,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary Queries { get; } - = new(); + public virtual Dictionary Queries { get; } = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -80,7 +76,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary<(string, string?, IReadOnlyList), StoreFunction> Functions { get; } + public virtual Dictionary<(string, string?, IReadOnlyList), StoreFunction> Functions { get; } = new(NamedListComparer.Instance); /// @@ -89,7 +85,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual SortedDictionary<(string, string?), StoreStoredProcedure> StoredProcedures { get; } + public virtual Dictionary<(string, string?), StoreStoredProcedure> StoredProcedures { get; } = new(); /// @@ -175,7 +171,8 @@ public static IRelationalModel Create( AddTvfs(databaseModel); - foreach (var table in databaseModel.Tables.Values) + var tables = ((IRelationalModel)databaseModel).Tables; + foreach (Table table in tables) { PopulateRowInternalForeignKeys(table); PopulateTableConfiguration(table, designTime); @@ -196,23 +193,10 @@ public static IRelationalModel Create( { index.AddAnnotations(relationalAnnotationProvider.For(index, designTime)); } - - if (designTime) - { - foreach (var checkConstraint in table.CheckConstraints.Values) - { - checkConstraint.AddAnnotations(relationalAnnotationProvider.For(checkConstraint, designTime)); - } - } - - foreach (var trigger in table.Triggers.Values) - { - ((AnnotatableBase)trigger).AddAnnotations(relationalAnnotationProvider.For(trigger, designTime)); - } } } - foreach (var table in databaseModel.Tables.Values) + foreach (Table table in tables) { PopulateForeignKeyConstraints(table); @@ -227,7 +211,7 @@ public static IRelationalModel Create( } } - foreach (var view in databaseModel.Views.Values) + foreach (View view in ((IRelationalModel)databaseModel).Views) { PopulateRowInternalForeignKeys(view); @@ -244,7 +228,7 @@ public static IRelationalModel Create( if (relationalAnnotationProvider != null) { - foreach (var query in databaseModel.Queries.Values) + foreach (SqlQuery query in ((IRelationalModel)databaseModel).Queries) { foreach (SqlQueryColumn queryColumn in query.Columns.Values) { @@ -254,7 +238,7 @@ public static IRelationalModel Create( query.AddAnnotations(relationalAnnotationProvider.For(query, designTime)); } - foreach (var function in databaseModel.Functions.Values) + foreach (StoreFunction function in ((IRelationalModel)databaseModel).Functions) { foreach (var parameter in function.Parameters) { @@ -269,7 +253,7 @@ public static IRelationalModel Create( function.AddAnnotations(relationalAnnotationProvider.For(function, designTime)); } - foreach (var storedProcedure in databaseModel.StoredProcedures.Values) + foreach (StoreStoredProcedure storedProcedure in ((IRelationalModel)databaseModel).StoredProcedures) { foreach (StoreStoredProcedureParameter parameter in storedProcedure.Parameters) { @@ -284,11 +268,6 @@ public static IRelationalModel Create( storedProcedure.AddAnnotations(relationalAnnotationProvider.For(storedProcedure, designTime)); } - foreach (var sequence in ((IRelationalModel)databaseModel).Sequences) - { - ((AnnotatableBase)sequence).AddAnnotations(relationalAnnotationProvider.For(sequence, designTime)); - } - databaseModel.AddAnnotations(relationalAnnotationProvider.For(databaseModel, designTime)); } @@ -467,7 +446,8 @@ private static void CreateTableMapping( if (!databaseModel.Tables.TryGetValue((mappedTable.Name, mappedTable.Schema), out var table)) { table = new Table(mappedTable.Name, mappedTable.Schema, databaseModel); - databaseModel.Tables.Add((mappedTable.Name, mappedTable.Schema), table); + databaseModel.Tables.Add( + (mappedTable.Name, mappedTable.Schema), table); } var tableMapping = new TableMapping(typeBase, table, includesDerivedTypes) @@ -650,7 +630,8 @@ private static void CreateViewMapping( if (!databaseModel.Views.TryGetValue((mappedView.Name, mappedView.Schema), out var view)) { view = new View(mappedView.Name, mappedView.Schema, databaseModel); - databaseModel.Views.Add((mappedView.Name, mappedView.Schema), view); + databaseModel.Views.Add( + (mappedView.Name, mappedView.Schema), view); } var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes) @@ -913,17 +894,18 @@ private static FunctionMapping CreateFunctionMapping( return functionMapping; } - private static StoreFunction GetOrCreateStoreFunction(IRuntimeDbFunction dbFunction, RelationalModel model) + private static StoreFunction GetOrCreateStoreFunction(IRuntimeDbFunction dbFunction, RelationalModel databaseModel) { var storeFunction = (StoreFunction?)dbFunction.StoreFunction; if (storeFunction == null) { var parameterTypes = dbFunction.Parameters.Select(p => p.StoreType).ToArray(); - storeFunction = (StoreFunction?)model.FindFunction(dbFunction.Name, dbFunction.Schema, parameterTypes); + storeFunction = (StoreFunction?)databaseModel.FindFunction(dbFunction.Name, dbFunction.Schema, parameterTypes); if (storeFunction == null) { - storeFunction = new StoreFunction(dbFunction, model); - model.Functions.Add((storeFunction.Name, storeFunction.Schema, parameterTypes), storeFunction); + storeFunction = new StoreFunction(dbFunction, databaseModel); + databaseModel.Functions.Add( + (storeFunction.Name, storeFunction.Schema, parameterTypes), storeFunction); } else { @@ -1200,16 +1182,16 @@ private static StoredProcedureMapping CreateStoredProcedureMapping( static StoreStoredProcedure GetOrCreateStoreStoredProcedure( IRuntimeStoredProcedure storedProcedure, - RelationalModel model, + RelationalModel databaseModel, IRelationalTypeMappingSource relationalTypeMappingSource) { var storeStoredProcedure = (StoreStoredProcedure?)storedProcedure.StoreStoredProcedure; if (storeStoredProcedure == null) { - storeStoredProcedure = (StoreStoredProcedure?)model.FindStoredProcedure(storedProcedure.Name, storedProcedure.Schema); + storeStoredProcedure = (StoreStoredProcedure?)databaseModel.FindStoredProcedure(storedProcedure.Name, storedProcedure.Schema); if (storeStoredProcedure == null) { - storeStoredProcedure = new StoreStoredProcedure(storedProcedure.Name, storedProcedure.Schema, model); + storeStoredProcedure = new StoreStoredProcedure(storedProcedure.Name, storedProcedure.Schema, databaseModel); if (storedProcedure.IsRowsAffectedReturned) { var typeMapping = relationalTypeMappingSource.FindMapping(typeof(int))!; @@ -1220,7 +1202,8 @@ static StoreStoredProcedure GetOrCreateStoreStoredProcedure( typeMapping); } - model.StoredProcedures.Add((storeStoredProcedure.Name, storeStoredProcedure.Schema), storeStoredProcedure); + databaseModel.StoredProcedures.Add( + (storeStoredProcedure.Name, storeStoredProcedure.Schema), storeStoredProcedure); } storeStoredProcedure.StoredProcedures.Add(storedProcedure); @@ -2014,30 +1997,30 @@ public virtual DebugView DebugView IEnumerable IRelationalModel.Tables { [DebuggerStepThrough] - get => Tables.Values; + get => Tables.OrderBy(t => t.Key).Select(t => t.Value); } IEnumerable IRelationalModel.Views { [DebuggerStepThrough] - get => Views.Values; + get => Views.OrderBy(v => v.Key).Select(v => v.Value); } IEnumerable IRelationalModel.Functions { [DebuggerStepThrough] - get => Functions.Values; + get => Functions.OrderBy(f => f.Key).Select(t => t.Value); } IEnumerable IRelationalModel.StoredProcedures { [DebuggerStepThrough] - get => StoredProcedures.Values; + get => StoredProcedures.OrderBy(p => p.Key).Select(t => t.Value); } IEnumerable IRelationalModel.Queries { [DebuggerStepThrough] - get => Queries.Values; + get => Queries.OrderBy(q => q.Key).Select(t => t.Value); } } diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs index 8166ba25d3c..eb790f0db45 100644 --- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs +++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs @@ -130,8 +130,8 @@ public Sequence(IReadOnlyModel model, string annotationName) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static IEnumerable GetSequences(IReadOnlyModel model) - => ((SortedDictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]) - ?.Values + => ((Dictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]) + ?.OrderBy(t => t.Key).Select(t => t.Value) ?? Enumerable.Empty(); /// @@ -142,7 +142,7 @@ public static IEnumerable GetSequences(IReadOnlyModel model) /// public static ISequence? FindSequence(IReadOnlyModel model, string name, string? schema) { - var sequences = (SortedDictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; + var sequences = (Dictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; if (sequences == null || !sequences.TryGetValue((name, schema), out var sequence)) { @@ -165,10 +165,10 @@ public static Sequence AddSequence( ConfigurationSource configurationSource) { var sequence = new Sequence(name, schema, model, configurationSource); - var sequences = (SortedDictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; + var sequences = (Dictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; if (sequences == null) { - sequences = new SortedDictionary<(string, string?), ISequence>(); + sequences = new Dictionary<(string, string?), ISequence>(); model[RelationalAnnotationNames.Sequences] = sequences; } @@ -189,7 +189,7 @@ public static Sequence AddSequence( { sequence.EnsureMutable(); - var sequences = (SortedDictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; + var sequences = (Dictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; var tuple = (sequence.Name, sequence.ModelSchema); if (sequences == null || !sequences.ContainsKey(tuple)) @@ -214,7 +214,7 @@ public static Sequence AddSequence( /// public static Sequence? RemoveSequence(IMutableModel model, string name, string? schema) { - var sequences = (SortedDictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; + var sequences = (Dictionary<(string, string?), ISequence>?)model[RelationalAnnotationNames.Sequences]; if (sequences == null || !sequences.TryGetValue((name, schema), out var sequence)) { diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index a14998c9fd4..a1fe408e31e 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Globalization; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update.Internal; @@ -53,11 +54,13 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer public MigrationsModelDiffer( IRelationalTypeMappingSource typeMappingSource, IMigrationsAnnotationProvider migrationsAnnotationProvider, + IRelationalAnnotationProvider relationalAnnotationProvider, IRowIdentityMapFactory rowIdentityMapFactory, CommandBatchPreparerDependencies commandBatchPreparerDependencies) { TypeMappingSource = typeMappingSource; MigrationsAnnotationProvider = migrationsAnnotationProvider; + RelationalAnnotationProvider = relationalAnnotationProvider; RowIdentityMapFactory = rowIdentityMapFactory; CommandBatchPreparerDependencies = commandBatchPreparerDependencies; } @@ -78,6 +81,14 @@ public MigrationsModelDiffer( /// protected virtual IMigrationsAnnotationProvider MigrationsAnnotationProvider { get; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual IRelationalAnnotationProvider RelationalAnnotationProvider { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1571,7 +1582,9 @@ protected virtual IEnumerable Diff( /// protected virtual IEnumerable Add(ICheckConstraint target, DiffContext diffContext) { - yield return AddCheckConstraintOperation.CreateFrom(target); + var operation = AddCheckConstraintOperation.CreateFrom(target); + operation.AddAnnotations(RelationalAnnotationProvider.For(target, designTime: true)); + yield return operation; } /// @@ -1659,8 +1672,8 @@ protected virtual IEnumerable Diff( }; } - var sourceMigrationsAnnotations = source.GetAnnotations(); - var targetMigrationsAnnotations = target.GetAnnotations(); + var sourceMigrationsAnnotations = RelationalAnnotationProvider.For(source, designTime: true); + var targetMigrationsAnnotations = RelationalAnnotationProvider.For(target, designTime: true); if (source.IncrementBy != target.IncrementBy || source.MaxValue != target.MaxValue @@ -1693,7 +1706,7 @@ protected virtual IEnumerable Add(ISequence target, DiffCont StartValue = target.StartValue }; - yield return Initialize(operation, target, target.GetAnnotations()); + yield return Initialize(operation, target, RelationalAnnotationProvider.For(target, designTime: true)); } /// @@ -2023,7 +2036,7 @@ protected virtual void DiffData( var tableMapping = new Dictionary(); var unchangedColumns = new List(); - var overridenColumns = new List(); + var overriddenColumns = new List(); foreach (var targetPair in _targetIdentityMaps) { var (targetTable, targetIdentityMap) = targetPair; @@ -2107,7 +2120,7 @@ protected virtual void DiffData( var recreateRow = false; unchangedColumns.Clear(); - overridenColumns.Clear(); + overriddenColumns.Clear(); var anyColumnsModified = false; foreach (var targetColumnModification in targetRow.ColumnModifications) { @@ -2154,7 +2167,7 @@ protected virtual void DiffData( if (!targetColumnModification.IsWrite) { - overridenColumns.Add(targetColumnModification); + overriddenColumns.Add(targetColumnModification); } else if (targetProperty.GetAfterSaveBehavior() != PropertySaveBehavior.Save) { @@ -2176,9 +2189,9 @@ protected virtual void DiffData( unchangedColumn.IsWrite = false; } - foreach (var overridenColumn in overridenColumns) + foreach (var overriddenColumn in overriddenColumns) { - overridenColumn.IsWrite = true; + overriddenColumn.IsWrite = true; } } else diff --git a/src/EFCore.Relational/Migrations/Operations/AddCheckConstraintOperation.cs b/src/EFCore.Relational/Migrations/Operations/AddCheckConstraintOperation.cs index 7747f10915a..ebe6d57edaf 100644 --- a/src/EFCore.Relational/Migrations/Operations/AddCheckConstraintOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/AddCheckConstraintOperation.cs @@ -51,7 +51,6 @@ public static AddCheckConstraintOperation CreateFrom(ICheckConstraint checkConst Schema = checkConstraint.EntityType.GetSchema(), Table = checkConstraint.EntityType.GetTableName()! }; - operation.AddAnnotations(checkConstraint.GetAnnotations()); return operation; } diff --git a/src/EFCore/Infrastructure/AnnotatableBase.cs b/src/EFCore/Infrastructure/AnnotatableBase.cs index aaf9fca9d20..8762e07d55d 100644 --- a/src/EFCore/Infrastructure/AnnotatableBase.cs +++ b/src/EFCore/Infrastructure/AnnotatableBase.cs @@ -21,7 +21,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure; /// public class AnnotatableBase : IAnnotatable { - private SortedDictionary? _annotations; + private Dictionary? _annotations; private ConcurrentDictionary? _runtimeAnnotations; /// @@ -52,7 +52,7 @@ protected virtual void EnsureMutable() /// Gets all annotations on the current object. /// public virtual IEnumerable GetAnnotations() - => _annotations?.Values ?? Enumerable.Empty(); + => _annotations?.Values.OrderBy(a => a.Name, StringComparer.Ordinal) ?? Enumerable.Empty(); /// /// Adds an annotation to this object. Throws if an annotation with the specified name already exists. @@ -148,7 +148,7 @@ public virtual void SetAnnotation(string name, object? value) { EnsureMutable(); - _annotations ??= new SortedDictionary(StringComparer.Ordinal); + _annotations ??= new Dictionary(StringComparer.Ordinal); _annotations[name] = annotation; return OnAnnotationSet(name, annotation, oldAnnotation); @@ -280,9 +280,7 @@ protected virtual Annotation CreateAnnotation(string name, object? value) /// Gets all runtime annotations on the current object. /// public virtual IEnumerable GetRuntimeAnnotations() - => _runtimeAnnotations == null - ? Enumerable.Empty() - : _runtimeAnnotations.OrderBy(p => p.Key).Select(p => p.Value); + => _runtimeAnnotations?.OrderBy(p => p.Key).Select(p => p.Value) ?? Enumerable.Empty(); /// /// Adds a runtime annotation to this object. Throws if an annotation with the specified name already exists. diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index c2951d842a8..8cba9454a72 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -31,7 +32,7 @@ public RuntimeModelConvention( /// public virtual IModel ProcessModelFinalized(IModel model) - => Create(model); + => Create(model).FinalizeModel(); /// /// Creates an optimized model base on the supplied one. @@ -40,9 +41,11 @@ public virtual IModel ProcessModelFinalized(IModel model) /// An optimized model. protected virtual RuntimeModel Create(IModel model) { - var runtimeModel = new RuntimeModel(); - runtimeModel.ModelId = model.ModelId; - runtimeModel.SetSkipDetectChanges(((IRuntimeModel)model).SkipDetectChanges); + var runtimeModel = new RuntimeModel( + skipDetectChanges: ((IRuntimeModel)model).SkipDetectChanges, + modelId: model.ModelId, + entityTypeCount: model.GetEntityTypes().Count(), + typeConfigurationCount: model.GetTypeMappingConfigurations().Count()); ((IModel)runtimeModel).ModelDependencies = model.ModelDependencies!; var entityTypes = model.GetEntityTypesInHierarchicalOrder(); @@ -195,7 +198,7 @@ private void CreateAnnotations( TTarget target, Action, TSource, TTarget, bool> process) where TSource : IAnnotatable - where TTarget : AnnotatableBase + where TTarget : RuntimeAnnotatableBase { var annotations = source.GetAnnotations().ToDictionary(a => a.Name, a => a.Value); process(this, annotations, source, target, false); @@ -248,7 +251,17 @@ private static RuntimeEntityType Create(IEntityType entityType, RuntimeModel mod entityType.GetChangeTrackingStrategy(), entityType.FindIndexerPropertyInfo(), entityType.IsPropertyBag, - entityType.GetDiscriminatorValue()); + entityType.GetDiscriminatorValue(), + derivedTypesCount: entityType.GetDirectlyDerivedTypes().Count(), + propertyCount: entityType.GetDeclaredProperties().Count(), + complexPropertyCount: entityType.GetDeclaredComplexProperties().Count(), + navigationCount: entityType.GetDeclaredNavigations().Count(), + skipNavigationCount: entityType.GetDeclaredSkipNavigations().Count(), + servicePropertyCount: entityType.GetDeclaredServiceProperties().Count(), + foreignKeyCount: entityType.GetDeclaredForeignKeys().Count(), + unnamedIndexCount: entityType.GetDeclaredIndexes().Count(i => i.Name == null), + namedIndexCount: entityType.GetDeclaredProperties().Count(i => i.Name != null), + keyCount: entityType.GetDeclaredKeys().Count()); private static ParameterBinding Create(ParameterBinding parameterBinding, RuntimeEntityType entityType) => parameterBinding.With( @@ -497,7 +510,7 @@ protected virtual void ProcessServicePropertyAnnotations( } } - private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeEntityType runtimeEntityType) + private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeTypeBase runtimeEntityType) { var runtimeComplexProperty = runtimeEntityType.AddComplexProperty( complexProperty.Name, @@ -511,7 +524,9 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeE complexProperty.IsCollection, complexProperty.ComplexType.GetChangeTrackingStrategy(), complexProperty.ComplexType.FindIndexerPropertyInfo(), - complexProperty.ComplexType.IsPropertyBag); + complexProperty.ComplexType.IsPropertyBag, + propertyCount: complexProperty.ComplexType.GetDeclaredProperties().Count(), + complexPropertyCount: complexProperty.ComplexType.GetDeclaredComplexProperties().Count()); var complexType = complexProperty.ComplexType; var runtimeComplexType = runtimeComplexProperty.ComplexType; @@ -544,53 +559,6 @@ private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeE return runtimeComplexProperty; } - private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeComplexType runtimeComplexType) - { - var runtimeComplexProperty = runtimeComplexType.AddComplexProperty( - complexProperty.Name, - complexProperty.ClrType, - complexProperty.ComplexType.Name, - complexProperty.ComplexType.ClrType, - complexProperty.PropertyInfo, - complexProperty.FieldInfo, - complexProperty.GetPropertyAccessMode(), - complexProperty.IsNullable, - complexProperty.IsCollection, - complexProperty.ComplexType.GetChangeTrackingStrategy(), - complexProperty.ComplexType.FindIndexerPropertyInfo(), - complexProperty.ComplexType.IsPropertyBag); - - var complexType = complexProperty.ComplexType; - var newRuntimeComplexType = runtimeComplexProperty.ComplexType; - - foreach (var property in complexType.GetProperties()) - { - var runtimeProperty = Create(property, newRuntimeComplexType); - CreateAnnotations( - property, runtimeProperty, static (convention, annotations, source, target, runtime) => - convention.ProcessPropertyAnnotations(annotations, source, target, runtime)); - - var elementType = property.GetElementType(); - if (elementType != null) - { - var runtimeElementType = Create(runtimeProperty, elementType, property.IsPrimitiveCollection); - CreateAnnotations( - elementType, runtimeElementType, static (convention, annotations, source, target, runtime) => - convention.ProcessElementTypeAnnotations(annotations, source, target, runtime)); - } - } - - foreach (var property in complexType.GetComplexProperties()) - { - var runtimeProperty = Create(property, newRuntimeComplexType); - CreateAnnotations( - property, runtimeProperty, static (convention, annotations, source, target, runtime) => - convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); - } - - return runtimeComplexProperty; - } - /// /// Updates the property annotations that will be set on the read-only object. /// diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 1fd1e381464..dfc86b1bad6 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -459,7 +459,7 @@ public virtual IEnumerable FindEntityTypes(Type type) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Guid ModelId { get; } = Guid.NewGuid(); + public virtual Guid ModelId { get; set; } = Guid.NewGuid(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1094,7 +1094,6 @@ public virtual IModel FinalizeModel() ConventionDispatcher.AssertNoScope(); var finalizedModel = (IModel)ConventionDispatcher.OnModelFinalizing(Builder).Metadata; - if (finalizedModel is Model model) { finalizedModel = model.MakeReadonly(); diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 35b3cfe445c..d87af29aa5e 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -1323,7 +1323,7 @@ public virtual bool IsKey() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetContainingKeys() - => Keys ?? Enumerable.Empty(); + => Keys?.OrderBy(k => k.Properties, PropertyListComparer.Instance) ?? Enumerable.Empty(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1349,7 +1349,7 @@ public virtual bool IsForeignKey() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetContainingForeignKeys() - => ForeignKeys ?? Enumerable.Empty(); + => ForeignKeys?.OrderBy(fk => fk.Properties, PropertyListComparer.Instance) ?? Enumerable.Empty(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1375,7 +1375,7 @@ public virtual bool IsIndex() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetContainingIndexes() - => Indexes ?? Enumerable.Empty(); + => Indexes?.OrderBy(i => i.Properties, PropertyListComparer.Instance) ?? Enumerable.Empty(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/PropertyListComparer.cs b/src/EFCore/Metadata/Internal/PropertyListComparer.cs index c8866315551..f8a91b27d8a 100644 --- a/src/EFCore/Metadata/Internal/PropertyListComparer.cs +++ b/src/EFCore/Metadata/Internal/PropertyListComparer.cs @@ -49,7 +49,6 @@ public int Compare(IReadOnlyList? x, IReadOnlyList -public sealed class PropertyNameComparer : IComparer +public sealed class PropertyNameComparer : IComparer, IEqualityComparer { private readonly IReadOnlyEntityType? _entityType; @@ -76,4 +76,22 @@ public int Compare(string? x, string? y) ? -1 : 1; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public bool Equals(string? x, string? y) + => StringComparer.Ordinal.Equals(x, y); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public int GetHashCode(string obj) + => StringComparer.Ordinal.GetHashCode(obj); } diff --git a/src/EFCore/Metadata/RuntimeAnnotatableBase.cs b/src/EFCore/Metadata/RuntimeAnnotatableBase.cs new file mode 100644 index 00000000000..260f4cfc02e --- /dev/null +++ b/src/EFCore/Metadata/RuntimeAnnotatableBase.cs @@ -0,0 +1,360 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Concurrent; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Infrastructure; + +/// +/// +/// Base class for types that support reading and writing annotations. +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +public class RuntimeAnnotatableBase : IAnnotatable +{ + private readonly Dictionary _annotations = new(StringComparer.Ordinal); + private ConcurrentDictionary? _runtimeAnnotations; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public RuntimeAnnotatableBase() + { + } + + /// + /// Adds an annotation to this object. Throws if an annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The value to be stored in the annotation. + /// The newly added annotation. + public virtual Annotation AddAnnotation(string name, object? value) + { + Check.NotEmpty(name, nameof(name)); + + var annotation = CreateAnnotation(name, value); + + if (FindAnnotation(name) != null) + { + throw new InvalidOperationException(CoreStrings.DuplicateAnnotation(name, ToString())); + } + + SetAnnotation(name, annotation); + + return annotation; + } + + /// + /// Adds annotations to this object overriding any existing annotations with the same name. + /// + /// The annotations to be added. + public virtual void AddAnnotations(IEnumerable annotations) + { + foreach (var annotation in annotations) + { + SetAnnotation(annotation.Name, (annotation as Annotation) ?? CreateAnnotation(annotation.Name, annotation.Value)); + } + } + + /// + /// Adds annotations to this object overriding any existing annotations with the same name. + /// + /// The annotations to be added. + public virtual void AddAnnotations(IReadOnlyDictionary annotations) + { + foreach (var annotation in annotations) + { + SetAnnotation(annotation.Key, CreateAnnotation(annotation.Key, annotation.Value)); + } + } + + /// + /// Sets the annotation stored under the given key. Overwrites the existing annotation if an + /// annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The value to be stored in the annotation. + public virtual void SetAnnotation(string name, object? value) + { + var oldAnnotation = FindAnnotation(name); + if (oldAnnotation != null + && Equals(oldAnnotation.Value, value)) + { + return; + } + + SetAnnotation(name, CreateAnnotation(name, value)); + } + + private Annotation? SetAnnotation( + string name, + Annotation annotation) + { + _annotations[name] = annotation; + + return annotation; + } + + /// + /// Gets the annotation with the given name, returning if it does not exist. + /// + /// The key of the annotation to find. + /// + /// The existing annotation if an annotation with the specified name already exists. Otherwise, . + /// + public virtual Annotation? FindAnnotation(string name) + { + Check.NotEmpty(name, nameof(name)); + + return _annotations.TryGetValue(name, out var annotation) + ? annotation + : null; + } + + /// + /// Gets the annotation with the given name, throwing if it does not exist. + /// + /// The key of the annotation to find. + /// The annotation with the specified name. + public virtual Annotation GetAnnotation(string annotationName) + { + Check.NotEmpty(annotationName, nameof(annotationName)); + + var annotation = FindAnnotation(annotationName); + if (annotation == null) + { + throw new InvalidOperationException(CoreStrings.AnnotationNotFound(annotationName, ToString())); + } + + return annotation; + } + + /// + /// Gets the value annotation with the given name, returning if it does not exist. + /// + /// The key of the annotation to find. + /// + /// The value of the existing annotation if an annotation with the specified name already exists. + /// Otherwise, . + /// + public virtual object? this[string name] => FindAnnotation(name)?.Value; + + /// + /// Creates a new annotation. + /// + /// The key of the annotation. + /// The value to be stored in the annotation. + /// The newly created annotation. + protected virtual Annotation CreateAnnotation(string name, object? value) + => new(name, value); + + /// + /// Gets all runtime annotations on the current object. + /// + public virtual IEnumerable GetRuntimeAnnotations() + => _runtimeAnnotations?.OrderBy(p => p.Key, StringComparer.Ordinal).Select(p => p.Value) + ?? Enumerable.Empty(); + + /// + /// Adds a runtime annotation to this object. Throws if an annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The value to be stored in the annotation. + /// The newly added annotation. + public virtual Annotation AddRuntimeAnnotation(string name, object? value) + { + Check.NotEmpty(name, nameof(name)); + + var annotation = CreateRuntimeAnnotation(name, value); + + return AddRuntimeAnnotation(name, annotation); + } + + /// + /// Adds a runtime annotation to this object. Throws if an annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The annotation to be added. + /// The added annotation. + protected virtual Annotation AddRuntimeAnnotation(string name, Annotation annotation) + => GetOrCreateRuntimeAnnotations().TryAdd(name, annotation) + ? annotation + : throw new InvalidOperationException(CoreStrings.DuplicateAnnotation(name, ToString())); + + /// + /// Adds runtime annotations to this object. + /// + /// The annotations to be added. + public virtual void AddRuntimeAnnotations(IEnumerable annotations) + { + foreach (var annotation in annotations) + { + AddRuntimeAnnotation(annotation.Name, annotation); + } + } + + /// + /// Adds runtime annotations to this object. + /// + /// The annotations to be added. + public virtual void AddRuntimeAnnotations(IReadOnlyDictionary annotations) + { + foreach (var annotation in annotations) + { + AddRuntimeAnnotation(annotation.Key, CreateRuntimeAnnotation(annotation.Key, annotation.Value)); + } + } + + /// + /// Sets the runtime annotation stored under the given key. Overwrites the existing annotation if an + /// annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The value to be stored in the annotation. + public virtual Annotation SetRuntimeAnnotation(string name, object? value) + => GetOrCreateRuntimeAnnotations().AddOrUpdate( + name, + static (n, a) => a.Annotatable.CreateRuntimeAnnotation(n, a.Value), + static (n, oldAnnotation, a) => + !Equals(oldAnnotation.Value, a.Value) ? a.Annotatable.CreateRuntimeAnnotation(n, a.Value) : oldAnnotation, + (Value: value, Annotatable: this)); + + /// + /// Sets the runtime annotation stored under the given key. Overwrites the existing annotation if an + /// annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The annotation to be set. + /// The annotation being replaced. + /// The annotation that was set. + protected virtual Annotation SetRuntimeAnnotation( + string name, + Annotation annotation, + Annotation? oldAnnotation) + { + GetOrCreateRuntimeAnnotations()[name] = annotation; + + return annotation; + } + + /// + /// Gets the value of the runtime annotation with the given name, adding it if one does not exist. + /// + /// The name of the annotation. + /// The factory used to create the value if the annotation doesn't exist. + /// An argument for the factory method. + /// + /// The value of the existing runtime annotation if an annotation with the specified name already exists. + /// Otherwise a newly created value. + /// + public virtual TValue GetOrAddRuntimeAnnotationValue( + string name, + Func valueFactory, + TArg? factoryArgument) + => (TValue)GetOrCreateRuntimeAnnotations().GetOrAdd( + name, + static (n, t) => t.Annotatable.CreateRuntimeAnnotation(n, t.CreateValue(t.Argument)), + (CreateValue: valueFactory, Argument: factoryArgument, Annotatable: this)).Value!; + + /// + /// Gets the runtime annotation with the given name, returning if it does not exist. + /// + /// The key of the annotation to find. + /// + /// The existing annotation if an annotation with the specified name already exists. Otherwise, . + /// + public virtual Annotation? FindRuntimeAnnotation(string name) + { + Check.NotEmpty(name, nameof(name)); + + return _runtimeAnnotations == null + ? null + : _runtimeAnnotations.TryGetValue(name, out var annotation) + ? annotation + : null; + } + + /// + /// Removes the given runtime annotation from this object. + /// + /// The annotation to remove. + /// The annotation that was removed. + public virtual Annotation? RemoveRuntimeAnnotation(string name) + { + Check.NotNull(name, nameof(name)); + + if (_runtimeAnnotations == null) + { + return null; + } + + _runtimeAnnotations.Remove(name, out var annotation); + + return annotation; + } + + /// + /// Creates a new runtime annotation. + /// + /// The key of the annotation. + /// The value to be stored in the annotation. + /// The newly created annotation. + protected virtual Annotation CreateRuntimeAnnotation(string name, object? value) + => new(name, value); + + private ConcurrentDictionary GetOrCreateRuntimeAnnotations() + => NonCapturingLazyInitializer.EnsureInitialized( + ref _runtimeAnnotations, (object?)null, static _ => new ConcurrentDictionary()); + + /// + object? IReadOnlyAnnotatable.this[string name] + { + [DebuggerStepThrough] + get => this[name]; + } + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyAnnotatable.GetAnnotations() + => _annotations.Values.OrderBy(a => a.Name, StringComparer.Ordinal); + + /// + [DebuggerStepThrough] + IAnnotation? IReadOnlyAnnotatable.FindAnnotation(string name) + => FindAnnotation(name); + + /// + IEnumerable IAnnotatable.GetRuntimeAnnotations() + => GetRuntimeAnnotations(); + + /// + IAnnotation? IAnnotatable.FindRuntimeAnnotation(string name) + => FindRuntimeAnnotation(name); + + /// + IAnnotation IAnnotatable.AddRuntimeAnnotation(string name, object? value) + => AddRuntimeAnnotation(name, value); + + /// + IAnnotation? IAnnotatable.RemoveRuntimeAnnotation(string name) + => RemoveRuntimeAnnotation(name); + + /// + [DebuggerStepThrough] + IAnnotation IAnnotatable.SetRuntimeAnnotation(string name, object? value) + => SetRuntimeAnnotation(name, value); +} diff --git a/src/EFCore/Metadata/RuntimeComplexProperty.cs b/src/EFCore/Metadata/RuntimeComplexProperty.cs index 90b477d87c0..e8b95bbeafb 100644 --- a/src/EFCore/Metadata/RuntimeComplexProperty.cs +++ b/src/EFCore/Metadata/RuntimeComplexProperty.cs @@ -36,7 +36,9 @@ public RuntimeComplexProperty( bool collection, ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, - bool propertyBag) + bool propertyBag, + int propertyCount, + int complexPropertyCount) : base(name, propertyInfo, fieldInfo, propertyAccessMode) { DeclaringType = declaringType; @@ -44,7 +46,9 @@ public RuntimeComplexProperty( _isNullable = nullable; _isCollection = collection; ComplexType = new RuntimeComplexType( - targetTypeName, targetType, this, changeTrackingStrategy, indexerPropertyInfo, propertyBag); + targetTypeName, targetType, this, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + propertyCount: propertyCount, + complexPropertyCount: complexPropertyCount); } /// diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs index b3e0608c4d8..a46e7f3d3de 100644 --- a/src/EFCore/Metadata/RuntimeComplexType.cs +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -33,8 +33,13 @@ public RuntimeComplexType( RuntimeComplexProperty complexProperty, ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, - bool propertyBag) - : base(name, type, complexProperty.DeclaringType.Model, null, changeTrackingStrategy, indexerPropertyInfo, propertyBag) + bool propertyBag, + int propertyCount, + int complexPropertyCount) + : base(name, type, complexProperty.DeclaringType.Model, null, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + derivedTypesCount: 0, + propertyCount: propertyCount, + complexPropertyCount: complexPropertyCount) { ComplexProperty = complexProperty; ContainingEntityType = complexProperty.DeclaringType switch diff --git a/src/EFCore/Metadata/RuntimeElementType.cs b/src/EFCore/Metadata/RuntimeElementType.cs index 80ba1155301..74a78f2d229 100644 --- a/src/EFCore/Metadata/RuntimeElementType.cs +++ b/src/EFCore/Metadata/RuntimeElementType.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public class RuntimeElementType : AnnotatableBase, IElementType +public class RuntimeElementType : RuntimeAnnotatableBase, IElementType { private readonly bool _isNullable; private readonly ValueConverter? _valueConverter; diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index be62a254d74..be821c357fc 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -17,36 +17,19 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// public class RuntimeEntityType : RuntimeTypeBase, IRuntimeEntityType { - private readonly List _foreignKeys = new(); - - private readonly SortedDictionary _navigations - = new(StringComparer.Ordinal); - - private readonly SortedDictionary _skipNavigations - = new(StringComparer.Ordinal); - - private readonly SortedDictionary _serviceProperties - = new(StringComparer.Ordinal); - - private readonly SortedDictionary, RuntimeIndex> _unnamedIndexes - = new(PropertyListComparer.Instance); - - private readonly SortedDictionary _namedIndexes - = new(StringComparer.Ordinal); - - private readonly SortedDictionary, RuntimeKey> _keys - = new(PropertyListComparer.Instance); - - private readonly SortedDictionary _triggers - = new(StringComparer.Ordinal); - - private RuntimeKey? _primaryKey; + private readonly List _foreignKeys; + private readonly OrderedDictionary _navigations; + private OrderedDictionary? _skipNavigations; + private OrderedDictionary? _serviceProperties; + private readonly OrderedDictionary, RuntimeIndex> _unnamedIndexes; + private OrderedDictionary? _namedIndexes; + private readonly OrderedDictionary, RuntimeKey> _keys; + private OrderedDictionary? _triggers; + private readonly object? _discriminatorValue; private readonly bool _hasSharedClrType; - + private RuntimeKey? _primaryKey; private InstantiationBinding? _constructorBinding; private InstantiationBinding? _serviceOnlyConstructorBinding; - private readonly object? _discriminatorValue; - private bool _hasServiceProperties; // Warning: Never access these fields directly as access needs to be thread-safe private PropertyCounts? _counts; @@ -79,13 +62,47 @@ public RuntimeEntityType( ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, bool propertyBag, - object? discriminatorValue) - : base(name, type, model, baseType, changeTrackingStrategy, indexerPropertyInfo, propertyBag) + object? discriminatorValue, + int derivedTypesCount, + int propertyCount, + int complexPropertyCount, + int foreignKeyCount, + int navigationCount, + int skipNavigationPropertyCount, + int servicePropertyCount, + int unnamedIndexCount, + int namedIndexCount, + int keyCount, + int triggerPropertyCount) + : base(name, type, model, baseType, changeTrackingStrategy, indexerPropertyInfo, propertyBag, + derivedTypesCount: derivedTypesCount, + propertyCount: propertyCount, + complexPropertyCount: complexPropertyCount) { _hasSharedClrType = sharedClrType; SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, discriminatorProperty); _discriminatorValue = discriminatorValue; + _foreignKeys = new(foreignKeyCount); + _navigations = new(navigationCount, StringComparer.Ordinal); + if (skipNavigationPropertyCount > 0) + { + _skipNavigations = new(skipNavigationPropertyCount, StringComparer.Ordinal); + } + if (servicePropertyCount > 0) + { + _serviceProperties = new(servicePropertyCount, StringComparer.Ordinal); + } + _unnamedIndexes = new(unnamedIndexCount, PropertyListComparer.Instance); + if(namedIndexCount > 0) + { + _namedIndexes = new(namedIndexCount, StringComparer.Ordinal); + } + _keys = new(keyCount, PropertyListComparer.Instance); + if(triggerPropertyCount > 0) + { + _triggers = new(triggerPropertyCount, StringComparer.Ordinal); + } } private new RuntimeEntityType? BaseType @@ -109,16 +126,10 @@ public virtual void SetPrimaryKey(RuntimeKey key) { foreach (var property in key.Properties) { - Properties.Remove(property.Name); property.PrimaryKey = key; } _primaryKey = key; - - foreach (var property in key.Properties) - { - Properties.Add(property.Name, property); - } } /// @@ -258,7 +269,7 @@ private IEnumerable FindForeignKeys(IReadOnlyList GetDerivedForeignKeys() - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? Enumerable.Empty() : GetDerivedTypes().Cast().SelectMany(et => et._foreignKeys); @@ -342,7 +353,7 @@ public virtual RuntimeNavigation AddNavigation( var navigation = new RuntimeNavigation( name, clrType, propertyInfo, fieldInfo, foreignKey, propertyAccessMode, eagerLoaded, lazyLoadingEnabled); - _navigations.Add(name, navigation); + _navigations.Insert(name, navigation); foreignKey.AddNavigation(navigation, onDependent); @@ -374,7 +385,7 @@ private IEnumerable FindDerivedNavigations(string name) { Check.NotNull(name, nameof(name)); - return DirectlyDerivedTypes.Count == 0 + return !HasDirectlyDerivedTypes ? Enumerable.Empty() : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredNavigation(name)).Where(n => n != null); @@ -385,7 +396,7 @@ private IEnumerable FindDerivedNavigations(string name) /// /// Type navigations. public virtual IEnumerable FindNavigationsInHierarchy(string name) - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? ToEnumerable(FindNavigation(name)) : ToEnumerable(FindNavigation(name)).Concat(FindDerivedNavigations(name)); @@ -433,6 +444,7 @@ public virtual RuntimeSkipNavigation AddSkipNavigation( eagerLoaded, lazyLoadingEnabled); + _skipNavigations ??= new(StringComparer.Ordinal); _skipNavigations.Add(name, skipNavigation); return skipNavigation; @@ -450,30 +462,30 @@ public virtual RuntimeSkipNavigation AddSkipNavigation( => FindSkipNavigation(memberInfo.GetSimpleMemberName()); private RuntimeSkipNavigation? FindDeclaredSkipNavigation(string name) - => _skipNavigations.TryGetValue(name, out var navigation) + => _skipNavigations != null && _skipNavigations.TryGetValue(name, out var navigation) ? navigation : null; private IEnumerable GetDeclaredSkipNavigations() - => _skipNavigations.Values; + => _skipNavigations?.Values ?? Enumerable.Empty(); private IEnumerable GetDerivedSkipNavigations() - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? Enumerable.Empty() : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredSkipNavigations()); private IEnumerable GetSkipNavigations() => BaseType != null - ? _skipNavigations.Count == 0 + ? _skipNavigations == null ? BaseType.GetSkipNavigations() : BaseType.GetSkipNavigations().Concat(_skipNavigations.Values) - : _skipNavigations.Values; + : GetDeclaredSkipNavigations(); private IEnumerable FindDerivedSkipNavigations(string name) { Check.NotNull(name, nameof(name)); - return DirectlyDerivedTypes.Count == 0 + return !HasDirectlyDerivedTypes ? Enumerable.Empty() : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredSkipNavigation(name)).Where(n => n != null); @@ -484,7 +496,7 @@ private IEnumerable FindDerivedSkipNavigations(string nam /// /// Type skip navigations. public virtual IEnumerable FindSkipNavigationsInHierarchy(string name) - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? ToEnumerable(FindSkipNavigation(name)) : ToEnumerable(FindSkipNavigation(name)).Concat(FindDerivedSkipNavigations(name)); @@ -503,7 +515,7 @@ public virtual RuntimeIndex AddIndex( var index = new RuntimeIndex(properties, this, name, unique); if (name != null) { - _namedIndexes.Add(name, index); + (_namedIndexes ??= new(StringComparer.Ordinal)).Add(name, index); } else { @@ -544,23 +556,23 @@ public virtual RuntimeIndex AddIndex( /// The name of the index. /// The index, or if none is found. public virtual RuntimeIndex? FindIndex(string name) - => _namedIndexes.TryGetValue(name, out var index) + => _namedIndexes != null && _namedIndexes.TryGetValue(name, out var index) ? index : BaseType?.FindIndex(name); private IEnumerable GetDeclaredIndexes() - => _namedIndexes.Count == 0 + => _namedIndexes == null ? _unnamedIndexes.Values : _unnamedIndexes.Values.Concat(_namedIndexes.Values); private IEnumerable GetDerivedIndexes() - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? Enumerable.Empty() : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredIndexes()); private IEnumerable GetIndexes() => BaseType != null - ? _namedIndexes.Count == 0 && _unnamedIndexes.Count == 0 + ? _namedIndexes == null ? BaseType.GetIndexes() : BaseType.GetIndexes().Concat(GetDeclaredIndexes()) : GetDeclaredIndexes(); @@ -589,8 +601,7 @@ public virtual RuntimeServiceProperty AddServiceProperty( this, propertyAccessMode); - _serviceProperties[serviceProperty.Name] = serviceProperty; - _hasServiceProperties = true; + (_serviceProperties ??= new(StringComparer.Ordinal))[serviceProperty.Name] = serviceProperty; return serviceProperty; } @@ -608,25 +619,25 @@ public virtual RuntimeServiceProperty AddServiceProperty( => FindDeclaredServiceProperty(name) ?? BaseType?.FindServiceProperty(name); private RuntimeServiceProperty? FindDeclaredServiceProperty(string name) - => _serviceProperties.TryGetValue(name, out var property) + => _serviceProperties != null && _serviceProperties.TryGetValue(name, out var property) ? property : null; private bool HasServiceProperties() - => _hasServiceProperties || BaseType != null && BaseType.HasServiceProperties(); + => _serviceProperties != null || BaseType != null && BaseType.HasServiceProperties(); private IEnumerable GetServiceProperties() => BaseType != null - ? _hasServiceProperties + ? _serviceProperties != null ? BaseType.GetServiceProperties().Concat(_serviceProperties.Values) : BaseType.GetServiceProperties() - : _serviceProperties.Values; + : GetDeclaredServiceProperties(); private IEnumerable GetDeclaredServiceProperties() - => _serviceProperties.Values; + => _serviceProperties?.Values ?? Enumerable.Empty(); private IEnumerable GetDerivedServiceProperties() - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? Enumerable.Empty() : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredServiceProperties()); @@ -640,7 +651,7 @@ private IEnumerable FindDerivedServiceProperties(string { Check.NotNull(propertyName, nameof(propertyName)); - return DirectlyDerivedTypes.Count == 0 + return !HasDirectlyDerivedTypes ? Enumerable.Empty() : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredServiceProperty(propertyName)) @@ -652,7 +663,7 @@ private IEnumerable FindDerivedServiceProperties(string /// /// Type service properties. public virtual IEnumerable FindServicePropertiesInHierarchy(string propertyName) - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? ToEnumerable(FindServiceProperty(propertyName)) : ToEnumerable(FindServiceProperty(propertyName)).Concat(FindDerivedServiceProperties(propertyName)); @@ -718,7 +729,7 @@ public virtual RuntimeTrigger AddTrigger(string modelName) { var trigger = new RuntimeTrigger(this, modelName); - _triggers.Add(modelName, trigger); + (_triggers ??= new(StringComparer.Ordinal)).Add(modelName, trigger); return trigger; } @@ -732,13 +743,13 @@ public virtual RuntimeTrigger AddTrigger(string modelName) { Check.NotEmpty(modelName, nameof(modelName)); - return _triggers.TryGetValue(modelName, out var trigger) + return _triggers != null && _triggers.TryGetValue(modelName, out var trigger) ? trigger : null; } private IEnumerable GetDeclaredTriggers() - => _triggers.Values; + => _triggers?.Values ?? Enumerable.Empty(); private IEnumerable GetTriggers() => BaseType != null @@ -762,13 +773,13 @@ public override InstantiationBinding? ConstructorBinding { get => !base.ClrType.IsAbstract ? NonCapturingLazyInitializer.EnsureInitialized( - ref _constructorBinding, this, (Action)(entityType => + ref _constructorBinding, this, entityType => { ((IModel)entityType.Model).GetModelDependencies().ConstructorBindingFactory.GetBindings( entityType, out entityType._constructorBinding, out entityType._serviceOnlyConstructorBinding); - })) + }) : _constructorBinding; [DebuggerStepThrough] @@ -943,7 +954,7 @@ IEnumerable IReadOnlyEntityType.GetDerivedTypes() /// IEnumerable IReadOnlyEntityType.GetDerivedTypesInclusive() - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? new[] { this } : new[] { this }.Concat(GetDerivedTypes()); @@ -1106,7 +1117,7 @@ IEnumerable IEntityType.GetDeclaredNavigations() /// [DebuggerStepThrough] IEnumerable IReadOnlyEntityType.GetDerivedNavigations() - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? Enumerable.Empty() : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredNavigations()); diff --git a/src/EFCore/Metadata/RuntimeForeignKey.cs b/src/EFCore/Metadata/RuntimeForeignKey.cs index 16b4daf6154..c9fbea555c9 100644 --- a/src/EFCore/Metadata/RuntimeForeignKey.cs +++ b/src/EFCore/Metadata/RuntimeForeignKey.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public class RuntimeForeignKey : AnnotatableBase, IRuntimeForeignKey +public class RuntimeForeignKey : RuntimeAnnotatableBase, IRuntimeForeignKey { private readonly DeleteBehavior _deleteBehavior; private readonly bool _isUnique; diff --git a/src/EFCore/Metadata/RuntimeIndex.cs b/src/EFCore/Metadata/RuntimeIndex.cs index c5ecc2c41e3..f0631916ad5 100644 --- a/src/EFCore/Metadata/RuntimeIndex.cs +++ b/src/EFCore/Metadata/RuntimeIndex.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public class RuntimeIndex : AnnotatableBase, IIndex +public class RuntimeIndex : RuntimeAnnotatableBase, IIndex { private readonly bool _isUnique; @@ -122,9 +122,5 @@ bool IReadOnlyIndex.IsUnique [DebuggerStepThrough] IDependentKeyValueFactory IIndex.GetNullableValueFactory() => (IDependentKeyValueFactory)NonCapturingLazyInitializer.EnsureInitialized( - ref _nullableValueFactory, this, static index => - { - index.EnsureReadOnly(); - return new CompositeValueFactory(index.Properties); - }); + ref _nullableValueFactory, this, static index => new CompositeValueFactory(index.Properties)); } diff --git a/src/EFCore/Metadata/RuntimeKey.cs b/src/EFCore/Metadata/RuntimeKey.cs index 3de663baef6..88547106e7c 100644 --- a/src/EFCore/Metadata/RuntimeKey.cs +++ b/src/EFCore/Metadata/RuntimeKey.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public class RuntimeKey : AnnotatableBase, IRuntimeKey +public class RuntimeKey : RuntimeAnnotatableBase, IRuntimeKey { // Warning: Never access these fields directly as access needs to be thread-safe private Func? _identityMapFactory; @@ -144,18 +144,10 @@ IPrincipalKeyValueFactory IKey.GetPrincipalKeyValueFactory() .GetDeclaredMethod(nameof(CreatePrincipalKeyValueFactory))!; private IPrincipalKeyValueFactory CreatePrincipalKeyValueFactory() - where TKey : notnull - { - EnsureReadOnly(); - return new KeyValueFactoryFactory().Create(this); - } + where TKey : notnull => new KeyValueFactoryFactory().Create(this); /// Func IRuntimeKey.GetIdentityMapFactory() => NonCapturingLazyInitializer.EnsureInitialized( - ref _identityMapFactory, this, static key => - { - key.EnsureReadOnly(); - return new IdentityMapFactoryFactory().Create(key); - }); + ref _identityMapFactory, this, static key => new IdentityMapFactoryFactory().Create(key)); } diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index b6e7e261211..ae84df6a368 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -29,23 +29,69 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// examples. /// /// -public class RuntimeModel : AnnotatableBase, IRuntimeModel +public class RuntimeModel : RuntimeAnnotatableBase, IRuntimeModel { - private readonly SortedDictionary _entityTypes = new(StringComparer.Ordinal); - private readonly Dictionary> _sharedTypes = new(); - private readonly Dictionary _typeConfigurations = new(); private bool _skipDetectChanges; + private Guid _modelId; + private readonly Dictionary _entityTypes; + private readonly Dictionary> _sharedTypes = new(); + private readonly Dictionary _typeConfigurations; private readonly ConcurrentDictionary _indexerPropertyInfoMap = new(); private readonly ConcurrentDictionary _clrTypeNameMap = new(); private readonly ConcurrentDictionary _adHocEntityTypes = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + [Obsolete("Use a constructor with parameters")] + public RuntimeModel() + : base() + { + _entityTypes = new(StringComparer.Ordinal); + _typeConfigurations = new(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public RuntimeModel( + bool skipDetectChanges, + Guid modelId, + int entityTypeCount, + int typeConfigurationCount = 0) + { + _skipDetectChanges = skipDetectChanges; + _modelId = modelId; + _entityTypes = new(entityTypeCount, StringComparer.Ordinal); + _typeConfigurations = new(typeConfigurationCount); + } + /// /// Sets a value indicating whether should be called. /// + [Obsolete("This is set in the constructor now")] public virtual void SetSkipDetectChanges(bool skipDetectChanges) => _skipDetectChanges = skipDetectChanges; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + [Obsolete("This is set in the constructor now")] + public virtual Guid ModelId { get => _modelId; set => _modelId = value; } + /// /// Adds an entity type with a defining navigation to the model. /// @@ -61,6 +107,17 @@ public virtual void SetSkipDetectChanges(bool skipDetectChanges) /// and a method that can be used to determine whether a given indexer property contains a value. /// /// The discriminator value for this entity type. + /// The expected number of directly derived entity types. + /// The expected number of declared properties for this entity type. + /// The expected number of declared complex properties for this entity type. + /// The expected number of declared navigations for this entity type. + /// The expected number of declared skip navigation for this entity type. + /// The expected number of declared service properties for this entity type. + /// The expected number of declared foreign keys for this entity type. + /// The expected number of declared unnamed indexes for this entity type. + /// The expected number of declared named indexes for this entity type. + /// The expected number of declared keys for this entity type. + /// The expected number of declared triggers for this entity type. /// The new entity type. public virtual RuntimeEntityType AddEntityType( string name, @@ -71,7 +128,18 @@ public virtual RuntimeEntityType AddEntityType( ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, PropertyInfo? indexerPropertyInfo = null, bool propertyBag = false, - object? discriminatorValue = null) + object? discriminatorValue = null, + int derivedTypesCount = 0, + int propertyCount = 0, + int complexPropertyCount = 0, + int navigationCount = 0, + int skipNavigationCount = 0, + int servicePropertyCount = 0, + int foreignKeyCount = 0, + int unnamedIndexCount = 0, + int namedIndexCount = 0, + int keyCount = 0, + int triggerPropertyCount = 0) { var entityType = new RuntimeEntityType( name, @@ -83,7 +151,18 @@ public virtual RuntimeEntityType AddEntityType( changeTrackingStrategy, indexerPropertyInfo, propertyBag, - discriminatorValue); + discriminatorValue, + derivedTypesCount: derivedTypesCount, + propertyCount: propertyCount, + complexPropertyCount: complexPropertyCount, + foreignKeyCount: foreignKeyCount, + navigationCount: navigationCount, + skipNavigationPropertyCount: skipNavigationCount, + servicePropertyCount: servicePropertyCount, + unnamedIndexCount: unnamedIndexCount, + namedIndexCount: namedIndexCount, + keyCount: keyCount, + triggerPropertyCount: triggerPropertyCount); if (sharedClrType) { @@ -93,7 +172,7 @@ public virtual RuntimeEntityType AddEntityType( } else { - var types = new SortedSet(TypeBaseNameComparer.Instance) { entityType }; + var types = new List { entityType }; _sharedTypes.Add(type, types); } } @@ -164,19 +243,10 @@ private IEnumerable FindEntityTypes(Type type) : new[] { entityType }; return _sharedTypes.TryGetValue(type, out var sharedTypes) - ? result.Concat(sharedTypes) + ? result.Concat(sharedTypes.OrderBy(n => n.Name, StringComparer.Ordinal)) : result; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [EntityFrameworkInternal] - public virtual Guid ModelId { get; set; } - /// /// Adds configuration for a scalar type. /// @@ -219,6 +289,16 @@ private string GetDisplayName(Type type) private PropertyInfo? FindIndexerPropertyInfo([DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type) => _indexerPropertyInfoMap.GetOrAdd(type, type.FindIndexerProperty()); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public virtual IModel FinalizeModel() + => this; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -312,12 +392,12 @@ bool IModel.IsIndexerMethod(MethodInfo methodInfo) /// [DebuggerStepThrough] IEnumerable IReadOnlyModel.GetEntityTypes() - => _entityTypes.Values; + => _entityTypes.Values.OrderBy(e => e.Name, StringComparer.Ordinal); /// [DebuggerStepThrough] IEnumerable IModel.GetEntityTypes() - => _entityTypes.Values; + => _entityTypes.Values.OrderBy(e => e.Name, StringComparer.Ordinal); /// [DebuggerStepThrough] @@ -346,5 +426,5 @@ IEnumerable IModel.GetTypeMappingConfigurations() /// Guid IReadOnlyModel.ModelId - => ModelId; + => _modelId; } diff --git a/src/EFCore/Metadata/RuntimeNavigation.cs b/src/EFCore/Metadata/RuntimeNavigation.cs index 9b77cd250da..06c23de8a7c 100644 --- a/src/EFCore/Metadata/RuntimeNavigation.cs +++ b/src/EFCore/Metadata/RuntimeNavigation.cs @@ -111,9 +111,5 @@ IReadOnlyForeignKey IReadOnlyNavigation.ForeignKey ref _collectionAccessor, ref _collectionAccessorInitialized, this, - static navigation => - { - navigation.EnsureReadOnly(); - return new ClrCollectionAccessorFactory().Create(navigation); - }); + static navigation => new ClrCollectionAccessorFactory().Create(navigation)); } diff --git a/src/EFCore/Metadata/RuntimePropertyBase.cs b/src/EFCore/Metadata/RuntimePropertyBase.cs index fb322307576..1f6f9825f4a 100644 --- a/src/EFCore/Metadata/RuntimePropertyBase.cs +++ b/src/EFCore/Metadata/RuntimePropertyBase.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public abstract class RuntimePropertyBase : AnnotatableBase, IRuntimePropertyBase +public abstract class RuntimePropertyBase : RuntimeAnnotatableBase, IRuntimePropertyBase { private readonly PropertyInfo? _propertyInfo; private readonly FieldInfo? _fieldInfo; diff --git a/src/EFCore/Metadata/RuntimeSkipNavigation.cs b/src/EFCore/Metadata/RuntimeSkipNavigation.cs index 8fb83e55a52..3747cb8a226 100644 --- a/src/EFCore/Metadata/RuntimeSkipNavigation.cs +++ b/src/EFCore/Metadata/RuntimeSkipNavigation.cs @@ -170,18 +170,10 @@ bool IReadOnlyNavigationBase.IsCollection ref _collectionAccessor, ref _collectionAccessorInitialized, this, - static navigation => - { - navigation.EnsureReadOnly(); - return new ClrCollectionAccessorFactory().Create(navigation); - }); + static navigation => new ClrCollectionAccessorFactory().Create(navigation)); /// ICollectionLoader IRuntimeSkipNavigation.GetManyToManyLoader() => NonCapturingLazyInitializer.EnsureInitialized( - ref _manyToManyLoader, this, static navigation => - { - navigation.EnsureReadOnly(); - return new ManyToManyLoaderFactory().Create(navigation); - }); + ref _manyToManyLoader, this, static navigation => new ManyToManyLoaderFactory().Create(navigation)); } diff --git a/src/EFCore/Metadata/RuntimeTrigger.cs b/src/EFCore/Metadata/RuntimeTrigger.cs index 6632b5e14fc..c5d8fd1472d 100644 --- a/src/EFCore/Metadata/RuntimeTrigger.cs +++ b/src/EFCore/Metadata/RuntimeTrigger.cs @@ -6,13 +6,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// Represents a database trigger on a table. /// -public class RuntimeTrigger : AnnotatableBase, ITrigger +public class RuntimeTrigger : RuntimeAnnotatableBase, ITrigger { /// - /// Initializes a new instance of the class. + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - /// The entity type. - /// The name in the model. + [EntityFrameworkInternal] public RuntimeTrigger( RuntimeEntityType entityType, string modelName) diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index c54de0fc11f..4a06204e51c 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -14,15 +14,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public abstract class RuntimeTypeBase : AnnotatableBase, IRuntimeTypeBase +public abstract class RuntimeTypeBase : RuntimeAnnotatableBase, IRuntimeTypeBase { private RuntimeModel _model; private readonly RuntimeTypeBase? _baseType; - private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); - private readonly SortedDictionary _properties; - - private readonly SortedDictionary _complexProperties = new(StringComparer.Ordinal); - + private SortedSet? _directlyDerivedTypes; + private readonly OrderedDictionary _properties; + private OrderedDictionary? _complexProperties; private readonly PropertyInfo? _indexerPropertyInfo; private readonly bool _isPropertyBag; private readonly ChangeTrackingStrategy _changeTrackingStrategy; @@ -46,7 +44,10 @@ protected RuntimeTypeBase( RuntimeTypeBase? baseType, ChangeTrackingStrategy changeTrackingStrategy, PropertyInfo? indexerPropertyInfo, - bool propertyBag) + bool propertyBag, + int derivedTypesCount, + int propertyCount, + int complexPropertyCount) { Name = name; ClrType = type; @@ -54,13 +55,17 @@ protected RuntimeTypeBase( if (baseType != null) { _baseType = baseType; - baseType._directlyDerivedTypes.Add(this); + (baseType._directlyDerivedTypes ??= new(TypeBaseNameComparer.Instance)).Add(this); } _changeTrackingStrategy = changeTrackingStrategy; _indexerPropertyInfo = indexerPropertyInfo; _isPropertyBag = propertyBag; - _properties = new SortedDictionary(new PropertyNameComparer(this)); + _properties = new OrderedDictionary(propertyCount, new PropertyNameComparer(this)); + if (complexPropertyCount > 0) + { + _complexProperties = new(complexPropertyCount, StringComparer.Ordinal); + } } /// @@ -88,8 +93,20 @@ public virtual RuntimeTypeBase? BaseType /// Gets all types in the model that directly derive from this type. /// /// The derived types. - public virtual SortedSet DirectlyDerivedTypes - => _directlyDerivedTypes; + [EntityFrameworkInternal] + protected virtual IEnumerable DirectlyDerivedTypes + => _directlyDerivedTypes ?? Enumerable.Empty(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual bool HasDirectlyDerivedTypes + => _directlyDerivedTypes != null + && _directlyDerivedTypes.Count > 0; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -110,7 +127,7 @@ public virtual IEnumerable GetDerivedTypes() protected virtual IEnumerable GetDerivedTypes() where T : RuntimeTypeBase { - if (DirectlyDerivedTypes.Count == 0) + if (!HasDirectlyDerivedTypes) { return Enumerable.Empty(); } @@ -234,7 +251,7 @@ public virtual RuntimeProperty AddProperty( => _properties.TryGetValue(name, out var property) ? property : null; - + /// /// Gets all scalar properties declared on this type. /// @@ -248,7 +265,7 @@ public virtual IEnumerable GetDeclaredProperties() => _properties.Values; private IEnumerable GetDerivedProperties() - => _directlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? Enumerable.Empty() : GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties()); @@ -282,7 +299,7 @@ private IEnumerable GetDerivedProperties() /// /// Type properties. public virtual IEnumerable FindPropertiesInHierarchy(string propertyName) - => _directlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? ToEnumerable(FindProperty(propertyName)) : ToEnumerable(FindProperty(propertyName)).Concat(FindDerivedProperties(propertyName)); @@ -290,7 +307,7 @@ private IEnumerable FindDerivedProperties(string propertyName) { Check.NotNull(propertyName, nameof(propertyName)); - return _directlyDerivedTypes.Count == 0 + return !HasDirectlyDerivedTypes ? Enumerable.Empty() : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredProperty(propertyName)).Where(p => p != null); @@ -308,16 +325,6 @@ protected virtual IEnumerable GetProperties() ? _baseType.GetProperties().Concat(_properties.Values) : _properties.Values; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [EntityFrameworkInternal] - protected virtual SortedDictionary Properties - => _properties; - /// [DebuggerStepThrough] public virtual PropertyInfo? FindIndexerPropertyInfo() @@ -351,6 +358,8 @@ protected virtual SortedDictionary Properties /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. /// + /// The expected number of declared properties for this complex type. + /// The expected number of declared complex properties for this complex type. /// The newly created property. public virtual RuntimeComplexProperty AddComplexProperty( string name, @@ -364,7 +373,9 @@ public virtual RuntimeComplexProperty AddComplexProperty( bool collection = false, ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, PropertyInfo? indexerPropertyInfo = null, - bool propertyBag = false) + bool propertyBag = false, + int propertyCount = 0, + int complexPropertyCount = 0) { var property = new RuntimeComplexProperty( name, @@ -379,8 +390,11 @@ public virtual RuntimeComplexProperty AddComplexProperty( collection, changeTrackingStrategy, indexerPropertyInfo, - propertyBag); + propertyBag, + propertyCount: propertyCount, + complexPropertyCount: complexPropertyCount); + _complexProperties ??= new(StringComparer.Ordinal); _complexProperties.Add(property.Name, property); return property; @@ -395,7 +409,8 @@ public virtual RuntimeComplexProperty AddComplexProperty( => FindDeclaredComplexProperty(name) ?? BaseType?.FindComplexProperty(name); private RuntimeComplexProperty? FindDeclaredComplexProperty(string name) - => _complexProperties.TryGetValue(name, out var property) + => _complexProperties != null + && _complexProperties.TryGetValue(name, out var property) ? property : null; @@ -404,10 +419,10 @@ public virtual RuntimeComplexProperty AddComplexProperty( /// /// Declared complex properties. public virtual IEnumerable GetDeclaredComplexProperties() - => _complexProperties.Values; + => _complexProperties?.Values ?? Enumerable.Empty(); private IEnumerable GetDerivedComplexProperties() - => DirectlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? Enumerable.Empty() : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredComplexProperties()); @@ -420,15 +435,17 @@ private IEnumerable GetDerivedComplexProperties() /// The complex properties defined on this type. public virtual IEnumerable GetComplexProperties() => BaseType != null - ? BaseType.GetComplexProperties().Concat(_complexProperties.Values) - : _complexProperties.Values; + ? _complexProperties != null + ? BaseType.GetComplexProperties().Concat(_complexProperties.Values) + : BaseType.GetComplexProperties() + : GetDeclaredComplexProperties(); /// /// Gets the complex properties with the given name on this type, base types or derived types. /// /// Type complex properties. public virtual IEnumerable FindComplexPropertiesInHierarchy(string propertyName) - => _directlyDerivedTypes.Count == 0 + => !HasDirectlyDerivedTypes ? ToEnumerable(FindComplexProperty(propertyName)) : ToEnumerable(FindComplexProperty(propertyName)).Concat(FindDerivedComplexProperties(propertyName)); @@ -436,7 +453,7 @@ private IEnumerable FindDerivedComplexProperties(string { Check.NotNull(propertyName, nameof(propertyName)); - return _directlyDerivedTypes.Count == 0 + return !HasDirectlyDerivedTypes ? Enumerable.Empty() : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); @@ -565,6 +582,17 @@ static IEnumerable Create(RuntimeTypeBase type) /// public abstract IEnumerable GetSnapshottableMembers(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public virtual void FinalizeType() + { + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/RuntimeTypeMappingConfiguration.cs b/src/EFCore/Metadata/RuntimeTypeMappingConfiguration.cs index 7b0998891f4..090d4699264 100644 --- a/src/EFCore/Metadata/RuntimeTypeMappingConfiguration.cs +++ b/src/EFCore/Metadata/RuntimeTypeMappingConfiguration.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public sealed class RuntimeTypeMappingConfiguration : AnnotatableBase, ITypeMappingConfiguration +public sealed class RuntimeTypeMappingConfiguration : RuntimeAnnotatableBase, ITypeMappingConfiguration { private readonly ValueConverter? _valueConverter; diff --git a/src/EFCore/Storage/ExecutionStrategy.cs b/src/EFCore/Storage/ExecutionStrategy.cs index 0aed4c64ed4..4653bc05b6c 100644 --- a/src/EFCore/Storage/ExecutionStrategy.cs +++ b/src/EFCore/Storage/ExecutionStrategy.cs @@ -87,10 +87,7 @@ protected ExecutionStrategy( int maxRetryCount, TimeSpan maxRetryDelay) { - if (maxRetryCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(maxRetryCount)); - } + ArgumentOutOfRangeException.ThrowIfNegative(maxRetryCount); if (maxRetryDelay.TotalMilliseconds < 0.0) { diff --git a/src/Shared/HashHelpers.cs b/src/Shared/HashHelpers.cs new file mode 100644 index 00000000000..f9055d6211a --- /dev/null +++ b/src/Shared/HashHelpers.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Utilities +{ + internal static partial class HashHelpers + { + internal static int PowerOf2(int v) + { + if ((v & (v - 1)) == 0) + { + return v; + } + + var i = 2; + while (i < v) + { + i <<= 1; + } + + return i; + } + + // must never be written to + internal static readonly int[] SizeOneIntArray = new int[1]; + + public const int HashCollisionThreshold = 100; + + // This is the maximum prime smaller than Array.MaxArrayLength + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + public static readonly int[] primes = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; + + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + var limit = (int)Math.Sqrt(candidate); + for (var divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + { + return false; + } + } + return true; + } + return candidate == 2; + } + + public static int GetPrime(int min) + { + if (min < 0) + { + throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); + } + + for (var i = 0; i < primes.Length; i++) + { + var prime = primes[i]; + if (prime >= min) + { + return prime; + } + } + + //outside of our predefined table. + //compute the hard way. + for (var i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + { + return i; + } + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + var newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + } +} diff --git a/src/Shared/IDictionaryDebugView.cs b/src/Shared/IDictionaryDebugView.cs new file mode 100644 index 00000000000..4c995f84a8e --- /dev/null +++ b/src/Shared/IDictionaryDebugView.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Utilities +{ + internal sealed class IDictionaryDebugView + { + private readonly IDictionary _dict; + + public IDictionaryDebugView(IDictionary dictionary) + { + _dict = dictionary ?? throw new ArgumentNullException(nameof(dictionary)); + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Items + { + get + { + var items = new KeyValuePair[_dict.Count]; + _dict.CopyTo(items, 0); + return items; + } + } + } + + internal sealed class DictionaryKeyCollectionDebugView + { + private readonly ICollection _collection; + + public DictionaryKeyCollectionDebugView(ICollection collection) + { + _collection = collection ?? throw new ArgumentNullException(nameof(collection)); + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public TKey[] Items + { + get + { + var items = new TKey[_collection.Count]; + _collection.CopyTo(items, 0); + return items; + } + } + } + + internal sealed class DictionaryValueCollectionDebugView + { + private readonly ICollection _collection; + + public DictionaryValueCollectionDebugView(ICollection collection) + { + _collection = collection ?? throw new ArgumentNullException(nameof(collection)); + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public TValue[] Items + { + get + { + var items = new TValue[_collection.Count]; + _collection.CopyTo(items, 0); + return items; + } + } + } +} diff --git a/src/Shared/OrderedDictionary.KeyCollection.cs b/src/Shared/OrderedDictionary.KeyCollection.cs new file mode 100644 index 00000000000..401aec343ea --- /dev/null +++ b/src/Shared/OrderedDictionary.KeyCollection.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Collections; + +namespace Microsoft.EntityFrameworkCore.Utilities +{ + internal partial class OrderedDictionary + { + /// + /// Represents the collection of keys in a . This class cannot be inherited. + /// + [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + internal sealed class KeyCollection : IList, IReadOnlyList + { + private readonly OrderedDictionary _orderedDictionary; + + /// + /// Gets the number of elements contained in the . + /// + /// The number of elements contained in the . + public int Count => _orderedDictionary.Count; + + /// + /// Gets the key at the specified index as an O(1) operation. + /// + /// The zero-based index of the key to get. + /// The key at the specified index. + /// is less than 0.-or- is equal to or greater than . + public TKey this[int index] => ((IList>)_orderedDictionary)[index].Key; + + TKey IList.this[int index] + { + get => this[index]; + set => throw new NotSupportedException(); + } + + bool ICollection.IsReadOnly => true; + + internal KeyCollection(OrderedDictionary orderedDictionary) + { + _orderedDictionary = orderedDictionary; + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// A for the . + public Enumerator GetEnumerator() => new Enumerator(_orderedDictionary); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + int IList.IndexOf(TKey item) => _orderedDictionary.IndexOf(item); + + void IList.Insert(int index, TKey item) => throw new NotSupportedException(); + + void IList.RemoveAt(int index) => throw new NotSupportedException(); + + void ICollection.Add(TKey item) => throw new NotSupportedException(); + + void ICollection.Clear() => throw new NotSupportedException(); + + bool ICollection.Contains(TKey item) => _orderedDictionary.ContainsKey(item); + + void ICollection.CopyTo(TKey[] array, int arrayIndex) + { + ArgumentNullException.ThrowIfNull(array); + if ((uint)arrayIndex > (uint)array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + var count = Count; + if (array.Length - arrayIndex < count) + { + throw new ArgumentException(); + } + + var entries = _orderedDictionary._entries; + for (var i = 0; i < count; ++i) + { + array[i + arrayIndex] = entries[i].Key; + } + } + + bool ICollection.Remove(TKey item) => throw new NotSupportedException(); + + /// + /// Enumerates the elements of a . + /// + public struct Enumerator : IEnumerator + { + private readonly OrderedDictionary _orderedDictionary; + private readonly int _version; + private int _index; + private TKey _current; + + /// + /// Gets the element at the current position of the enumerator. + /// + /// The element in the at the current position of the enumerator. + public readonly TKey Current => _current; + + readonly object? IEnumerator.Current => _current; + + internal Enumerator(OrderedDictionary orderedDictionary) + { + _orderedDictionary = orderedDictionary; + _version = orderedDictionary._version; + _index = 0; + _current = default!; + } + + /// + /// Releases all resources used by the . + /// + public void Dispose() + { + } + + /// + /// Advances the enumerator to the next element of the . + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// The collection was modified after the enumerator was created. + public bool MoveNext() + { + if (_version != _orderedDictionary._version) + { + throw new InvalidOperationException(); + } + + if (_index < _orderedDictionary.Count) + { + _current = _orderedDictionary._entries[_index].Key; + ++_index; + return true; + } + _current = default!; + return false; + } + + void IEnumerator.Reset() + { + if (_version != _orderedDictionary._version) + { + throw new InvalidOperationException(); + } + + _index = 0; + _current = default!; + } + } + } + } +} diff --git a/src/Shared/OrderedDictionary.ValueCollection.cs b/src/Shared/OrderedDictionary.ValueCollection.cs new file mode 100644 index 00000000000..71ead29c182 --- /dev/null +++ b/src/Shared/OrderedDictionary.ValueCollection.cs @@ -0,0 +1,174 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Collections; + +namespace Microsoft.EntityFrameworkCore.Utilities +{ + internal partial class OrderedDictionary + { + /// + /// Represents the collection of values in a . This class cannot be inherited. + /// + [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + public sealed class ValueCollection : IList, IReadOnlyList + { + private readonly OrderedDictionary _orderedDictionary; + + /// + /// Gets the number of elements contained in the . + /// + /// The number of elements contained in the . + public int Count => _orderedDictionary.Count; + + /// + /// Gets the value at the specified index as an O(1) operation. + /// + /// The zero-based index of the value to get. + /// The value at the specified index. + /// is less than 0.-or- is equal to or greater than . + public TValue this[int index] => _orderedDictionary[index]; + + TValue IList.this[int index] + { + get => this[index]; + set => throw new NotSupportedException(); + } + + bool ICollection.IsReadOnly => true; + + internal ValueCollection(OrderedDictionary orderedDictionary) + { + _orderedDictionary = orderedDictionary; + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// A for the . + public Enumerator GetEnumerator() => new Enumerator(_orderedDictionary); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + int IList.IndexOf(TValue item) + { + var comparer = EqualityComparer.Default; + var entries = _orderedDictionary._entries; + var count = Count; + for (var i = 0; i < count; ++i) + { + if (comparer.Equals(entries[i].Value, item)) + { + return i; + } + } + return -1; + } + + void IList.Insert(int index, TValue item) => throw new NotSupportedException(); + + void IList.RemoveAt(int index) => throw new NotSupportedException(); + + void ICollection.Add(TValue item) => throw new NotSupportedException(); + + void ICollection.Clear() => throw new NotSupportedException(); + + bool ICollection.Contains(TValue item) => ((IList)this).IndexOf(item) >= 0; + + void ICollection.CopyTo(TValue[] array, int arrayIndex) + { + ArgumentNullException.ThrowIfNull(array); + + if ((uint)arrayIndex > (uint)array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + var count = Count; + if (array.Length - arrayIndex < count) + { + throw new ArgumentException(); + } + + var entries = _orderedDictionary._entries; + for (var i = 0; i < count; ++i) + { + array[i + arrayIndex] = entries[i].Value; + } + } + + bool ICollection.Remove(TValue item) => throw new NotSupportedException(); + + /// + /// Enumerates the elements of a . + /// + public struct Enumerator : IEnumerator + { + private readonly OrderedDictionary _orderedDictionary; + private readonly int _version; + private int _index; + private TValue _current; + + /// + /// Gets the element at the current position of the enumerator. + /// + /// The element in the at the current position of the enumerator. + public TValue Current => _current; + + object? IEnumerator.Current => _current; + + internal Enumerator(OrderedDictionary orderedDictionary) + { + _orderedDictionary = orderedDictionary; + _version = orderedDictionary._version; + _index = 0; + _current = default!; + } + + /// + /// Releases all resources used by the . + /// + public void Dispose() + { + } + + /// + /// Advances the enumerator to the next element of the . + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// The collection was modified after the enumerator was created. + public bool MoveNext() + { + if (_version != _orderedDictionary._version) + { + throw new InvalidOperationException(); + } + + if (_index < _orderedDictionary.Count) + { + _current = _orderedDictionary._entries[_index].Value; + ++_index; + return true; + } + _current = default!; + return false; + } + + void IEnumerator.Reset() + { + if (_version != _orderedDictionary._version) + { + throw new InvalidOperationException(); + } + + _index = 0; + _current = default!; + } + } + } + } +} diff --git a/src/Shared/OrderedDictionary.cs b/src/Shared/OrderedDictionary.cs new file mode 100644 index 00000000000..76211c9002c --- /dev/null +++ b/src/Shared/OrderedDictionary.cs @@ -0,0 +1,893 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Collections; + +namespace Microsoft.EntityFrameworkCore.Utilities +{ + internal enum InsertionBehavior + { + None = 0, + OverwriteExisting = 1, + ThrowOnExisting = 2 + } + + /// + /// Represents an ordered collection of keys and values with the same performance as with O(1) lookups and adds but with O(n) inserts and removes. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))] + [DebuggerDisplay("Count = {Count}")] + internal sealed partial class OrderedDictionary : IDictionary, IReadOnlyDictionary, IList>, IReadOnlyList> + { + private struct Entry + { + public uint HashCode; + public TKey Key; + public TValue Value; + public int Next; // the index of the next item in the same bucket, -1 if last + } + + // We want to initialize without allocating arrays. We also want to avoid null checks. + // Array.Empty would give divide by zero in modulo operation. So we use static one element arrays. + // The first add will cause a resize replacing these with real arrays of three elements. + // Arrays are wrapped in a class to avoid being duplicated for each + private static readonly Entry[] InitialEntries = new Entry[1]; + // 1-based index into _entries; 0 means empty + private int[] _buckets = HashHelpers.SizeOneIntArray; + // remains contiguous and maintains order + private Entry[] _entries = InitialEntries; + private int _count; + private int _version; + // is null when comparer is EqualityComparer.Default so that the GetHashCode method is used explicitly on the object + private readonly IEqualityComparer? _comparer; + private KeyCollection? _keys; + private ValueCollection? _values; + + /// + /// Gets the number of key/value pairs contained in the . + /// + /// The number of key/value pairs contained in the . + public int Count => _count; + + /// + /// Gets the that is used to determine equality of keys for the dictionary. + /// + /// The generic interface implementation that is used to determine equality of keys for the current and to provide hash values for the keys. + public IEqualityComparer Comparer => _comparer ?? EqualityComparer.Default; + + /// + /// Gets a collection containing the keys in the . + /// + /// An containing the keys in the . + public KeyCollection Keys => _keys ??= new KeyCollection(this); + + /// + /// Gets a collection containing the values in the . + /// + /// An containing the values in the . + public ValueCollection Values => _values ??= new ValueCollection(this); + + /// + /// Gets or sets the value associated with the specified key as an O(1) operation. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. If the specified key is not found, a get operation throws a , and a set operation creates a new element with the specified key. + /// is null. + /// The property is retrieved and does not exist in the collection. + public TValue this[TKey key] + { + get + { + var index = IndexOf(key); + return index < 0 + ? throw new KeyNotFoundException($"Key {key} not found in the dictionary") + : _entries[index].Value; + } + set => TryInsert(null, key, value, InsertionBehavior.OverwriteExisting); + } + + /// + /// Gets or sets the value at the specified index as an O(1) operation. + /// + /// The zero-based index of the element to get or set. + /// The value at the specified index. + /// is less than 0.-or- is equal to or greater than . + public TValue this[int index] + { + get + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count); + + return _entries[index].Value; + } + set + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count); + + _entries[index].Value = value; + } + } + + /// + /// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the default equality comparer for the key type. + /// + public OrderedDictionary() + : this(0, null) + { + } + + /// + /// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type. + /// + /// The initial number of elements that the can contain. + /// is less than 0. + public OrderedDictionary(int capacity) + : this(capacity, null) + { + } + + /// + /// Initializes a new instance of the class that is empty, has the default initial capacity, and uses the specified . + /// + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + public OrderedDictionary(IEqualityComparer comparer) + : this(0, comparer) + { + } + + /// + /// Initializes a new instance of the class that is empty, has the specified initial capacity, and uses the specified . + /// + /// The initial number of elements that the can contain. + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + /// is less than 0. + public OrderedDictionary(int capacity, IEqualityComparer? comparer) + { + ArgumentOutOfRangeException.ThrowIfNegative(capacity); + + if (capacity > 0) + { + var newSize = HashHelpers.GetPrime(capacity); + _buckets = new int[newSize]; + _entries = new Entry[newSize]; + } + + if (comparer != EqualityComparer.Default) + { + _comparer = comparer; + } + } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified and uses the default equality comparer for the key type. + /// + /// The whose elements are copied to the new . + /// is null. + /// contains one or more duplicate keys. + public OrderedDictionary(IEnumerable> collection) + : this(collection, null) + { + } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified and uses the specified . + /// + /// The whose elements are copied to the new . + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + /// is null. + /// contains one or more duplicate keys. + public OrderedDictionary(IEnumerable> collection, IEqualityComparer? comparer) + : this((collection as ICollection>)?.Count ?? 0, comparer) + { + ArgumentNullException.ThrowIfNull(collection); + + foreach (var pair in collection) + { + Add(pair.Key, pair.Value); + } + } + + /// + /// Adds the specified key and value to the dictionary as an O(1) operation. + /// + /// The key of the element to add. + /// The value of the element to add. The value can be null for reference types. + /// is null. + /// An element with the same key already exists in the . + public void Add(TKey key, TValue value) => TryInsert(null, key, value, InsertionBehavior.ThrowOnExisting); + + /// + /// Removes all keys and values from the . + /// + public void Clear() + { + if (_count > 0) + { + Array.Clear(_buckets, 0, _buckets.Length); + Array.Clear(_entries, 0, _count); + _count = 0; + ++_version; + } + } + + /// + /// Determines whether the contains the specified key as an O(1) operation. + /// + /// The key to locate in the . + /// true if the contains an element with the specified key; otherwise, false. + /// is null. + public bool ContainsKey(TKey key) => IndexOf(key) >= 0; + + /// + /// Resizes the internal data structure if necessary to ensure no additional resizing to support the specified capacity. + /// + /// The number of elements that the must be able to contain. + /// The capacity of the . + /// is less than 0. + public int EnsureCapacity(int capacity) + { + ArgumentOutOfRangeException.ThrowIfNegative(capacity); + + if (_entries.Length >= capacity) + { + return _entries.Length; + } + var newSize = HashHelpers.GetPrime(capacity); + Resize(newSize); + ++_version; + return newSize; + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// An structure for the . + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Adds a key/value pair to the if the key does not already exist as an O(1) operation. + /// + /// The key of the element to add. + /// The value to be added, if the key does not already exist. + /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new value if the key was not in the dictionary. + /// is null. + public TValue GetOrAdd(TKey key, TValue value) => GetOrAdd(key, () => value); + + /// + /// Adds a key/value pair to the by using the specified function, if the key does not already exist as an O(1) operation. + /// + /// The key of the element to add. + /// The function used to generate a value for the key. + /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new value for the key as returned by valueFactory if the key was not in the dictionary. + /// is null.-or- is null. + public TValue GetOrAdd(TKey key, Func valueFactory) + { + ArgumentNullException.ThrowIfNull(valueFactory); + + var index = IndexOf(key, out var hashCode); + TValue value; + if (index < 0) + { + value = valueFactory(); + AddInternal(null, key, value, hashCode); + } + else + { + value = _entries[index].Value; + } + return value; + } + + /// + /// Returns the zero-based index of the element with the specified key within the as an O(1) operation. + /// + /// The key of the element to locate. + /// The zero-based index of the element with the specified key within the , if found; otherwise, -1. + /// is null. + public int IndexOf(TKey key) => IndexOf(key, out _); + + /// + /// Inserts the specified key/value pair into the at the specified index as an O(n) operation. + /// + /// The zero-based index of the key/value pair to insert. + /// The key of the element to insert. + /// The value of the element to insert. + /// is null. + /// An element with the same key already exists in the . + /// is less than 0.-or- is greater than . + public void Insert(int index, TKey key, TValue value) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(index, Count); + + TryInsert(index, key, value, InsertionBehavior.ThrowOnExisting); + } + + /// + /// Inserts the element in this sorted dictionary to the corresponding index using the default comparer. + /// + /// The key of the element to insert. + /// The value of the element to insert. + public void Insert(TKey key, TValue value) + { + var existingIndex = IndexOf(key, out var hashCode); + if (existingIndex >= 0) + { + throw new ArgumentException($"Key {key} is already present"); + } + + var comparer = Comparer.Default; + for (var i = _count - 1; i >= 0; i--) + { + if (comparer.Compare(key, _entries[i].Key) >= 0) + { + AddInternal(i + 1, key, value, hashCode); + return; + } + } + + AddInternal(0, key, value, hashCode); + } + + /// + /// Moves the element at the specified fromIndex to the specified toIndex while re-arranging the elements in between. + /// + /// The zero-based index of the element to move. + /// The zero-based index to move the element to. + /// + /// is less than 0. + /// -or- + /// is equal to or greater than + /// -or- + /// is less than 0. + /// -or- + /// is equal to or greater than + /// + public void Move(int fromIndex, int toIndex) + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(fromIndex, Count); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(toIndex, Count); + + if (fromIndex == toIndex) + { + return; + } + + var entries = _entries; + var temp = entries[fromIndex]; + RemoveEntryFromBucket(fromIndex); + var direction = fromIndex < toIndex ? 1 : -1; + for (var i = fromIndex; i != toIndex; i += direction) + { + entries[i] = entries[i + direction]; + UpdateBucketIndex(i + direction, -direction); + } + AddEntryToBucket(ref temp, toIndex, _buckets); + entries[toIndex] = temp; + ++_version; + } + + /// + /// Moves the specified number of elements at the specified fromIndex to the specified toIndex while re-arranging the elements in between. + /// + /// The zero-based index of the elements to move. + /// The zero-based index to move the elements to. + /// The number of elements to move. + /// is less than 0. + /// -or- + /// is equal to or greater than . + /// -or- + /// is less than 0. + /// -or- + /// is equal to or greater than . + /// -or- + /// is less than 0. + /// + is greater than . + /// -or- + /// + is greater than . + public void MoveRange(int fromIndex, int toIndex, int count) + { + if (count == 1) + { + Move(fromIndex, toIndex); + return; + } + + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(fromIndex, Count); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(toIndex, Count); + ArgumentOutOfRangeException.ThrowIfNegative(count); + ArgumentOutOfRangeException.ThrowIfGreaterThan(fromIndex + count, Count, nameof(fromIndex)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(toIndex + count, Count, nameof(toIndex)); + + if (fromIndex == toIndex || count == 0) + { + return; + } + + var entries = _entries; + // Make a copy of the entries to move. Consider using ArrayPool instead to avoid allocations? + var entriesToMove = new Entry[count]; + for (var i = 0; i < count; ++i) + { + entriesToMove[i] = entries[fromIndex + i]; + RemoveEntryFromBucket(fromIndex + i); + } + + // Move entries in between + var direction = 1; + var amount = count; + var start = fromIndex; + var end = toIndex; + if (fromIndex > toIndex) + { + direction = -1; + amount = -count; + start = fromIndex + count - 1; + end = toIndex + count - 1; + } + for (var i = start; i != end; i += direction) + { + entries[i] = entries[i + amount]; + UpdateBucketIndex(i + amount, -amount); + } + + var buckets = _buckets; + // Copy entries to destination + for (var i = 0; i < count; ++i) + { + var temp = entriesToMove[i]; + AddEntryToBucket(ref temp, toIndex + i, buckets); + entries[toIndex + i] = temp; + } + ++_version; + } + + /// + /// Removes the value with the specified key from the as an O(n) operation. + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; otherwise, false. This method returns false if is not found in the . + /// is null. + public bool Remove(TKey key) => Remove(key, out _); + + /// + /// Removes the value with the specified key from the and returns the value as an O(n) operation. + /// + /// The key of the element to remove. + /// When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. + /// true if the element is successfully found and removed; otherwise, false. This method returns false if is not found in the . + /// is null. + public bool Remove(TKey key, out TValue value) + { + var index = IndexOf(key); + if (index >= 0) + { + value = _entries[index].Value; + RemoveAt(index); + return true; + } + value = default!; + return false; + } + + /// + /// Removes the value at the specified index from the as an O(n) operation. + /// + /// The zero-based index of the element to remove. + /// is less than 0.-or- is equal to or greater than . + public void RemoveAt(int index) + { + var count = Count; + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, count); + + // Remove the entry from the bucket + RemoveEntryFromBucket(index); + + // Decrement the indices > index + var entries = _entries; + for (var i = index + 1; i < count; ++i) + { + entries[i - 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: -1); + } + --_count; + entries[_count] = default; + ++_version; + } + + /// + /// Sets the capacity of an object to the actual number of elements it contains, rounded up to a nearby, implementation-specific value. + /// + public void TrimExcess() => TrimExcess(Count); + + /// + /// Sets the capacity of an object to the specified capacity, rounded up to a nearby, implementation-specific value. + /// + /// The number of elements that the must be able to contain. + /// is less than . + public void TrimExcess(int capacity) + { + ArgumentOutOfRangeException.ThrowIfLessThan(capacity, Count); + + var newSize = HashHelpers.GetPrime(capacity); + if (newSize < _entries.Length) + { + Resize(newSize); + ++_version; + } + } + + /// + /// Tries to add the specified key and value to the dictionary as an O(1) operation. + /// + /// The key of the element to add. + /// The value of the element to add. The value can be null for reference types. + /// true if the element was added to the ; false if the already contained an element with the specified key. + /// is null. + public bool TryAdd(TKey key, TValue value) => TryInsert(null, key, value, InsertionBehavior.None); + + /// + /// Gets the value associated with the specified key as an O(1) operation. + /// + /// The key of the value to get. + /// When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. + /// true if the contains an element with the specified key; otherwise, false. + /// is null. + public bool TryGetValue(TKey key, out TValue value) + { + var index = IndexOf(key); + if (index >= 0) + { + value = _entries[index].Value; + return true; + } + value = default!; + return false; + } + + #region Explicit Interface Implementation + KeyValuePair IList>.this[int index] + { + get + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count); + + var entry = _entries[index]; + return new KeyValuePair(entry.Key, entry.Value); + } + set + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count); + + var key = value.Key; + var foundIndex = IndexOf(key, out var hashCode); + // key does not exist in dictionary thus replace entry at index + if (foundIndex < 0) + { + RemoveEntryFromBucket(index); + var entry = new Entry { HashCode = hashCode, Key = key, Value = value.Value }; + AddEntryToBucket(ref entry, index, _buckets); + _entries[index] = entry; + ++_version; + } + // key already exists in dictionary at the specified index thus just replace the key and value as hashCode remains the same + else if (foundIndex == index) + { + ref var entry = ref _entries[index]; + entry.Key = key; + entry.Value = value.Value; + } + // key already exists in dictionary but not at the specified index thus throw exception as this method shouldn't affect the indices of other entries + else + { + throw new ArgumentException($"Key {key} already exists in dictionary but not at the specified index {index}"); + } + } + } + + KeyValuePair IReadOnlyList>.this[int index] => ((IList>)this)[index]; + + ICollection IDictionary.Keys => Keys; + + ICollection IDictionary.Values => Values; + + IEnumerable IReadOnlyDictionary.Keys => Keys; + + IEnumerable IReadOnlyDictionary.Values => Values; + + bool ICollection>.IsReadOnly => false; + + IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); + + bool ICollection>.Contains(KeyValuePair item) => TryGetValue(item.Key, out var value) && EqualityComparer.Default.Equals(value, item.Value); + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ArgumentNullException.ThrowIfNull(array); + ArgumentOutOfRangeException.ThrowIfGreaterThan(arrayIndex, array.Length); + var count = Count; + ArgumentOutOfRangeException.ThrowIfLessThan(array.Length - arrayIndex, count); + + var entries = _entries; + for (var i = 0; i < count; ++i) + { + var entry = entries[i]; + array[i + arrayIndex] = new KeyValuePair(entry.Key, entry.Value); + } + } + + bool ICollection>.Remove(KeyValuePair item) + { + var index = IndexOf(item.Key); + if (index >= 0 && EqualityComparer.Default.Equals(_entries[index].Value, item.Value)) + { + RemoveAt(index); + return true; + } + return false; + } + + int IList>.IndexOf(KeyValuePair item) + { + var index = IndexOf(item.Key); + if (index >= 0 && !EqualityComparer.Default.Equals(_entries[index].Value, item.Value)) + { + index = -1; + } + return index; + } + + void IList>.Insert(int index, KeyValuePair item) => Insert(index, item.Key, item.Value); + #endregion + + private Entry[] Resize(int newSize) + { + var newBuckets = new int[newSize]; + var newEntries = new Entry[newSize]; + + var count = Count; + Array.Copy(_entries, newEntries, count); + for (var i = 0; i < count; ++i) + { + AddEntryToBucket(ref newEntries[i], i, newBuckets); + } + + _buckets = newBuckets; + _entries = newEntries; + return newEntries; + } + + private int IndexOf(TKey key, out uint hashCode) + { + ArgumentNullException.ThrowIfNull(key); + + var comparer = _comparer; + hashCode = (uint)(comparer?.GetHashCode(key) ?? key.GetHashCode()); + var index = _buckets[(int)(hashCode % (uint)_buckets.Length)] - 1; + if (index >= 0) + { + comparer ??= EqualityComparer.Default; + var entries = _entries; + var collisionCount = 0; + do + { + var entry = entries[index]; + if (entry.HashCode == hashCode && comparer.Equals(entry.Key, key)) + { + break; + } + index = entry.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException("Concurrent update detected"); + } + ++collisionCount; + } while (index >= 0); + } + return index; + } + + private bool TryInsert(int? index, TKey key, TValue value, InsertionBehavior behavior) + { + var i = IndexOf(key, out var hashCode); + if (i >= 0) + { + switch (behavior) + { + case InsertionBehavior.OverwriteExisting: + _entries[i].Value = value; + return true; + case InsertionBehavior.ThrowOnExisting: + throw new ArgumentException($"Key {key} is already present"); + default: + return false; + } + } + + AddInternal(index, key, value, hashCode); + return true; + } + + private int AddInternal(int? index, TKey key, TValue value, uint hashCode) + { + var entries = _entries; + // Check if resize is needed + var count = Count; + if (entries.Length == count || entries.Length == 1) + { + entries = Resize(HashHelpers.ExpandPrime(entries.Length)); + } + + // Increment indices >= index; + var actualIndex = index ?? count; + for (var i = count - 1; i >= actualIndex; --i) + { + entries[i + 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: 1); + } + + ref var entry = ref entries[actualIndex]; + entry.HashCode = hashCode; + entry.Key = key; + entry.Value = value; + AddEntryToBucket(ref entry, actualIndex, _buckets); + ++_count; + ++_version; + return actualIndex; + } + + // Returns the index of the next entry in the bucket + private void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets) + { + ref var b = ref buckets[(int)(entry.HashCode % (uint)buckets.Length)]; + entry.Next = b - 1; + b = entryIndex + 1; + } + + private void RemoveEntryFromBucket(int entryIndex) + { + var entries = _entries; + var entry = entries[entryIndex]; + ref var b = ref _buckets[(int)(entry.HashCode % (uint)_buckets.Length)]; + // Bucket was pointing to removed entry. Update it to point to the next in the chain + if (b == entryIndex + 1) + { + b = entry.Next + 1; + } + else + { + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain + var i = b - 1; + var collisionCount = 0; + while (true) + { + ref var e = ref entries[i]; + if (e.Next == entryIndex) + { + e.Next = entry.Next; + return; + } + i = e.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException("Concurrent update detected"); + } + ++collisionCount; + } + } + } + + private void UpdateBucketIndex(int entryIndex, int incrementAmount) + { + var entries = _entries; + var entry = entries[entryIndex]; + ref var b = ref _buckets[(int)(entry.HashCode % (uint)_buckets.Length)]; + // Bucket was pointing to entry. Increment the index by incrementAmount. + if (b == entryIndex + 1) + { + b += incrementAmount; + } + else + { + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. + var i = b - 1; + var collisionCount = 0; + while (true) + { + ref var e = ref entries[i]; + if (e.Next == entryIndex) + { + e.Next += incrementAmount; + return; + } + i = e.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException("Concurrent update detected"); + } + ++collisionCount; + } + } + } + + /// + /// Enumerates the elements of a . + /// + public struct Enumerator : IEnumerator> + { + private readonly OrderedDictionary _orderedDictionary; + private readonly int _version; + private int _index; + private KeyValuePair _current; + + /// + /// Gets the element at the current position of the enumerator. + /// + /// The element in the at the current position of the enumerator. + public KeyValuePair Current => _current; + + object IEnumerator.Current => _current; + + internal Enumerator(OrderedDictionary orderedDictionary) + { + _orderedDictionary = orderedDictionary; + _version = orderedDictionary._version; + _index = 0; + } + + /// + /// Releases all resources used by the . + /// + public void Dispose() + { + } + + /// + /// Advances the enumerator to the next element of the . + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// The collection was modified after the enumerator was created. + public bool MoveNext() + { + if (_version != _orderedDictionary._version) + { + throw new InvalidOperationException("The dictionary has been modified during enumeration"); + } + + if (_index < _orderedDictionary.Count) + { + var entry = _orderedDictionary._entries[_index]; + _current = new KeyValuePair(entry.Key, entry.Value); + ++_index; + return true; + } + _current = default; + return false; + } + + void IEnumerator.Reset() + { + if (_version != _orderedDictionary._version) + { + throw new InvalidOperationException("The dictionary has been modified during enumeration"); + } + + _index = 0; + _current = default; + } + } + } +} diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 7cffb61282d..0805530a9d2 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -609,7 +609,7 @@ protected override void Down(MigrationBuilder migrationBuilder) var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(configureConventions: c => c.RemoveAllConventions()); modelBuilder.HasAnnotation("Some:EnumValue", RegexOptions.Multiline); - modelBuilder.HasAnnotation(RelationalAnnotationNames.DbFunctions, new SortedDictionary()); + modelBuilder.HasAnnotation(RelationalAnnotationNames.DbFunctions, new Dictionary()); modelBuilder.Entity( "T1", eb => { diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index 2c4d6144fa7..8b607331d8c 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -85,6 +85,8 @@ var migrationAssembly TestServiceFactory.Instance.Create()), new MigrationsAnnotationProvider( new MigrationsAnnotationProviderDependencies()), + new RelationalAnnotationProvider( + new RelationalAnnotationProviderDependencies()), services.GetRequiredService(), services.GetRequiredService()), idGenerator, diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 03927467588..26656c763df 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -65,12 +65,11 @@ public void Empty_model() => Test( new EmptyContext(), new CompiledModelCodeGenerationOptions(), - code => - Assert.Collection( - code, - c => AssertFileContents( - "EmptyContextModel.cs", - """ + code => Assert.Collection( + code, + c => AssertFileContents( + "EmptyContextModel.cs", + """ // using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -108,7 +107,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (EmptyContextModel)model.FinalizeModel(); } private static EmptyContextModel _instance; @@ -119,12 +118,12 @@ void RunInitialization() partial void Customize(); } } -""", - c), - c => AssertFileContents( - "EmptyContextModelBuilder.cs", - """ +""", c), + c => AssertFileContents( + "EmptyContextModelBuilder.cs", + """ // +using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -135,13 +134,17 @@ namespace TestNamespace { public partial class EmptyContextModel { + private EmptyContextModel() + : base(skipDetectChanges: true, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 0) + { + } + partial void Initialize() { } } } -""", - c)), +""", c)), model => { Assert.Empty(model.GetEntityTypes()); @@ -388,7 +391,7 @@ public void Custom_value_converter() => Test( new ValueConverterContext(), new CompiledModelCodeGenerationOptions(), - code => Assert.Collection( + code => Assert.Collection( code, c => AssertFileContents( "ValueConverterContextModel.cs", @@ -430,7 +433,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (ValueConverterContextModel)model.FinalizeModel(); } private static ValueConverterContextModel _instance; @@ -446,6 +449,7 @@ void RunInitialization() "ValueConverterContextModelBuilder.cs", """ // +using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -456,6 +460,11 @@ namespace TestNamespace { public partial class ValueConverterContextModel { + private ValueConverterContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var myEntity = MyEntityEntityType.Create(this); @@ -494,7 +503,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas baseEntityType, sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), - propertyBag: true); + propertyBag: true, + propertyCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -611,7 +622,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (ValueComparerContextModel)model.FinalizeModel(); } private static ValueComparerContextModel _instance; @@ -627,6 +638,7 @@ void RunInitialization() "ValueComparerContextModelBuilder.cs", """ // +using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -637,6 +649,11 @@ namespace TestNamespace { public partial class ValueComparerContextModel { + private ValueComparerContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var myEntity = MyEntityEntityType.Create(this); @@ -674,7 +691,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas baseEntityType, sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), - propertyBag: true); + propertyBag: true, + propertyCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -808,7 +827,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (ProviderValueComparerContextModel)model.FinalizeModel(); } private static ProviderValueComparerContextModel _instance; @@ -824,6 +843,7 @@ void RunInitialization() "ProviderValueComparerContextModelBuilder.cs", """ // +using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -834,6 +854,11 @@ namespace TestNamespace { public partial class ProviderValueComparerContextModel { + private ProviderValueComparerContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var myEntity = MyEntityEntityType.Create(this); @@ -871,7 +896,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas baseEntityType, sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), - propertyBag: true); + propertyBag: true, + propertyCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -986,7 +1013,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (TypeMappingContextModel)model.FinalizeModel(); } private static TypeMappingContextModel _instance; @@ -997,12 +1024,12 @@ void RunInitialization() partial void Customize(); } } -""", - c), +""", c), c => AssertFileContents( "TypeMappingContextModelBuilder.cs", """ // +using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -1013,6 +1040,11 @@ namespace TestNamespace { public partial class TypeMappingContextModel { + private TypeMappingContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var myEntity = MyEntityEntityType.Create(this); @@ -1022,8 +1054,7 @@ partial void Initialize() } } } -""", - c), +""", c), c => AssertFileContents( "MyEntityEntityType.cs", """ @@ -1051,7 +1082,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas baseEntityType, sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), - propertyBag: true); + propertyBag: true, + propertyCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -1091,8 +1124,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) static partial void Customize(RuntimeEntityType runtimeEntityType); } } -""", - c)), +""", c)), model => { var entityType = model.GetEntityTypes().Single(); @@ -1165,7 +1197,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (FunctionTypeMappingContextModel)model.FinalizeModel(); } private static FunctionTypeMappingContextModel _instance; @@ -1200,9 +1232,14 @@ namespace TestNamespace { public partial class FunctionTypeMappingContextModel { + private FunctionTypeMappingContextModel() + : base(skipDetectChanges: true, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 0) + { + } + partial void Initialize() { - var functions = new SortedDictionary(); + var functions = new Dictionary(); var getSqlFragmentStatic = new RuntimeDbFunction( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+FunctionTypeMappingContext.GetSqlFragmentStatic(string)", this, @@ -1350,7 +1387,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (FunctionParameterTypeMappingContextModel)model.FinalizeModel(); } private static FunctionParameterTypeMappingContextModel _instance; @@ -1385,9 +1422,14 @@ namespace TestNamespace { public partial class FunctionParameterTypeMappingContextModel { + private FunctionParameterTypeMappingContextModel() + : base(skipDetectChanges: true, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 0) + { + } + partial void Initialize() { - var functions = new SortedDictionary(); + var functions = new Dictionary(); var getSqlFragmentStatic = new RuntimeDbFunction( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+FunctionParameterTypeMappingContext.GetSqlFragmentStatic(string)", this, @@ -1557,7 +1599,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (DbContextModel)model.FinalizeModel(); } private static DbContextModel _instance; @@ -1573,6 +1615,7 @@ void RunInitialization() "DbContextModelBuilder.cs", """ // +using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -1583,6 +1626,11 @@ namespace Internal { public partial class DbContextModel { + private DbContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 4) + { + } + partial void Initialize() { var index = IndexEntityType.Create(this); @@ -1623,7 +1671,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.Index", typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Index), - baseEntityType); + baseEntityType, + propertyCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -1690,7 +1740,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.Internal", typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Internal), - baseEntityType); + baseEntityType, + propertyCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -1760,7 +1812,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(IdentityUser), baseEntityType, discriminatorProperty: "Discriminator", - discriminatorValue: "IdentityUser"); + discriminatorValue: "IdentityUser", + derivedTypesCount: 1, + propertyCount: 16, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -2153,7 +2208,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(IdentityUser), baseEntityType, discriminatorProperty: "Discriminator", - discriminatorValue: "DerivedIdentityUser"); + discriminatorValue: "DerivedIdentityUser", + propertyCount: 0); return runtimeEntityType; } @@ -2243,7 +2299,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (BigContextModel)model.FinalizeModel(); } private static BigContextModel _instance; @@ -2274,6 +2330,11 @@ namespace TestNamespace { public partial class BigContextModel { + private BigContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 8) + { + } + partial void Initialize() { var dependentBase = DependentBaseEntityType.Create(this); @@ -5740,7 +5801,13 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), baseEntityType, discriminatorProperty: "EnumDiscriminator", - discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.One); + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.One, + derivedTypesCount: 1, + propertyCount: 4, + navigationCount: 1, + foreignKeyCount: 2, + unnamedIndexCount: 1, + keyCount: 1); var principalId = runtimeEntityType.AddProperty( "PrincipalId", @@ -5937,7 +6004,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+ManyTypes", typeof(CSharpRuntimeModelCodeGeneratorTest.ManyTypes), - baseEntityType); + baseEntityType, + propertyCount: 236, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -15708,7 +15777,13 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), baseEntityType, - discriminatorValue: "PrincipalBase"); + discriminatorValue: "PrincipalBase", + derivedTypesCount: 1, + propertyCount: 15, + navigationCount: 1, + skipNavigationCount: 1, + unnamedIndexCount: 1, + keyCount: 2); var id = runtimeEntityType.AddProperty( "Id", @@ -16367,7 +16442,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, sharedClrType: true, - changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues, + propertyCount: 12, + servicePropertyCount: 1, + foreignKeyCount: 2, + keyCount: 1); var principalBaseId = runtimeEntityType.AddProperty( "PrincipalBaseId", @@ -16976,7 +17055,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.ManyOwned#OwnedType", typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, - sharedClrType: true); + sharedClrType: true, + propertyCount: 13, + servicePropertyCount: 1, + foreignKeyCount: 1, + keyCount: 1); var principalDerivedId = runtimeEntityType.AddProperty( "PrincipalDerivedId", @@ -17549,7 +17632,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas baseEntityType, sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), - propertyBag: true); + propertyBag: true, + propertyCount: 5, + foreignKeyCount: 2, + unnamedIndexCount: 1, + keyCount: 1); var derivedsId = runtimeEntityType.AddProperty( "DerivedsId", @@ -17738,7 +17825,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived), baseEntityType, discriminatorProperty: "EnumDiscriminator", - discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.Two); + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.Two, + propertyCount: 2); var data = runtimeEntityType.AddProperty( "Data", @@ -17836,7 +17924,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), baseEntityType, - discriminatorValue: "PrincipalDerived>"); + discriminatorValue: "PrincipalDerived>", + propertyCount: 0, + navigationCount: 2, + skipNavigationCount: 1, + foreignKeyCount: 1); return runtimeEntityType; } @@ -18408,7 +18500,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (BigContextWithJsonModel)model.FinalizeModel(); } private static BigContextWithJsonModel _instance; @@ -18439,6 +18531,11 @@ namespace TestNamespace { public partial class BigContextWithJsonModel { + private BigContextWithJsonModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 8) + { + } + partial void Initialize() { var dependentBase = DependentBaseEntityType.Create(this); @@ -21554,7 +21651,13 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), baseEntityType, discriminatorProperty: "EnumDiscriminator", - discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.One); + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.One, + derivedTypesCount: 1, + propertyCount: 4, + navigationCount: 1, + foreignKeyCount: 2, + unnamedIndexCount: 1, + keyCount: 1); var principalId = runtimeEntityType.AddProperty( "PrincipalId", @@ -21751,7 +21854,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+ManyTypes", typeof(CSharpRuntimeModelCodeGeneratorTest.ManyTypes), - baseEntityType); + baseEntityType, + propertyCount: 236, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -31524,7 +31629,13 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), baseEntityType, discriminatorProperty: "Discriminator", - discriminatorValue: "PrincipalBase"); + discriminatorValue: "PrincipalBase", + derivedTypesCount: 1, + propertyCount: 16, + navigationCount: 1, + skipNavigationCount: 1, + unnamedIndexCount: 1, + keyCount: 2); var id = runtimeEntityType.AddProperty( "Id", @@ -32197,7 +32308,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, sharedClrType: true, - changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues, + propertyCount: 12, + servicePropertyCount: 1, + foreignKeyCount: 1, + keyCount: 1); var principalBaseId = runtimeEntityType.AddProperty( "PrincipalBaseId", @@ -32765,7 +32880,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.ManyOwned#OwnedType", typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, - sharedClrType: true); + sharedClrType: true, + propertyCount: 13, + servicePropertyCount: 1, + foreignKeyCount: 1, + keyCount: 1); var principalDerivedId = runtimeEntityType.AddProperty( "PrincipalDerivedId", @@ -33338,7 +33457,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas baseEntityType, sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), - propertyBag: true); + propertyBag: true, + propertyCount: 5, + foreignKeyCount: 2, + unnamedIndexCount: 1, + keyCount: 1); var derivedsId = runtimeEntityType.AddProperty( "DerivedsId", @@ -33527,7 +33650,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived), baseEntityType, discriminatorProperty: "EnumDiscriminator", - discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.Two); + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.Two, + propertyCount: 2); var data = runtimeEntityType.AddProperty( "Data", @@ -33625,7 +33749,10 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), baseEntityType, discriminatorProperty: "Discriminator", - discriminatorValue: "PrincipalDerived>"); + discriminatorValue: "PrincipalDerived>", + propertyCount: 0, + navigationCount: 2, + skipNavigationCount: 1); return runtimeEntityType; } @@ -34172,7 +34299,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (ComplexTypesContextModel)model.FinalizeModel(); } private static ComplexTypesContextModel _instance; @@ -34206,6 +34333,11 @@ namespace TestNamespace { public partial class ComplexTypesContextModel { + private ComplexTypesContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 2) + { + } + partial void Initialize() { var principalBase = PrincipalBaseEntityType.Create(this); @@ -34216,7 +34348,7 @@ partial void Initialize() PrincipalBaseEntityType.CreateAnnotations(principalBase); PrincipalDerivedEntityType.CreateAnnotations(principalDerived); - var functions = new SortedDictionary(); + var functions = new Dictionary(); var principalBaseTvf = new RuntimeDbFunction( "PrincipalBaseTvf", this, @@ -35178,7 +35310,14 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), baseEntityType, discriminatorProperty: "Discriminator", - discriminatorValue: "PrincipalBase"); + discriminatorValue: "PrincipalBase", + derivedTypesCount: 1, + propertyCount: 15, + complexPropertyCount: 1, + navigationCount: 1, + foreignKeyCount: 1, + unnamedIndexCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -35768,7 +35907,9 @@ public static RuntimeComplexProperty Create(RuntimeEntityType declaringType) propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Owned", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("_ownedField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.Field, - changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues, + propertyCount: 10, + complexPropertyCount: 1); var complexType = complexProperty.ComplexType; var details = complexType.AddProperty( @@ -36236,7 +36377,8 @@ public static RuntimeComplexProperty Create(RuntimeComplexType declaringType) "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase.Owned#OwnedType.Principal#PrincipalBase", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Principal", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyCount: 14); var complexType = complexProperty.ComplexType; var alternateId = complexType.AddProperty( @@ -36934,7 +37076,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), baseEntityType, discriminatorProperty: "Discriminator", - discriminatorValue: "PrincipalDerived>"); + discriminatorValue: "PrincipalDerived>", + propertyCount: 0); return runtimeEntityType; } @@ -37512,7 +37655,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (TpcContextModel)model.FinalizeModel(); } private static TpcContextModel _instance; @@ -37543,6 +37686,11 @@ namespace TestNamespace { public partial class TpcContextModel { + private TpcContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 3) + { + } + partial void Initialize() { var dependentBase = DependentBaseEntityType.Create(this); @@ -37557,7 +37705,7 @@ partial void Initialize() PrincipalBaseEntityType.CreateAnnotations(principalBase); PrincipalDerivedEntityType.CreateAnnotations(principalDerived); - var sequences = new SortedDictionary<(string, string), ISequence>(); + var sequences = new Dictionary<(string, string), ISequence>(); var principalBaseSequence = new RuntimeSequence( "PrincipalBaseSequence", this, @@ -38650,7 +38798,12 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DependentBase", typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), - baseEntityType); + baseEntityType, + propertyCount: 2, + navigationCount: 1, + foreignKeyCount: 1, + unnamedIndexCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -38774,7 +38927,14 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), baseEntityType, - discriminatorValue: "PrincipalBase"); + discriminatorValue: "PrincipalBase", + derivedTypesCount: 1, + propertyCount: 15, + navigationCount: 1, + foreignKeyCount: 2, + unnamedIndexCount: 1, + namedIndexCount: 1, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -39530,7 +39690,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), baseEntityType, - discriminatorValue: "PrincipalDerived>"); + discriminatorValue: "PrincipalDerived>", + propertyCount: 0, + navigationCount: 2); return runtimeEntityType; } @@ -40647,7 +40809,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (DbFunctionContextModel)model.FinalizeModel(); } private static DbFunctionContextModel _instance; @@ -40683,6 +40845,11 @@ namespace TestNamespace { public partial class DbFunctionContextModel { + private DbFunctionContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 2, typeConfigurationCount: 1) + { + } + partial void Initialize() { var data = DataEntityType.Create(this); @@ -40696,7 +40863,7 @@ partial void Initialize() maxLength: 256); type.AddAnnotation("Relational:IsFixedLength", true); - var functions = new SortedDictionary(); + var functions = new Dictionary(); var getBlobs = new RuntimeDbFunction( "GetBlobs()", this, @@ -41009,7 +41176,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data", typeof(CSharpRuntimeModelCodeGeneratorTest.Data), - baseEntityType); + baseEntityType, + propertyCount: 1); var blob = runtimeEntityType.AddProperty( "Blob", @@ -41074,7 +41242,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "object", typeof(object), - baseEntityType); + baseEntityType, + propertyCount: 0); return runtimeEntityType; } @@ -41361,7 +41530,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (SequencesContextModel)model.FinalizeModel(); } private static SequencesContextModel _instance; @@ -41391,13 +41560,18 @@ namespace TestNamespace { public partial class SequencesContextModel { + private SequencesContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var data = DataEntityType.Create(this); DataEntityType.CreateAnnotations(data); - var sequences = new SortedDictionary<(string, string), ISequence>(); + var sequences = new Dictionary<(string, string), ISequence>(); var hL = new RuntimeSequence( "HL", this, @@ -41503,7 +41677,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data", typeof(CSharpRuntimeModelCodeGeneratorTest.Data), - baseEntityType); + baseEntityType, + propertyCount: 2, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -41674,7 +41850,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (KeySequencesContextModel)model.FinalizeModel(); } private static KeySequencesContextModel _instance; @@ -41704,13 +41880,18 @@ namespace TestNamespace { public partial class KeySequencesContextModel { + private KeySequencesContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var data = DataEntityType.Create(this); DataEntityType.CreateAnnotations(data); - var sequences = new SortedDictionary<(string, string), ISequence>(); + var sequences = new Dictionary<(string, string), ISequence>(); var keySeq = new RuntimeSequence( "KeySeq", this, @@ -41803,7 +41984,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data", typeof(CSharpRuntimeModelCodeGeneratorTest.Data), - baseEntityType); + baseEntityType, + propertyCount: 2, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -41958,7 +42141,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (ConstraintsContextModel)model.FinalizeModel(); } private static ConstraintsContextModel _instance; @@ -41988,6 +42171,11 @@ namespace TestNamespace { public partial class ConstraintsContextModel { + private ConstraintsContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var data = DataEntityType.Create(this); @@ -42077,7 +42265,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data", typeof(CSharpRuntimeModelCodeGeneratorTest.Data), - baseEntityType); + baseEntityType, + propertyCount: 2, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -42221,7 +42411,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (TriggersContextModel)model.FinalizeModel(); } private static TriggersContextModel _instance; @@ -42251,6 +42441,11 @@ namespace TestNamespace { public partial class TriggersContextModel { + private TriggersContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var data = DataEntityType.Create(this); @@ -42342,7 +42537,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data", typeof(CSharpRuntimeModelCodeGeneratorTest.Data), - baseEntityType); + baseEntityType, + propertyCount: 2, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -42496,7 +42693,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (SqliteContextModel)model.FinalizeModel(); } private static SqliteContextModel _instance; @@ -42512,6 +42709,7 @@ void RunInitialization() "SqliteContextModelBuilder.cs", """ // +using System; using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -42524,6 +42722,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata { public partial class SqliteContextModel { + private SqliteContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 9) + { + } + partial void Initialize() { var data = DataEntityType.Create(this); @@ -46041,7 +46244,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data", typeof(CSharpRuntimeModelCodeGeneratorTest.Data), - baseEntityType); + baseEntityType, + propertyCount: 3, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -46143,7 +46348,13 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), baseEntityType, discriminatorProperty: "EnumDiscriminator", - discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.One); + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.One, + derivedTypesCount: 1, + propertyCount: 4, + navigationCount: 1, + foreignKeyCount: 2, + unnamedIndexCount: 1, + keyCount: 1); var principalId = runtimeEntityType.AddProperty( "PrincipalId", @@ -46328,7 +46539,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+ManyTypes", typeof(CSharpRuntimeModelCodeGeneratorTest.ManyTypes), - baseEntityType); + baseEntityType, + propertyCount: 236, + keyCount: 1); var id = runtimeEntityType.AddProperty( "Id", @@ -54769,7 +54982,13 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), baseEntityType, - discriminatorValue: "PrincipalBase"); + discriminatorValue: "PrincipalBase", + derivedTypesCount: 1, + propertyCount: 15, + navigationCount: 1, + skipNavigationCount: 1, + unnamedIndexCount: 1, + keyCount: 2); var id = runtimeEntityType.AddProperty( "Id", @@ -55333,7 +55552,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, sharedClrType: true, - changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues, + propertyCount: 12, + servicePropertyCount: 1, + foreignKeyCount: 2, + keyCount: 1); var principalBaseId = runtimeEntityType.AddProperty( "PrincipalBaseId", @@ -55830,7 +56053,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.ManyOwned#OwnedType", typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, - sharedClrType: true); + sharedClrType: true, + propertyCount: 13, + servicePropertyCount: 1, + foreignKeyCount: 1, + keyCount: 1); var principalDerivedId = runtimeEntityType.AddProperty( "PrincipalDerivedId", @@ -56290,7 +56517,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas baseEntityType, sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), - propertyBag: true); + propertyBag: true, + propertyCount: 5, + foreignKeyCount: 2, + unnamedIndexCount: 1, + keyCount: 1); var derivedsId = runtimeEntityType.AddProperty( "DerivedsId", @@ -56443,7 +56674,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived), baseEntityType, discriminatorProperty: "EnumDiscriminator", - discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.Two); + discriminatorValue: CSharpMigrationsGeneratorTest.Enum1.Two, + propertyCount: 2); var data = runtimeEntityType.AddProperty( "Data", @@ -56505,7 +56737,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>", typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), baseEntityType, - discriminatorValue: "PrincipalDerived>"); + discriminatorValue: "PrincipalDerived>", + propertyCount: 0, + navigationCount: 2, + skipNavigationCount: 1, + foreignKeyCount: 1); return runtimeEntityType; } @@ -57308,7 +57544,7 @@ void RunInitialization() } model.Customize(); - _instance = model; + _instance = (CosmosContextModel)model.FinalizeModel(); } private static CosmosContextModel _instance; @@ -57335,6 +57571,11 @@ namespace TestNamespace { public partial class CosmosContextModel { + private CosmosContextModel() + : base(skipDetectChanges: false, modelId: new Guid("00000000-0000-0000-0000-000000000000"), entityTypeCount: 1) + { + } + partial void Initialize() { var data = DataEntityType.Create(this); @@ -57376,7 +57617,9 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var runtimeEntityType = model.AddEntityType( "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data", typeof(CSharpRuntimeModelCodeGeneratorTest.Data), - baseEntityType); + baseEntityType, + propertyCount: 6, + keyCount: 2); var id = runtimeEntityType.AddProperty( "Id", @@ -57718,6 +57961,7 @@ protected void Test( string expectedExceptionMessage = null) { var model = context.GetService().Model; + ((Model)model).ModelId = new Guid(); options.ModelNamespace ??= "TestNamespace"; options.ContextType = context.GetType(); diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs index 6f591dbfd03..c53fb05c4fc 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs @@ -124,6 +124,8 @@ protected virtual MigrationsModelDiffer CreateModelDiffer(DbContextOptions optio TestServiceFactory.Instance.Create()), new MigrationsAnnotationProvider( new MigrationsAnnotationProviderDependencies()), + new RelationalAnnotationProvider( + new RelationalAnnotationProviderDependencies()), TestServiceFactory.Instance.Create(), TestHelpers.CreateContext(options).GetService()); } diff --git a/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs b/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs index 1ea522ca099..f5a507348b7 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs @@ -41,10 +41,15 @@ private class EmptyContextModel : RuntimeModel { static EmptyContextModel() { - var model = new EmptyContextModel(); + var model = new EmptyContextModel(false, Guid.NewGuid(), 0, 0); _instance = model; } + public EmptyContextModel(bool skipDetectChanges, Guid modelId, int entityTypeCount, int typeConfigurationCount) + : base(skipDetectChanges, modelId, entityTypeCount, typeConfigurationCount) + { + } + private static readonly EmptyContextModel _instance; public static IModel Instance diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs index f536d8d4e20..dd5c4d46329 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs @@ -263,7 +263,7 @@ public void Entry_handles_null_or_empty_string_in_INotifyPropertyChanging_and_IN entity.NotifyChanging(null); Assert.Equal( - new[] { "Name", "RelatedCollection" }, + ["Name", "RelatedCollection"], testListener.Changing.Select(e => e.Item2.Name).OrderBy(e => e).ToArray()); Assert.Empty(testListener.Changed); @@ -271,7 +271,7 @@ public void Entry_handles_null_or_empty_string_in_INotifyPropertyChanging_and_IN entity.NotifyChanged(""); Assert.Equal( - new[] { "Name", "RelatedCollection" }, + ["Name", "RelatedCollection"], testListener.Changed.Select(e => e.Item2.Name).OrderBy(e => e).ToArray()); } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 08bacf8a836..349286f1456 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -403,14 +403,14 @@ public void Can_remove_keys() Assert.True(((Key)key1).IsInModel); Assert.True(((Key)key2).IsInModel); - Assert.Equal(new[] { key2, key1 }, entityType.GetKeys().ToArray()); + Assert.Equal(new[] { key2, key1 }, entityType.GetKeys()); Assert.True(idProperty.IsKey()); - Assert.Equal(new[] { key1, key2 }, idProperty.GetContainingKeys().ToArray()); + Assert.Equal(new[] { key2, key1 }, idProperty.GetContainingKeys()); Assert.Same(key1, entityType.RemoveKey(key1.Properties)); Assert.Null(entityType.RemoveKey(key1.Properties)); - Assert.Equal(new[] { key2 }, entityType.GetKeys().ToArray()); + Assert.Equal(new[] { key2 }, entityType.GetKeys()); Assert.Same(key2, entityType.RemoveKey(new[] { idProperty }));