From 546547d1398740dd6a66439b5c4e6e0539ea271d Mon Sep 17 00:00:00 2001 From: lajones Date: Thu, 4 Jun 2020 14:00:31 -0700 Subject: [PATCH] Fix for 21089. Allow multiple indexes on the same properties. (#21115) Fix for 21089. Allow multiple indexes on the same properties. --- .../Design/CSharpSnapshotGenerator.cs | 31 ++- .../Internal/CSharpDbContextGenerator.cs | 15 +- .../RelationalScaffoldingModelFactory.cs | 31 +-- .../RelationalIndexBuilderExtensions.cs | 74 +++++- .../Extensions/RelationalIndexExtensions.cs | 77 ++++-- .../Conventions/SharedTableConvention.cs | 4 +- .../ConventionEntityTypeExtensions.cs | 7 +- src/EFCore/Extensions/EntityTypeExtensions.cs | 9 +- src/EFCore/Extensions/IndexExtensions.cs | 3 + .../Metadata/Builders/EntityTypeBuilder.cs | 22 +- .../Metadata/Builders/EntityTypeBuilder`.cs | 53 ++++- .../Builders/IConventionEntityTypeBuilder.cs | 22 +- .../Builders/IConventionIndexBuilder.cs | 23 -- src/EFCore/Metadata/Builders/IndexBuilder.cs | 15 -- src/EFCore/Metadata/Builders/IndexBuilder`.cs | 11 - .../Conventions/IndexAttributeConvention.cs | 12 +- .../ConventionDispatcher.ConventionScope.cs | 1 - ...entionDispatcher.DelayedConventionScope.cs | 19 -- ...tionDispatcher.ImmediateConventionScope.cs | 28 --- .../Internal/ConventionDispatcher.cs | 9 - src/EFCore/Metadata/IConventionEntityType.cs | 32 ++- src/EFCore/Metadata/IConventionIndex.cs | 15 -- src/EFCore/Metadata/IEntityType.cs | 14 +- src/EFCore/Metadata/IMutableEntityType.cs | 25 +- src/EFCore/Metadata/IMutableIndex.cs | 6 - src/EFCore/Metadata/Internal/EntityType.cs | 225 ++++++++++++++++-- src/EFCore/Metadata/Internal/Index.cs | 85 ++----- .../Internal/InternalEntityTypeBuilder.cs | 119 ++++++++- .../Metadata/Internal/InternalIndexBuilder.cs | 53 +---- src/EFCore/Properties/CoreStrings.Designer.cs | 32 ++- src/EFCore/Properties/CoreStrings.resx | 10 +- .../TestUtilities/CosmosTestStore.cs | 1 + .../Migrations/ModelSnapshotSqlServerTest.cs | 23 +- .../Internal/CSharpDbContextGeneratorTest.cs | 21 +- .../Internal/CSharpEntityTypeGeneratorTest.cs | 12 +- .../MigrationsTestBase.cs | 4 +- .../StoreGeneratedFixupRelationalTestBase.cs | 4 +- .../RelationalModelValidatorTest.cs | 37 +-- .../RelationalBuilderExtensionsTest.cs | 6 +- .../Metadata/RelationalModelTest.cs | 4 +- .../Internal/MigrationsModelDifferTest.cs | 26 +- .../RelationalEventIdTest.cs | 2 +- .../MigrationsInfrastructureSqlServerTest.cs | 6 +- .../SqlAzure/Model/AdventureWorksContext.cs | 63 ++--- .../SqlServerModelValidatorTest.cs | 12 +- .../MigrationsInfrastructureSqliteTest.cs | 6 +- .../Conventions/ConventionDispatcherTest.cs | 72 ------ .../IndexAttributeConventionTest.cs | 13 +- .../Metadata/Internal/EntityTypeTest.cs | 96 +++++++- .../Internal/InternalEntityTypeBuilderTest.cs | 151 ++++++++++++ .../Internal/InternalIndexBuilderTest.cs | 34 --- .../ModelBuilding/ModelBuilderGenericTest.cs | 3 - .../ModelBuilderNonGenericTest.cs | 3 - .../ModelBuilding/ModelBuilderTestBase.cs | 1 - .../ModelBuilding/NonRelationshipTestBase.cs | 3 +- 55 files changed, 1067 insertions(+), 618 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 5b404c0563e..11fb0a3e56e 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -750,21 +750,26 @@ protected virtual void GenerateIndex( stringBuilder .AppendLine() .Append(builderName) - .Append(".HasIndex(") - .Append(string.Join(", ", index.Properties.Select(p => Code.Literal(p.Name)))) - .Append(")"); + .Append(".HasIndex("); - using (stringBuilder.Indent()) + if (index.Name == null) { - if (index.Name != null) - { - stringBuilder - .AppendLine() - .Append(".HasName(") - .Append(Code.Literal(index.Name)) - .Append(")"); - } + stringBuilder + .Append(string.Join(", ", index.Properties.Select(p => Code.Literal(p.Name)))); + } + else + { + stringBuilder + .Append("new[] { ") + .Append(string.Join(", ", index.Properties.Select(p => Code.Literal(p.Name)))) + .Append(" }, ") + .Append(Code.Literal(index.Name)); + } + stringBuilder.Append(")"); + + using (stringBuilder.Indent()) + { if (index.IsUnique) { stringBuilder @@ -792,6 +797,8 @@ protected virtual void GenerateIndexAnnotations( annotations, CSharpModelGenerator.IgnoredIndexAnnotations); + GenerateFluentApiForAnnotation( + ref annotations, RelationalAnnotationNames.Name, nameof(RelationalIndexBuilderExtensions.HasDatabaseName), stringBuilder); GenerateFluentApiForAnnotation( ref annotations, RelationalAnnotationNames.Filter, nameof(RelationalIndexBuilderExtensions.HasFilter), stringBuilder); diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index c9e186a2f11..cda204de21d 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -586,22 +586,19 @@ private void GenerateTableName(IEntityType entityType) private void GenerateIndex(IIndex index) { - var lines = new List { $".{nameof(EntityTypeBuilder.HasIndex)}({_code.Lambda(index.Properties)})" }; - var annotations = index.GetAnnotations().ToList(); + var lines = new List { + $".{nameof(EntityTypeBuilder.HasIndex)}" + + $"({_code.Lambda(index.Properties)}, " + + $"{_code.Literal(index.GetDatabaseName())})" }; + RemoveAnnotation(ref annotations, RelationalAnnotationNames.Name); + foreach (var annotation in CSharpModelGenerator.IgnoredIndexAnnotations) { RemoveAnnotation(ref annotations, annotation); } - if (index.Name != null) - { - lines.Add( - $".{nameof(IndexBuilder.HasName)}" + - $"({_code.Literal(index.GetDatabaseName())})"); - } - if (index.IsUnique) { lines.Add($".{nameof(IndexBuilder.IsUnique)}()"); diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 6bebc055b35..0bcbd093fb0 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -617,14 +617,7 @@ protected virtual IndexBuilder VisitUniqueConstraint( } var propertyNames = uniqueConstraint.Columns.Select(GetPropertyName).ToArray(); - var indexBuilder = builder.HasIndex(propertyNames).IsUnique(); - - if (!string.IsNullOrEmpty(uniqueConstraint.Name) - && uniqueConstraint.Name != indexBuilder.Metadata.GetDefaultDatabaseName()) - { - indexBuilder.HasName(uniqueConstraint.Name); - } - + var indexBuilder = builder.HasIndex(propertyNames, uniqueConstraint.Name).IsUnique(); indexBuilder.Metadata.AddAnnotations(uniqueConstraint.GetAnnotations()); return indexBuilder; @@ -674,7 +667,10 @@ protected virtual IndexBuilder VisitIndex([NotNull] EntityTypeBuilder builder, [ } var propertyNames = index.Columns.Select(GetPropertyName).ToArray(); - var indexBuilder = builder.HasIndex(propertyNames) + var indexBuilder = + index.Name == null + ? builder.HasIndex(propertyNames) + : builder.HasIndex(propertyNames, index.Name) .IsUnique(index.IsUnique); if (index.Filter != null) @@ -682,12 +678,6 @@ protected virtual IndexBuilder VisitIndex([NotNull] EntityTypeBuilder builder, [ indexBuilder.HasFilter(index.Filter); } - if (!string.IsNullOrEmpty(index.Name) - && index.Name != indexBuilder.Metadata.GetDefaultDatabaseName()) - { - indexBuilder.HasName(index.Name); - } - indexBuilder.Metadata.AddAnnotations(index.GetAnnotations()); return indexBuilder; @@ -803,8 +793,10 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode var principalKey = principalEntityType.FindKey(principalProperties); if (principalKey == null) { - var index = principalEntityType.FindIndex(principalProperties.AsReadOnly()); - if (index?.IsUnique == true) + var index = principalEntityType.GetIndexes() + .Where(i => i.Properties.SequenceEqual(principalProperties) && i.IsUnique) + .FirstOrDefault(); + if (index != null) { // ensure all principal properties are non-nullable even if the columns // are nullable on the database. EF's concept of a key requires this. @@ -844,9 +836,10 @@ protected virtual IMutableForeignKey VisitForeignKey([NotNull] ModelBuilder mode dependentProperties, principalKey, principalEntityType); var dependentKey = dependentEntityType.FindKey(dependentProperties); - var dependentIndex = dependentEntityType.FindIndex(dependentProperties); + var dependentIndexes = dependentEntityType.GetIndexes() + .Where(i => i.Properties.SequenceEqual(dependentProperties)); newForeignKey.IsUnique = dependentKey != null - || dependentIndex?.IsUnique == true; + || dependentIndexes.Any(i => i.IsUnique); if (!string.IsNullOrEmpty(foreignKey.Name) && foreignKey.Name != newForeignKey.GetDefaultName()) diff --git a/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs index c9efc627405..f708a9478c5 100644 --- a/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalIndexBuilderExtensions.cs @@ -21,9 +21,36 @@ public static class RelationalIndexBuilderExtensions /// The builder for the index being configured. /// The name of the index. /// A builder to further configure the index. - [Obsolete("Use IndexBuilder.HasName() instead.")] + public static IndexBuilder HasDatabaseName([NotNull] this IndexBuilder indexBuilder, [CanBeNull] string name) + { + indexBuilder.Metadata.SetDatabaseName(name); + + return indexBuilder; + } + + /// + /// Configures the name of the index in the database when targeting a relational database. + /// + /// The builder for the index being configured. + /// The name of the index. + /// A builder to further configure the index. + [Obsolete("Use HasDatabaseName() instead.")] public static IndexBuilder HasName([NotNull] this IndexBuilder indexBuilder, [CanBeNull] string name) - => indexBuilder.HasName(name); + => HasDatabaseName(indexBuilder, name); + + /// + /// Configures the name of the index in the database when targeting a relational database. + /// + /// The entity type being configured. + /// The builder for the index being configured. + /// The name of the index. + /// A builder to further configure the index. + public static IndexBuilder HasDatabaseName([NotNull] this IndexBuilder indexBuilder, [CanBeNull] string name) + { + indexBuilder.Metadata.SetDatabaseName(name); + + return indexBuilder; + } /// /// Configures the name of the index in the database when targeting a relational database. @@ -32,7 +59,7 @@ public static IndexBuilder HasName([NotNull] this IndexBuilder indexBuilder, [Ca /// The builder for the index being configured. /// The name of the index. /// A builder to further configure the index. - [Obsolete("Use IndexBuilder.HasName() instead.")] + [Obsolete("Use HasDatabaseName() instead.")] public static IndexBuilder HasName([NotNull] this IndexBuilder indexBuilder, [CanBeNull] string name) => indexBuilder.HasName(name); @@ -46,10 +73,43 @@ public static IndexBuilder HasName([NotNull] this IndexBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - [Obsolete("Use IConventionIndexBuilder.HasName() instead.")] + public static IConventionIndexBuilder HasDatabaseName( + [NotNull] this IConventionIndexBuilder indexBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + { + if (indexBuilder.CanSetDatabaseName(name, fromDataAnnotation)) + { + indexBuilder.Metadata.SetDatabaseName(name, fromDataAnnotation); + return indexBuilder; + } + + return null; + } + + /// + /// Configures the name of the index in the database when targeting a relational database. + /// + /// The builder for the index being configured. + /// The name of the index. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + [Obsolete("Use HasDatabaseName() instead.")] public static IConventionIndexBuilder HasName( [NotNull] this IConventionIndexBuilder indexBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) - => indexBuilder.HasName(name, fromDataAnnotation); + => indexBuilder.HasDatabaseName(name, fromDataAnnotation); + + /// + /// Returns a value indicating whether the given name can be set for the index. + /// + /// The builder for the index being configured. + /// The name of the index. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given name can be set for the index. + public static bool CanSetDatabaseName( + [NotNull] this IConventionIndexBuilder indexBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + => indexBuilder.CanSetAnnotation(RelationalAnnotationNames.Name, name, fromDataAnnotation); /// /// Returns a value indicating whether the given name can be set for the index. @@ -58,10 +118,10 @@ public static IConventionIndexBuilder HasName( /// The name of the index. /// Indicates whether the configuration was specified using a data annotation. /// if the given name can be set for the index. - [Obsolete("Use IConventionIndexBuilder.CanSetName() instead.")] + [Obsolete("Use CanSetDatabaseName() instead.")] public static bool CanSetName( [NotNull] this IConventionIndexBuilder indexBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) - => indexBuilder.CanSetName(name, fromDataAnnotation); + => CanSetDatabaseName(indexBuilder, name, fromDataAnnotation); /// /// Configures the filter expression for the index. diff --git a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs index febd1635574..a36c7732a2d 100644 --- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs @@ -19,34 +19,38 @@ namespace Microsoft.EntityFrameworkCore public static class RelationalIndexExtensions { /// - /// Returns the name for this index. + /// Returns the name of the index on the database. /// /// The index. - /// The name for this index. + /// The name of the index on the database. public static string GetDatabaseName([NotNull] this IIndex index) - => index.Name ?? index.GetDefaultDatabaseName(); + => (string)index[RelationalAnnotationNames.Name] + ?? index.Name + ?? index.GetDefaultDatabaseName(); /// - /// Returns the name for this index. + /// Returns the name of the index on the database. /// /// The index. - /// The name for this index. + /// The name of the index on the database. [Obsolete("Use GetDatabaseName() instead")] public static string GetName([NotNull] this IIndex index) => GetDatabaseName(index); /// - /// Returns the name for this index. + /// Returns the name of the index on the database. /// /// The index. /// The table name. /// The schema. - /// The name for this index. + /// The name of the index on the database. public static string GetDatabaseName( [NotNull] this IIndex index, [NotNull] string tableName, [CanBeNull] string schema) - => index.Name ?? index.GetDefaultDatabaseName(tableName, schema); + => (string)index[RelationalAnnotationNames.Name] + ?? index.Name + ?? index.GetDefaultDatabaseName(tableName, schema); /// /// Returns the default name that would be used for this index. @@ -123,33 +127,70 @@ public static string GetDefaultDatabaseName( } /// - /// Sets the index name. + /// Sets the name of the index on the database. /// /// The index. /// The value to set. - [Obsolete("Use IMutableIndex.Name instead.")] + public static void SetDatabaseName([NotNull] this IMutableIndex index, [CanBeNull] string name) + { + index.SetOrRemoveAnnotation( + RelationalAnnotationNames.Name, + Check.NullButNotEmpty(name, nameof(name))); + } + + /// + /// Sets the name of the index on the database. + /// + /// The index. + /// The value to set. + [Obsolete("Use SetDatabaseName() instead.")] public static void SetName([NotNull] this IMutableIndex index, [CanBeNull] string name) - => index.Name = Check.NullButNotEmpty(name, nameof(name)); + => SetDatabaseName(index, name); + + /// + /// Sets the name of the index on the database. + /// + /// The index. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string SetDatabaseName([NotNull] this IConventionIndex index, [CanBeNull] string name, bool fromDataAnnotation = false) + { + index.SetOrRemoveAnnotation( + RelationalAnnotationNames.Name, + Check.NullButNotEmpty(name, nameof(name)), + fromDataAnnotation); + + return name; + } /// - /// Sets the index name. + /// Sets the name of the index on the database. /// /// The index. /// The value to set. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. - [Obsolete("Use IConventionIndex.SetName() instead.")] + [Obsolete("Use SetDatabaseName() instead.")] public static string SetName([NotNull] this IConventionIndex index, [CanBeNull] string name, bool fromDataAnnotation = false) - => index.SetName(Check.NullButNotEmpty(name, nameof(name)), fromDataAnnotation); + => SetDatabaseName(index, name, fromDataAnnotation); + + /// + /// Gets the for the name of the index on the database. + /// + /// The index. + /// The for the name of the index on the database. + public static ConfigurationSource? GetDatabaseNameConfigurationSource([NotNull] this IConventionIndex index) + => index.FindAnnotation(RelationalAnnotationNames.Name)?.GetConfigurationSource(); /// - /// Gets the for the index name. + /// Gets the for the name of the index on the database. /// /// The index. - /// The for the index name. - [Obsolete("Use IConventionIndex.GetNameConfigurationSource() instead.")] + /// The for the name of the index on the database. + [Obsolete("Use GetDatabaseNameConfigurationSource() instead.")] public static ConfigurationSource? GetNameConfigurationSource([NotNull] this IConventionIndex index) - => index.GetNameConfigurationSource(); + => GetDatabaseNameConfigurationSource(index); /// /// Returns the index filter expression. diff --git a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs index 8e52a822944..06a96f1615b 100644 --- a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs @@ -349,10 +349,10 @@ protected virtual bool AreCompatible( private static string TryUniquify( IConventionIndex index, string indexName, Dictionary indexes, int maxLength) { - if (index.Builder.CanSetName(null)) + if (index.Builder.CanSetDatabaseName(null)) { indexName = Uniquifier.Uniquify(indexName, indexes, maxLength); - index.Builder.HasName(indexName); + index.Builder.HasDatabaseName(indexName); return indexName; } diff --git a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs index f3818a60b25..0f2d955f941 100644 --- a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs @@ -537,7 +537,12 @@ public static IConventionProperty AddIndexerProperty( } /// - /// Gets the index defined on the given property. Returns null if no index is defined. + /// + /// Gets the unnamed index defined on the given property. Returns if no such index is defined. + /// + /// + /// Named indexes will not be returned even if the list of properties matches. + /// /// /// The entity type. /// The property to find the index on. diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index 373bdd7f95f..c84a30a2137 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -130,7 +130,7 @@ public static bool IsAssignableFrom([NotNull] this IEntityType entityType, [NotN /// /// Returns the closest entity type that is a parent of both given entity types. If one of the given entities is - /// a parent of the other, that parent is returned. Returns null if the two entity types aren't in the same hierarchy. + /// a parent of the other, that parent is returned. Returns if the two entity types aren't in the same hierarchy. /// /// An entity type. /// Another entity type. @@ -663,7 +663,12 @@ public static IProperty FindDeclaredProperty([NotNull] this IEntityType entityTy => entityType.AsEntityType().FindDeclaredProperty(name); /// - /// Gets the index defined on the given property. Returns null if no index is defined. + /// + /// Gets the unnamed index defined on the given property. Returns if no such index is defined. + /// + /// + /// Named indexes will not be returned even if the list of properties matches. + /// /// /// The entity type. /// The property to find the index on. diff --git a/src/EFCore/Extensions/IndexExtensions.cs b/src/EFCore/Extensions/IndexExtensions.cs index 79ddbd74a83..294657802b4 100644 --- a/src/EFCore/Extensions/IndexExtensions.cs +++ b/src/EFCore/Extensions/IndexExtensions.cs @@ -69,6 +69,9 @@ public static string ToDebugString( ? p.DeclaringEntityType.DisplayName() + "." + p.Name : p.Name)); + builder.Append(" " + + index.Name ?? ""); + if (index.IsUnique) { builder.Append(" Unique"); diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index a0f9a000dfa..e988e6bbeb0 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -95,7 +95,7 @@ public virtual KeyBuilder HasKey([NotNull] params string[] propertyNames) /// /// Creates an alternate key in the model for this entity type if one does not already exist over the specified - /// properties. This will force the properties to be read-only. Use to specify uniqueness + /// properties. This will force the properties to be read-only. Use to specify uniqueness /// in the model that does not force properties to be read-only. /// /// The names of the properties that make up the key. @@ -258,8 +258,10 @@ public virtual EntityTypeBuilder HasQueryFilter([CanBeNull] LambdaExpression fil } /// - /// Configures an index on the specified properties. If there is an existing index on the given - /// set of properties, then the existing index will be returned for configuration. + /// Configures an unnamed index on the specified properties. + /// If there is an existing unnamed index on the given + /// list of properties, then the existing index will be + /// returned for configuration. /// /// The names of the properties that make up the index. /// An object that can be used to configure the index. @@ -267,6 +269,20 @@ public virtual IndexBuilder HasIndex([NotNull] params string[] propertyNames) => new IndexBuilder( Builder.HasIndex(Check.NotEmpty(propertyNames, nameof(propertyNames)), ConfigurationSource.Explicit).Metadata); + /// + /// Configures an index on the specified properties and with the given name. + /// If there is an existing index on the given list of properties and with + /// the given name, then the existing index will be returned for configuration. + /// + /// The names of the properties that make up the index. + /// The name to assign to the index. + /// An object that can be used to configure the index. + public virtual IndexBuilder HasIndex( + [NotNull] string[] propertyNames, + [CanBeNull] string name) + => new IndexBuilder( + Builder.HasIndex(Check.NotEmpty(propertyNames, nameof(propertyNames)), name, ConfigurationSource.Explicit).Metadata); + /// /// /// Configures a relationship where the target entity is owned by (or part of) this entity. diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 92b7ae20a7c..7833c28159f 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -216,8 +216,9 @@ public virtual EntityTypeBuilder ToQuery([NotNull] Expression - /// Configures an index on the specified properties. If there is an existing index on the given - /// set of properties, then the existing index will be returned for configuration. + /// Configures an unnamed index on the specified properties. + /// If there is an existing index on the given list of properties, + /// then the existing index will be returned for configuration. /// /// /// @@ -237,8 +238,35 @@ public virtual IndexBuilder HasIndex([NotNull] Expression - /// Configures an index on the specified properties. If there is an existing index on the given - /// set of properties, then the existing index will be returned for configuration. + /// Configures an index on the specified properties with the given name. + /// If there is an existing index on the given list of properties and with + /// the given name, then the existing index will be returned for configuration. + /// + /// + /// + /// A lambda expression representing the property(s) to be included in the index + /// (blog => blog.Url). + /// + /// + /// If the index is made up of multiple properties then specify an anonymous type including the + /// properties (post => new { post.Title, post.BlogId }). + /// + /// + /// The name to assign to the index. + /// An object that can be used to configure the index. + public virtual IndexBuilder HasIndex( + [NotNull] Expression> indexExpression, + [CanBeNull] string name) + => new IndexBuilder( + Builder.HasIndex( + Check.NotNull(indexExpression, nameof(indexExpression)).GetMemberAccessList(), + name, + ConfigurationSource.Explicit).Metadata); + + /// + /// Configures an unnamed index on the specified properties. + /// If there is an existing index on the given list of properties, + /// then the existing index will be returned for configuration. /// /// The names of the properties that make up the index. /// An object that can be used to configure the index. @@ -248,6 +276,23 @@ public virtual IndexBuilder HasIndex([NotNull] Expression + /// Configures an index on the specified properties with the given name. + /// If there is an existing index on the given list of properties and with + /// the given name, then the existing index will be returned for configuration. + /// + /// The names of the properties that make up the index. + /// The name to assign to the index. + /// An object that can be used to configure the index. + public new virtual IndexBuilder HasIndex( + [NotNull] string[] propertyNames, + [CanBeNull] string name) + => new IndexBuilder( + Builder.HasIndex( + Check.NotEmpty(propertyNames, nameof(propertyNames)), + name, + ConfigurationSource.Explicit).Metadata); + /// /// /// Configures a relationship where the target entity is owned by (or part of) this entity. diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index db036a17911..5b5b81f1f98 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -229,8 +229,9 @@ IConventionServicePropertyBuilder ServiceProperty( bool CanRemoveKey(bool fromDataAnnotation = false); /// - /// Configures an index on the specified properties. If there is an existing index on the given - /// set of properties, then the existing index will be returned for configuration. + /// Configures an index on the specified properties. + /// If there is an existing index on the given list of properties, + /// then the existing index will be returned for configuration. /// /// The properties that make up the index. /// Indicates whether the configuration was specified using a data annotation. @@ -241,6 +242,23 @@ IConventionServicePropertyBuilder ServiceProperty( IConventionIndexBuilder HasIndex( [NotNull] IReadOnlyList properties, bool fromDataAnnotation = false); + /// + /// Configures an index on the specified properties, with the specified name. + /// If there is an existing index on the given set of properties and with the given name, + /// then the existing index will be returned for configuration. + /// + /// The properties that make up the index. + /// The name of the index. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the index if it exists on the entity type, + /// otherwise. + /// + IConventionIndexBuilder HasIndex( + [NotNull] IReadOnlyList properties, + [NotNull] string name, + bool fromDataAnnotation = false); + /// /// Removes an index from this entity type. /// diff --git a/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs b/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs index 9dce6b218d6..c5864342662 100644 --- a/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using JetBrains.Annotations; - namespace Microsoft.EntityFrameworkCore.Metadata.Builders { /// @@ -40,26 +38,5 @@ public interface IConventionIndexBuilder : IConventionAnnotatableBuilder /// Indicates whether the configuration was specified using a data annotation. /// if the index uniqueness can be configured. bool CanSetIsUnique(bool? unique, bool fromDataAnnotation = false); - - /// - /// Configures the name of this index. - /// - /// The name of the index which can be - /// to indicate that a unique name should be generated. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the name is unchanged, - /// otherwise. - /// - IConventionIndexBuilder HasName([CanBeNull] string name, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the name can be configured - /// from the current configuration source. - /// - /// The name of the index. - /// Indicates whether the configuration was specified using a data annotation. - /// if the name can be configured. - bool CanSetName([CanBeNull] string name, bool fromDataAnnotation = false); } } diff --git a/src/EFCore/Metadata/Builders/IndexBuilder.cs b/src/EFCore/Metadata/Builders/IndexBuilder.cs index 6b0eca95631..ab92e068f62 100644 --- a/src/EFCore/Metadata/Builders/IndexBuilder.cs +++ b/src/EFCore/Metadata/Builders/IndexBuilder.cs @@ -74,21 +74,6 @@ public virtual IndexBuilder IsUnique(bool unique = true) return this; } - /// - /// Configures the name of this index. - /// - /// - /// The name of this index which can be - /// to indicate that a unique name should be generated. - /// - /// The same builder instance so that multiple configuration calls can be chained. - public virtual IndexBuilder HasName([CanBeNull] string name) - { - Builder.HasName(name, ConfigurationSource.Explicit); - - return this; - } - #region Hidden System.Object members /// diff --git a/src/EFCore/Metadata/Builders/IndexBuilder`.cs b/src/EFCore/Metadata/Builders/IndexBuilder`.cs index cc5b1fe290c..d964c742a0c 100644 --- a/src/EFCore/Metadata/Builders/IndexBuilder`.cs +++ b/src/EFCore/Metadata/Builders/IndexBuilder`.cs @@ -48,16 +48,5 @@ public IndexBuilder([NotNull] IMutableIndex index) /// The same builder instance so that multiple configuration calls can be chained. public new virtual IndexBuilder IsUnique(bool unique = true) => (IndexBuilder)base.IsUnique(unique); - - /// - /// Configures the name of this index. - /// - /// - /// The name of this index which can be - /// to indicate that a unique name should be generated. - /// - /// The same builder instance so that multiple configuration calls can be chained. - public new virtual IndexBuilder HasName([CanBeNull] string name) - => (IndexBuilder)base.HasName(name); } } diff --git a/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs b/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs index b9aefa58816..bbddf9209b7 100644 --- a/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs @@ -92,14 +92,14 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, indexProperties.Add(property); } - var indexBuilder = entityType.Builder.HasIndex(indexProperties, fromDataAnnotation: true); + var indexBuilder = indexAttribute.Name == null + ? entityType.Builder.HasIndex( + indexProperties, fromDataAnnotation: true) + : entityType.Builder.HasIndex( + indexProperties, indexAttribute.Name, fromDataAnnotation: true); + if (indexBuilder != null) { - if (indexAttribute.Name != null) - { - indexBuilder.HasName(indexAttribute.Name, fromDataAnnotation: true); - } - if (indexAttribute.GetIsUnique().HasValue) { indexBuilder.IsUnique(indexAttribute.GetIsUnique().Value, fromDataAnnotation: true); diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs index e8d36daab53..f3f53c9bfff 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs @@ -116,7 +116,6 @@ public abstract IConventionIndex OnIndexRemoved( [NotNull] IConventionEntityTypeBuilder entityTypeBuilder, [NotNull] IConventionIndex index); public abstract bool? OnIndexUniquenessChanged([NotNull] IConventionIndexBuilder indexBuilder); - public abstract string OnIndexNameChanged([NotNull] IConventionIndexBuilder indexBuilder); public abstract IConventionKeyBuilder OnKeyAdded([NotNull] IConventionKeyBuilder keyBuilder); public abstract IConventionAnnotation OnKeyAnnotationChanged( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs index 6e0523d0b50..2ccc197bae9 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs @@ -182,12 +182,6 @@ public override IConventionIndex OnIndexRemoved( return indexBuilder.Metadata.IsUnique; } - public override string OnIndexNameChanged(IConventionIndexBuilder indexBuilder) - { - Add(new OnIndexNameChangedNode(indexBuilder)); - return indexBuilder.Metadata.Name; - } - public override IConventionAnnotation OnIndexAnnotationChanged( IConventionIndexBuilder indexBuilder, string name, @@ -867,19 +861,6 @@ public override void Run(ConventionDispatcher dispatcher) => dispatcher._immediateConventionScope.OnIndexUniquenessChanged(IndexBuilder); } - private sealed class OnIndexNameChangedNode : ConventionNode - { - public OnIndexNameChangedNode(IConventionIndexBuilder indexBuilder) - { - IndexBuilder = indexBuilder; - } - - public IConventionIndexBuilder IndexBuilder { get; } - - public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnIndexNameChanged(IndexBuilder); - } - private sealed class OnIndexAnnotationChangedNode : ConventionNode { public OnIndexAnnotationChangedNode( diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index bfd892d3ef4..be8a37b18c8 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -1042,34 +1042,6 @@ public override IConventionIndex OnIndexRemoved(IConventionEntityTypeBuilder ent return _boolConventionContext.Result; } - public override string OnIndexNameChanged(IConventionIndexBuilder indexBuilder) - { - using (_dispatcher.DelayConventions()) - { - _stringConventionContext.ResetState(indexBuilder.Metadata.Name); - foreach (var indexConvention in _conventionSet.IndexNameChangedConventions) - { - if (indexBuilder.Metadata.Builder == null) - { - return null; - } - - indexConvention.ProcessIndexNameChanged(indexBuilder, _stringConventionContext); - if (_stringConventionContext.ShouldStopProcessing()) - { - return _stringConventionContext.Result; - } - } - } - - if (indexBuilder.Metadata.Builder == null) - { - return null; - } - - return _stringConventionContext.Result; - } - public override IConventionAnnotation OnIndexAnnotationChanged( IConventionIndexBuilder indexBuilder, string name, diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index 85f00a2a098..d0a198fb4d7 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -473,15 +473,6 @@ public virtual IConventionIndex OnIndexRemoved( public virtual bool? OnIndexUniquenessChanged([NotNull] IConventionIndexBuilder indexBuilder) => _scope.OnIndexUniquenessChanged(indexBuilder); - /// - /// 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 virtual string OnIndexNameChanged([NotNull] IConventionIndexBuilder indexBuilder) - => _scope.OnIndexNameChanged(indexBuilder); - /// /// 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/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index b201a2e1846..c299f5ac77a 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -294,20 +294,46 @@ IConventionSkipNavigation AddSkipNavigation( IConventionSkipNavigation RemoveSkipNavigation([NotNull] IConventionSkipNavigation navigation); /// - /// Adds an index to this entity type. + /// Adds an unnamed index to this entity type. /// /// The properties that are to be indexed. /// Indicates whether the configuration was specified using a data annotation. /// The newly created index. - IConventionIndex AddIndex([NotNull] IReadOnlyList properties, bool fromDataAnnotation = false); + IConventionIndex AddIndex( + [NotNull] IReadOnlyList properties, + bool fromDataAnnotation = false); + + /// + /// Adds a named index to this entity type. + /// + /// The properties that are to be indexed. + /// The name of the index. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created index. + IConventionIndex AddIndex( + [NotNull] IReadOnlyList properties, + [CanBeNull] string name, + bool fromDataAnnotation = false); /// - /// Gets the index defined on the given properties. Returns if no index is defined. + /// + /// Gets the unnamed index defined on the given properties. Returns if no index is defined. + /// + /// + /// Named indexes will not be returned even if the list of properties matches. + /// /// /// The properties to find the index on. /// The index, or if none is found. new IConventionIndex FindIndex([NotNull] IReadOnlyList properties); + /// + /// Gets the index with the given name. Returns if no such index exists. + /// + /// The name of the index to find. + /// The index, or if none is found. + new IConventionIndex FindIndex([NotNull] string name); + /// /// Gets the indexes defined on this entity type. /// diff --git a/src/EFCore/Metadata/IConventionIndex.cs b/src/EFCore/Metadata/IConventionIndex.cs index 3b50dedbb85..ca5963d1185 100644 --- a/src/EFCore/Metadata/IConventionIndex.cs +++ b/src/EFCore/Metadata/IConventionIndex.cs @@ -54,20 +54,5 @@ public interface IConventionIndex : IIndex, IConventionAnnotatable /// /// The configuration source for . ConfigurationSource? GetIsUniqueConfigurationSource(); - - /// - /// Sets the name of the index which can be - /// to indicate that a unique name should be generated. - /// - /// The name of the index. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured name. - string SetName([CanBeNull] string name, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetNameConfigurationSource(); } } diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 02a9c63af9b..34bf150d0b4 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -125,12 +125,24 @@ IEnumerable GetDeclaredSkipNavigations() IEnumerable GetSkipNavigations(); /// - /// Gets the index defined on the given properties. Returns if no index is defined. + /// + /// Gets the unnamed index defined on the given properties. Returns if no such index is defined. + /// + /// + /// Named indexes will not be returned even if the list of properties matches. + /// /// /// The properties to find the index on. /// The index, or if none is found. IIndex FindIndex([NotNull] IReadOnlyList properties); + /// + /// Gets the index with the given name. Returns if no such index exists. + /// + /// The name of the index to find. + /// The index, or if none is found. + IIndex FindIndex([NotNull] string name); + /// /// Gets the indexes defined on this entity type. /// diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index 45aa8e218e9..1843f13fc25 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -213,19 +213,40 @@ IMutableSkipNavigation AddSkipNavigation( IMutableSkipNavigation RemoveSkipNavigation([NotNull] IMutableSkipNavigation navigation); /// - /// Adds an index to this entity type. + /// Adds an unnamed index to this entity type. /// /// The properties that are to be indexed. /// The newly created index. IMutableIndex AddIndex([NotNull] IReadOnlyList properties); /// - /// Gets the index defined on the given properties. Returns if no index is defined. + /// Adds a named index to this entity type. + /// + /// The properties that are to be indexed. + /// The name of the index. + /// The newly created index. + IMutableIndex AddIndex( + [NotNull] IReadOnlyList properties, [NotNull] string name); + + /// + /// + /// Gets the unnamed index defined on the given properties. Returns if no such index is defined. + /// + /// + /// Named indexes will not be returned even if the list of properties matches. + /// /// /// The properties to find the index on. /// The index, or if none is found. new IMutableIndex FindIndex([NotNull] IReadOnlyList properties); + /// + /// Gets the index with the given name. Returns if no such index exists. + /// + /// The name of the index. + /// The index, or if none is found. + new IMutableIndex FindIndex([NotNull] string name); + /// /// Gets the indexes defined on this entity type. /// diff --git a/src/EFCore/Metadata/IMutableIndex.cs b/src/EFCore/Metadata/IMutableIndex.cs index cbd341409e9..833726feb5a 100644 --- a/src/EFCore/Metadata/IMutableIndex.cs +++ b/src/EFCore/Metadata/IMutableIndex.cs @@ -22,12 +22,6 @@ public interface IMutableIndex : IIndex, IMutableAnnotatable /// new bool IsUnique { get; set; } - /// - /// Gets or sets the name of the index which can be - /// to indicate that a unique name should be generated. - /// - new string Name { get; [param: CanBeNull] set; } - /// /// Gets the properties that this index is defined on. /// diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index f3a1a406c69..6c2c49deecf 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -39,9 +39,12 @@ private readonly SortedDictionary _navigations private readonly SortedDictionary _skipNavigations = new SortedDictionary(StringComparer.Ordinal); - private readonly SortedDictionary, Index> _indexes + private readonly SortedDictionary, Index> _unnamedIndexes = new SortedDictionary, Index>(PropertyListComparer.Instance); + private readonly SortedDictionary _namedIndexes + = new SortedDictionary(StringComparer.Ordinal); + private readonly SortedDictionary _properties; private readonly SortedDictionary, Key> _keys @@ -1847,8 +1850,20 @@ public virtual IEnumerable GetDerivedReferencingSkipNavigations( public virtual Index AddIndex( [NotNull] Property property, ConfigurationSource configurationSource) + => AddIndex(new[] { property }, configurationSource); + + /// + /// 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 virtual Index AddIndex( + [NotNull] Property property, + [CanBeNull] string name, + ConfigurationSource configurationSource) => AddIndex( - new[] { property }, configurationSource); + new[] { property }, name, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1863,6 +1878,61 @@ public virtual Index AddIndex( Check.NotEmpty(properties, nameof(properties)); Check.HasNoNulls(properties, nameof(properties)); + CheckIndexProperties(properties); + + var duplicateIndex = FindIndexesInHierarchy(properties).FirstOrDefault(); + if (duplicateIndex != null) + { + throw new InvalidOperationException( + CoreStrings.DuplicateIndex(properties.Format(), this.DisplayName(), duplicateIndex.DeclaringEntityType.DisplayName())); + } + + var index = new Index(properties, this, configurationSource); + _unnamedIndexes.Add(properties, index); + + UpdatePropertyIndexes(properties, index); + + return (Index)Model.ConventionDispatcher.OnIndexAdded(index.Builder)?.Metadata; + } + + /// + /// 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 virtual Index AddIndex( + [NotNull] IReadOnlyList properties, + [NotNull] string name, + ConfigurationSource configurationSource) + { + Check.NotEmpty(properties, nameof(properties)); + Check.HasNoNulls(properties, nameof(properties)); + Check.NotEmpty(name, nameof(name)); + + CheckIndexProperties(properties); + + var duplicateIndex = FindIndexesInHierarchy(name).FirstOrDefault(); + if (duplicateIndex != null) + { + throw new InvalidOperationException( + CoreStrings.DuplicateNamedIndex( + name, + properties.Format(), + this.DisplayName(), + duplicateIndex.DeclaringEntityType.DisplayName())); + } + + var index = new Index(properties, name, this, configurationSource); + _namedIndexes.Add(name, index); + + UpdatePropertyIndexes(properties, index); + + return (Index)Model.ConventionDispatcher.OnIndexAdded(index.Builder)?.Metadata; + } + + private void CheckIndexProperties([NotNull] IReadOnlyList properties) + { for (var i = 0; i < properties.Count; i++) { var property = properties[i]; @@ -1880,17 +1950,10 @@ public virtual Index AddIndex( throw new InvalidOperationException(CoreStrings.IndexPropertiesWrongEntity(properties.Format(), this.DisplayName())); } } + } - var duplicateIndex = FindIndexesInHierarchy(properties).FirstOrDefault(); - if (duplicateIndex != null) - { - throw new InvalidOperationException( - CoreStrings.DuplicateIndex(properties.Format(), this.DisplayName(), duplicateIndex.DeclaringEntityType.DisplayName())); - } - - var index = new Index(properties, this, configurationSource); - _indexes.Add(properties, index); - + private void UpdatePropertyIndexes([NotNull] IReadOnlyList properties, [NotNull] Index index) + { foreach (var property in properties) { if (property.Indexes == null) @@ -1902,8 +1965,6 @@ public virtual Index AddIndex( property.Indexes.Add(index); } } - - return (Index)Model.ConventionDispatcher.OnIndexAdded(index.Builder)?.Metadata; } /// @@ -1913,8 +1974,7 @@ public virtual Index AddIndex( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Index FindIndex([NotNull] IProperty property) - => FindIndex( - new[] { property }); + => FindIndex(new[] { property }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1936,7 +1996,21 @@ public virtual Index FindIndex([NotNull] IReadOnlyList properties) /// 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 IEnumerable GetDeclaredIndexes() => _indexes.Values; + public virtual Index FindIndex([NotNull] string name) + { + Check.NotEmpty(name, nameof(name)); + + return FindDeclaredIndex(name) ?? _baseType?.FindIndex(name); + } + + /// + /// 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 virtual IEnumerable GetDeclaredIndexes() => + _unnamedIndexes.Values.Concat(_namedIndexes.Values); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1954,7 +2028,18 @@ public virtual IEnumerable GetDerivedIndexes() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Index FindDeclaredIndex([NotNull] IReadOnlyList properties) - => _indexes.TryGetValue(Check.NotEmpty(properties, nameof(properties)), out var index) + => _unnamedIndexes.TryGetValue(Check.NotEmpty(properties, nameof(properties)), out var index) + ? index + : null; + + /// + /// 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 virtual Index FindDeclaredIndex([NotNull] string name) + => _namedIndexes.TryGetValue(name, out var index) ? index : null; @@ -1967,6 +2052,15 @@ public virtual Index FindDeclaredIndex([NotNull] IReadOnlyList proper public virtual IEnumerable FindDerivedIndexes([NotNull] IReadOnlyList properties) => GetDerivedTypes().Select(et => et.FindDeclaredIndex(properties)).Where(i => i != null); + /// + /// 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 virtual IEnumerable FindDerivedIndexes([NotNull] string name) + => GetDerivedTypes().Select(et => et.FindDeclaredIndex(Check.NotEmpty(name, nameof(name)))).Where(i => i != null); + /// /// 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 @@ -1976,6 +2070,15 @@ public virtual IEnumerable FindDerivedIndexes([NotNull] IReadOnlyList FindIndexesInHierarchy([NotNull] IReadOnlyList properties) => ToEnumerable(FindIndex(properties)).Concat(FindDerivedIndexes(properties)); + /// + /// 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 virtual IEnumerable FindIndexesInHierarchy([NotNull] string name) + => ToEnumerable(FindIndex(name)).Concat(FindDerivedIndexes(name)); + /// /// 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 @@ -1992,6 +2095,22 @@ public virtual Index RemoveIndex([NotNull] IReadOnlyList properties) : RemoveIndex(index); } + /// + /// 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 virtual Index RemoveIndex([NotNull] string name) + { + Check.NotEmpty(name, nameof(name)); + + var index = FindDeclaredIndex(name); + return index == null + ? null + : RemoveIndex(index); + } + /// /// 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 @@ -2003,10 +2122,21 @@ public virtual Index RemoveIndex([NotNull] Index index) Check.NotNull(index, nameof(index)); Check.DebugAssert(Builder != null, "Builder is null"); - if (!_indexes.Remove(index.Properties)) + if (index.Name == null) { - throw new InvalidOperationException( - CoreStrings.IndexWrongType(index.Properties.Format(), this.DisplayName(), index.DeclaringEntityType.DisplayName())); + if (!_unnamedIndexes.Remove(index.Properties)) + { + throw new InvalidOperationException( + CoreStrings.IndexWrongType(index.Properties.Format(), this.DisplayName(), index.DeclaringEntityType.DisplayName())); + } + } + else + { + if (!_namedIndexes.Remove(index.Name)) + { + throw new InvalidOperationException( + CoreStrings.NamedIndexWrongType(index.Name, this.DisplayName())); + } } index.Builder = null; @@ -2032,7 +2162,7 @@ public virtual Index RemoveIndex([NotNull] Index index) /// 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 IEnumerable GetIndexes() => _baseType?.GetIndexes().Concat(_indexes.Values) ?? _indexes.Values; + public virtual IEnumerable GetIndexes() => _baseType?.GetIndexes().Concat(GetDeclaredIndexes()) ?? GetDeclaredIndexes(); #endregion @@ -3501,6 +3631,16 @@ IConventionSkipNavigation IConventionEntityType.RemoveSkipNavigation([NotNull] I IMutableIndex IMutableEntityType.AddIndex(IReadOnlyList properties) => AddIndex(properties.Cast().ToList(), ConfigurationSource.Explicit); + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableIndex IMutableEntityType.AddIndex(IReadOnlyList properties, string name) + => AddIndex(properties.Cast().ToList(), name, ConfigurationSource.Explicit); + /// /// 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 @@ -3513,6 +3653,20 @@ IConventionIndex IConventionEntityType.AddIndex(IReadOnlyList().ToList(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionIndex IConventionEntityType.AddIndex( + IReadOnlyList properties, string name, bool fromDataAnnotation) + => AddIndex( + properties.Cast().ToList(), + name, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// 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 @@ -3522,6 +3676,15 @@ IConventionIndex IConventionEntityType.AddIndex(IReadOnlyList properties) => FindIndex(properties); + /// + /// 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. + /// + [DebuggerStepThrough] + IIndex IEntityType.FindIndex(string name) => FindIndex(name); + /// /// 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 @@ -3531,6 +3694,15 @@ IConventionIndex IConventionEntityType.AddIndex(IReadOnlyList properties) => FindIndex(properties); + /// + /// 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. + /// + [DebuggerStepThrough] + IMutableIndex IMutableEntityType.FindIndex(string name) => FindIndex(name); + /// /// 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 @@ -3540,6 +3712,15 @@ IConventionIndex IConventionEntityType.AddIndex(IReadOnlyList properties) => FindIndex(properties); + /// + /// 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. + /// + [DebuggerStepThrough] + IConventionIndex IConventionEntityType.FindIndex(string name) => FindIndex(name); + /// /// 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/Internal/Index.cs b/src/EFCore/Metadata/Internal/Index.cs index a6e0cb4f459..1aca822d460 100644 --- a/src/EFCore/Metadata/Internal/Index.cs +++ b/src/EFCore/Metadata/Internal/Index.cs @@ -23,11 +23,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal public class Index : ConventionAnnotatable, IMutableIndex, IConventionIndex { private bool? _isUnique; - private string _name; private ConfigurationSource _configurationSource; private ConfigurationSource? _isUniqueConfigurationSource; - private ConfigurationSource? _nameConfigurationSource; // Warning: Never access these fields directly as access needs to be thread-safe private object _nullableValueFactory; @@ -42,12 +40,28 @@ public Index( [NotNull] IReadOnlyList properties, [NotNull] EntityType declaringEntityType, ConfigurationSource configurationSource) + : this(properties, null, declaringEntityType, configurationSource) + { + } + + /// + /// 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 Index( + [NotNull] IReadOnlyList properties, + [CanBeNull] string name, + [NotNull] EntityType declaringEntityType, + ConfigurationSource configurationSource) { Check.NotEmpty(properties, nameof(properties)); Check.HasNoNulls(properties, nameof(properties)); Check.NotNull(declaringEntityType, nameof(declaringEntityType)); Properties = properties; + Name = name; DeclaringEntityType = declaringEntityType; _configurationSource = configurationSource; @@ -62,6 +76,14 @@ public Index( /// public virtual IReadOnlyList Properties { [DebuggerStepThrough] 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. + /// + public virtual string Name { [DebuggerStepThrough] 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 @@ -141,44 +163,6 @@ public virtual bool IsUnique private static bool DefaultIsUnique => false; - /// - /// 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 virtual string Name - { - get => _name; - set => SetName(value, ConfigurationSource.Explicit); - } - - /// - /// 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 virtual string SetName([CanBeNull] string name, ConfigurationSource configurationSource) - { - var oldName = Name; - var isChanging = !string.Equals(oldName, name, StringComparison.Ordinal); - _name = name; - - if (name == null) - { - _nameConfigurationSource = null; - } - else - { - UpdateNameConfigurationSource(configurationSource); - } - - return isChanging - ? DeclaringEntityType.Model.ConventionDispatcher.OnIndexNameChanged(Builder) - : oldName; - } - /// /// 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 @@ -190,17 +174,6 @@ public virtual string SetName([CanBeNull] string name, ConfigurationSource confi private void UpdateIsUniqueConfigurationSource(ConfigurationSource configurationSource) => _isUniqueConfigurationSource = configurationSource.Max(_isUniqueConfigurationSource); - /// - /// 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 virtual ConfigurationSource? GetNameConfigurationSource() => _nameConfigurationSource; - - private void UpdateNameConfigurationSource(ConfigurationSource configurationSource) - => _nameConfigurationSource = configurationSource.Max(_nameConfigurationSource); - /// /// Runs the conventions when an annotation was set or removed. /// @@ -338,15 +311,5 @@ IConventionEntityType IConventionIndex.DeclaringEntityType [DebuggerStepThrough] bool? IConventionIndex.SetIsUnique(bool? unique, bool fromDataAnnotation) => SetIsUnique(unique, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// 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. - /// - [DebuggerStepThrough] - string IConventionIndex.SetName(string name, bool fromDataAnnotation) - => SetName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 57b02642127..049e92329c4 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -1653,9 +1653,9 @@ public virtual InternalEntityTypeBuilder HasBaseType( if (detachedIndexes != null) { - foreach (var indexBuilderTuple in detachedIndexes) + foreach (var detachedIndex in detachedIndexes) { - indexBuilderTuple.Attach(indexBuilderTuple.Metadata.DeclaringEntityType.Builder); + detachedIndex.Attach(detachedIndex.Metadata.DeclaringEntityType.Builder); } } @@ -2164,6 +2164,18 @@ private static void RemovePropertyIfUnused(Property property, ConfigurationSourc public virtual InternalIndexBuilder HasIndex([NotNull] IReadOnlyList propertyNames, ConfigurationSource configurationSource) => HasIndex(GetOrCreateProperties(propertyNames, configurationSource), configurationSource); + /// + /// 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 virtual InternalIndexBuilder HasIndex( + [NotNull] IReadOnlyList propertyNames, + [NotNull] string name, + ConfigurationSource configurationSource) + => HasIndex(GetOrCreateProperties(propertyNames, configurationSource), name, configurationSource); + /// /// 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 @@ -2174,6 +2186,18 @@ public virtual InternalIndexBuilder HasIndex( [NotNull] IReadOnlyList clrMembers, ConfigurationSource configurationSource) => HasIndex(GetOrCreateProperties(clrMembers, configurationSource), configurationSource); + /// + /// 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 virtual InternalIndexBuilder HasIndex( + [NotNull] IReadOnlyList clrMembers, + [NotNull] string name, + ConfigurationSource configurationSource) + => HasIndex(GetOrCreateProperties(clrMembers, configurationSource), name, configurationSource); + /// /// 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 @@ -2196,16 +2220,68 @@ public virtual InternalIndexBuilder HasIndex( } else if (existingIndex.DeclaringEntityType != Metadata) { - return existingIndex.DeclaringEntityType.Builder.HasIndex(existingIndex, properties, configurationSource); + return existingIndex.DeclaringEntityType.Builder.HasIndex(existingIndex, properties, null, configurationSource); + } + + var indexBuilder = HasIndex(existingIndex, properties, null, configurationSource); + + if (detachedIndexes != null) + { + foreach (var detachedIndex in detachedIndexes) + { + detachedIndex.Attach(detachedIndex.Metadata.DeclaringEntityType.Builder); + } + } + + return indexBuilder; + } + + /// + /// 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 virtual InternalIndexBuilder HasIndex( + [CanBeNull] IReadOnlyList properties, + [NotNull] string name, + ConfigurationSource configurationSource) + { + Check.NotEmpty(name, nameof(name)); + + if (properties == null) + { + return null; + } + + List detachedIndexes = null; + + var existingIndex = Metadata.FindIndex(name); + if (existingIndex != null + && !existingIndex.Properties.SequenceEqual(properties)) + { + // use existing index only if properties match + existingIndex = null; + } + + if (existingIndex == null) + { + detachedIndexes = Metadata.FindDerivedIndexes(name) + .Where(i => i.Properties.SequenceEqual(properties)) + .ToList().Select(DetachIndex).ToList(); + } + else if (existingIndex.DeclaringEntityType != Metadata) + { + return existingIndex.DeclaringEntityType.Builder.HasIndex(existingIndex, properties, name, configurationSource); } - var indexBuilder = HasIndex(existingIndex, properties, configurationSource); + var indexBuilder = HasIndex(existingIndex, properties, name, configurationSource); if (detachedIndexes != null) { - foreach (var indexBuilderTuple in detachedIndexes) + foreach (var detachedIndex in detachedIndexes) { - indexBuilderTuple.Attach(indexBuilderTuple.Metadata.DeclaringEntityType.Builder); + detachedIndex.Attach(detachedIndex.Metadata.DeclaringEntityType.Builder); } } @@ -2213,11 +2289,21 @@ public virtual InternalIndexBuilder HasIndex( } private InternalIndexBuilder HasIndex( - Index index, IReadOnlyList properties, ConfigurationSource configurationSource) + Index index, + IReadOnlyList properties, + string name, + ConfigurationSource configurationSource) { if (index == null) { - index = Metadata.AddIndex(properties, configurationSource); + if (name == null) + { + index = Metadata.AddIndex(properties, configurationSource); + } + else + { + index = Metadata.AddIndex(properties, name, configurationSource); + } } else { @@ -2241,7 +2327,9 @@ public virtual InternalEntityTypeBuilder HasNoIndex([NotNull] Index index, Confi return null; } - var removedIndex = Metadata.RemoveIndex(index.Properties); + var removedIndex = index.Name == null + ? Metadata.RemoveIndex(index.Properties) + : Metadata.RemoveIndex(index.Name); Check.DebugAssert(removedIndex == index, "removedIndex != index"); RemoveUnusedShadowProperties(index.Properties); @@ -4380,6 +4468,19 @@ IConventionIndexBuilder IConventionEntityTypeBuilder.HasIndex( properties as IReadOnlyList ?? properties.Cast().ToList(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// 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. + /// + IConventionIndexBuilder IConventionEntityTypeBuilder.HasIndex( + IReadOnlyList properties, string name, bool fromDataAnnotation) + => HasIndex( + properties as IReadOnlyList ?? properties.Cast().ToList(), + name, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// 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/Internal/InternalIndexBuilder.cs b/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs index 06226f844ba..4bad9030ca0 100644 --- a/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs @@ -54,33 +54,6 @@ public virtual bool CanSetIsUnique(bool? unique, ConfigurationSource? configurat => Metadata.IsUnique == unique || configurationSource.Overrides(Metadata.GetIsUniqueConfigurationSource()); - /// - /// 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 virtual InternalIndexBuilder HasName([CanBeNull] string name, ConfigurationSource configurationSource) - { - if (!CanSetName(name, configurationSource)) - { - return null; - } - - Metadata.SetName(name, configurationSource); - return 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 - /// 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 bool CanSetName([CanBeNull] string name, ConfigurationSource? configurationSource) - => string.Equals(Metadata.Name, name, StringComparison.Ordinal) - || configurationSource.Overrides(Metadata.GetNameConfigurationSource()); - /// /// 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 @@ -95,7 +68,9 @@ public virtual InternalIndexBuilder Attach([NotNull] InternalEntityTypeBuilder e return null; } - var newIndexBuilder = entityTypeBuilder.HasIndex(properties, Metadata.GetConfigurationSource()); + var newIndexBuilder = Metadata.Name == null + ? entityTypeBuilder.HasIndex(properties, Metadata.GetConfigurationSource()) + : entityTypeBuilder.HasIndex(properties, Metadata.Name, Metadata.GetConfigurationSource()); newIndexBuilder?.MergeAnnotationsFrom(Metadata); var isUniqueConfigurationSource = Metadata.GetIsUniqueConfigurationSource(); @@ -136,27 +111,5 @@ bool IConventionIndexBuilder.CanSetIsUnique(bool? unique, bool fromDataAnnotatio => CanSetIsUnique( unique, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// 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. - /// - IConventionIndexBuilder IConventionIndexBuilder.HasName(string name, bool fromDataAnnotation) - => HasName( - name, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// 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. - /// - bool IConventionIndexBuilder.CanSetName(string name, bool fromDataAnnotation) - => CanSetName( - name, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index b6df4e5c2a8..3a5de815e6f 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -723,12 +723,20 @@ public static string DuplicateForeignKey([CanBeNull] object foreignKey, [CanBeNu foreignKey, entityType, duplicateEntityType, key, principalType); /// - /// The index {index} cannot be added to the entity type '{entityType}' because an index on the same properties already exists on entity type '{duplicateEntityType}'. + /// The index {indexProperties} cannot be added to the entity type '{entityType}' because an index on the same properties already exists on entity type '{duplicateEntityType}'. /// - public static string DuplicateIndex([CanBeNull] object index, [CanBeNull] object entityType, [CanBeNull] object duplicateEntityType) + public static string DuplicateIndex([CanBeNull] object indexProperties, [CanBeNull] object entityType, [CanBeNull] object duplicateEntityType) => string.Format( - GetString("DuplicateIndex", nameof(index), nameof(entityType), nameof(duplicateEntityType)), - index, entityType, duplicateEntityType); + GetString("DuplicateIndex", nameof(indexProperties), nameof(entityType), nameof(duplicateEntityType)), + indexProperties, entityType, duplicateEntityType); + + /// + /// The index named '{indexName}' defined on properties {indexProperties} cannot be added to the entity type '{entityType}' because an index with the same name already exists on entity type '{duplicateEntityType}'. + /// + public static string DuplicateNamedIndex([CanBeNull] object indexName, [CanBeNull] object indexProperties, [CanBeNull] object entityType, [CanBeNull] object duplicateEntityType) + => string.Format( + GetString("DuplicateNamedIndex", nameof(indexName), nameof(indexProperties), nameof(entityType), nameof(duplicateEntityType)), + indexName, indexProperties, entityType, duplicateEntityType); /// /// The key {key} cannot be added to the entity type '{entityType}' because a key on the same properties already exists on entity type '{duplicateEntityType}'. @@ -2139,12 +2147,20 @@ public static string ForeignKeyWrongType([CanBeNull] object foreignKey, [CanBeNu foreignKey, key, principalType, entityType, otherEntityType); /// - /// The index {index} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. + /// The index {indexProperties} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. + /// + public static string IndexWrongType([CanBeNull] object indexProperties, [CanBeNull] object entityType, [CanBeNull] object otherEntityType) + => string.Format( + GetString("IndexWrongType", nameof(indexProperties), nameof(entityType), nameof(otherEntityType)), + indexProperties, entityType, otherEntityType); + + /// + /// The index with name {indexName} cannot be removed from the entity type '{entityType}' because no such index exists on that entity type. /// - public static string IndexWrongType([CanBeNull] object index, [CanBeNull] object entityType, [CanBeNull] object otherEntityType) + public static string NamedIndexWrongType([CanBeNull] object indexName, [CanBeNull] object entityType) => string.Format( - GetString("IndexWrongType", nameof(index), nameof(entityType), nameof(otherEntityType)), - index, entityType, otherEntityType); + GetString("NamedIndexWrongType", nameof(indexName), nameof(entityType)), + indexName, entityType); /// /// The key {key} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index cd174bcc45f..b9ccda978ec 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -511,7 +511,10 @@ The foreign key {foreignKey} cannot be added to the entity type '{entityType}' because a foreign key on the same properties already exists on entity type '{duplicateEntityType}' and also targets the key {key} on '{principalType}'. - The index {index} cannot be added to the entity type '{entityType}' because an index on the same properties already exists on entity type '{duplicateEntityType}'. + The index {indexProperties} cannot be added to the entity type '{entityType}' because an index on the same properties already exists on entity type '{duplicateEntityType}'. + + + The index named '{indexName}' defined on properties {indexProperties} cannot be added to the entity type '{entityType}' because an index with the same name already exists on entity type '{duplicateEntityType}'. The key {key} cannot be added to the entity type '{entityType}' because a key on the same properties already exists on entity type '{duplicateEntityType}'. @@ -1170,7 +1173,10 @@ The foreign key {foreignKey} targeting the key {key} on '{principalType}' cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. - The index {index} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. + The index {indexProperties} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. + + + The index with name {indexName} cannot be removed from the entity type '{entityType}' because no such index exists on that entity type. The key {key} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index a51126219fb..4b8e8cfcf9c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -282,6 +282,7 @@ public class FakeEntityType : IEntityType public IAnnotation FindAnnotation(string name) => throw new NotImplementedException(); public IForeignKey FindForeignKey(IReadOnlyList properties, IKey principalKey, IEntityType principalEntityType) => throw new NotImplementedException(); public IIndex FindIndex(IReadOnlyList properties) => throw new NotImplementedException(); + public IIndex FindIndex(string name) => throw new NotImplementedException(); public IKey FindKey(IReadOnlyList properties) => throw new NotImplementedException(); public IKey FindPrimaryKey() => throw new NotImplementedException(); public IProperty FindProperty(string name) => null; diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 0ca8145e1a5..ead78b3fde7 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -2431,12 +2431,14 @@ public virtual void Index_isUnique_is_stored_in_snapshot() } [ConditionalFact] - public virtual void Index_name_annotation_is_stored_in_snapshot_as_fluent_api() + public virtual void Index_database_name_annotation_is_stored_in_snapshot_as_fluent_api() { Test( builder => { - builder.Entity().HasIndex(t => t.AlternateId).HasName("IndexName"); + builder.Entity() + .HasIndex(t => t.AlternateId) + .HasDatabaseName("IndexName"); builder.Ignore(); }, AddBoilerPlate( @@ -2455,11 +2457,16 @@ public virtual void Index_name_annotation_is_stored_in_snapshot_as_fluent_api() b.HasKey(""Id""); b.HasIndex(""AlternateId"") - .HasName(""IndexName""); + .HasDatabaseName(""IndexName""); b.ToTable(""EntityWithTwoProperties""); });"), - o => Assert.Equal("IndexName", o.GetEntityTypes().First().GetIndexes().First().Name)); + o => + { + var index = o.GetEntityTypes().First().GetIndexes().First(); + Assert.Null(index.Name); + Assert.Equal("IndexName", index.GetDatabaseName()); + }); } [ConditionalFact] @@ -2503,7 +2510,7 @@ public virtual void Index_multiple_annotations_are_stored_in_snapshot() Test( builder => { - builder.Entity().HasIndex(t => t.AlternateId).HasName("IndexName") + builder.Entity().HasIndex(t => t.AlternateId, "IndexName") .HasAnnotation("AnnotationName", "AnnotationValue"); builder.Ignore(); }, @@ -2522,8 +2529,7 @@ public virtual void Index_multiple_annotations_are_stored_in_snapshot() b.HasKey(""Id""); - b.HasIndex(""AlternateId"") - .HasName(""IndexName"") + b.HasIndex(new[] { ""AlternateId"" }, ""IndexName"") .HasAnnotation(""AnnotationName"", ""AnnotationValue""); b.ToTable(""EntityWithTwoProperties""); @@ -2531,9 +2537,10 @@ public virtual void Index_multiple_annotations_are_stored_in_snapshot() o => { var index = o.GetEntityTypes().First().GetIndexes().First(); + Assert.Equal("IndexName", index.Name); Assert.Equal(2, index.GetAnnotations().Count()); Assert.Equal("AnnotationValue", index["AnnotationName"]); - Assert.Equal("IndexName", index.Name); + Assert.Null(index["RelationalAnnotationNames.Name"]); }); } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 2c843ba4c6e..3f0e188df30 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -363,11 +363,9 @@ public void Entity_with_indexes_and_use_data_annotations_false_always_generates_ x.Property("B"); x.Property("C"); x.HasKey("Id"); - x.HasIndex("A", "B") - .HasName("IndexOnAAndB") + x.HasIndex(new[] { "A", "B" }, "IndexOnAAndB") .IsUnique(); - x.HasIndex("B", "C") - .HasName("IndexOnBAndC") + x.HasIndex(new[] { "B", "C" }, "IndexOnBAndC") .HasFilter("Filter SQL") .HasAnnotation("AnnotationName", "AnnotationValue"); }), @@ -409,12 +407,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { - entity.HasIndex(x => new { x.A, x.B }) - .HasName(""IndexOnAAndB"") + entity.HasIndex(x => new { x.A, x.B }, ""IndexOnAAndB"") .IsUnique(); - entity.HasIndex(x => new { x.B, x.C }) - .HasName(""IndexOnBAndC"") + entity.HasIndex(x => new { x.B, x.C }, ""IndexOnBAndC"") .HasFilter(""Filter SQL"") .HasAnnotation(""AnnotationName"", ""AnnotationValue""); @@ -449,11 +445,9 @@ public void Entity_with_indexes_and_use_data_annotations_true_generates_fluent_A x.Property("B"); x.Property("C"); x.HasKey("Id"); - x.HasIndex("A", "B") - .HasName("IndexOnAAndB") + x.HasIndex(new[] { "A", "B" }, "IndexOnAAndB") .IsUnique(); - x.HasIndex("B", "C") - .HasName("IndexOnBAndC") + x.HasIndex(new[] { "B", "C" }, "IndexOnBAndC") .HasFilter("Filter SQL") .HasAnnotation("AnnotationName", "AnnotationValue"); }), @@ -495,8 +489,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { - entity.HasIndex(x => new { x.B, x.C }) - .HasName(""IndexOnBAndC"") + entity.HasIndex(x => new { x.B, x.C }, ""IndexOnBAndC"") .HasFilter(""Filter SQL"") .HasAnnotation(""AnnotationName"", ""AnnotationValue""); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index 6d3d9432aad..f1ca39c98fe 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -424,11 +424,9 @@ public void Entity_with_multiple_indexes_generates_multiple_IndexAttributes() x.Property("B"); x.Property("C"); x.HasKey("Id"); - x.HasIndex("A", "B") - .HasName("IndexOnAAndB") + x.HasIndex(new[] { "A", "B" }, "IndexOnAAndB") .IsUnique(); - x.HasIndex("B", "C") - .HasName("IndexOnBAndC"); + x.HasIndex(new[] { "B", "C" }, "IndexOnBAndC"); }), new ModelCodeGenerationOptions { UseDataAnnotations = true }, code => @@ -481,11 +479,9 @@ public void Entity_with_indexes_generates_IndexAttribute_only_for_indexes_withou x.Property("B"); x.Property("C"); x.HasKey("Id"); - x.HasIndex("A", "B") - .HasName("IndexOnAAndB") + x.HasIndex(new[] { "A", "B" }, "IndexOnAAndB") .IsUnique(); - x.HasIndex("B", "C") - .HasName("IndexOnBAndC") + x.HasIndex(new[] { "B", "C" }, "IndexOnBAndC") .HasFilter("Filter SQL"); }), new ModelCodeGenerationOptions { UseDataAnnotations = true }, diff --git a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs index cd88b754309..9baad8c6d47 100644 --- a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs @@ -894,8 +894,8 @@ public virtual Task Rename_index() e.Property("Id"); e.Property("FirstName"); }), - builder => builder.Entity("People").HasIndex("FirstName").HasName("Foo"), - builder => builder.Entity("People").HasIndex("FirstName").HasName("foo"), + builder => builder.Entity("People").HasIndex(new[] { "FirstName" }, "Foo"), + builder => builder.Entity("People").HasIndex(new[] { "FirstName" }, "foo"), model => { var table = Assert.Single(model.Tables); diff --git a/test/EFCore.Relational.Specification.Tests/StoreGeneratedFixupRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/StoreGeneratedFixupRelationalTestBase.cs index 2fdc4ec5d58..8553e944be9 100644 --- a/test/EFCore.Relational.Specification.Tests/StoreGeneratedFixupRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/StoreGeneratedFixupRelationalTestBase.cs @@ -23,7 +23,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con eb.HasOne(i => i.Game).WithMany(g => g.Items).HasConstraintName("FK_GameEntity_Game_GameId"); eb.HasOne(i => i.Level).WithMany(g => g.Items).HasConstraintName("FK_GameEntity_Level_GameId_LevelId"); eb.HasIndex( - i => new { i.GameId, i.LevelId }).HasName("IX_GameEntity_GameId_LevelId"); + i => new { i.GameId, i.LevelId }, "IX_GameEntity_GameId_LevelId"); }); modelBuilder.Entity( @@ -32,7 +32,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con eb.HasOne(a => a.Game).WithMany(g => g.Actors).HasConstraintName("FK_GameEntity_Game_GameId"); eb.HasOne(a => a.Level).WithMany(g => g.Actors).HasConstraintName("FK_GameEntity_Level_GameId_LevelId"); eb.HasIndex( - a => new { a.GameId, a.LevelId }).HasName("IX_GameEntity_GameId_LevelId"); + a => new { a.GameId, a.LevelId }, "IX_GameEntity_GameId_LevelId"); }); } } diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 76cf2e372a3..98721d029e9 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -858,8 +858,8 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_with_differen { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity().Property("Shadow"); - modelBuilder.Entity().HasIndex(nameof(Cat.Name), "Shadow").HasName("IX"); - modelBuilder.Entity().HasIndex(d => d.Name).HasName("IX"); + modelBuilder.Entity().HasIndex(nameof(Cat.Name), "Shadow").HasDatabaseName("IX"); + modelBuilder.Entity().HasIndex(d => d.Name).HasDatabaseName("IX"); VerifyError( RelationalStrings.DuplicateIndexColumnMismatch( @@ -882,14 +882,14 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_with_differen { et.Property(c => c.Breed).HasColumnName("Breed"); et.HasIndex( - c => new { c.Name, c.Breed }).HasName("IX"); + c => new { c.Name, c.Breed }).HasDatabaseName("IX"); }); modelBuilder.Entity( et => { et.Property(c => c.Breed).HasColumnName("Breed"); et.HasIndex( - d => new { d.Breed, d.Name }).HasName("IX"); + d => new { d.Breed, d.Name }).HasDatabaseName("IX"); }); VerifyError( @@ -908,9 +908,9 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_mapped_to_dif var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); modelBuilder.Entity().HasIndex( - c => new { c.Name, c.Breed }).HasName("IX"); + c => new { c.Name, c.Breed }).HasDatabaseName("IX"); modelBuilder.Entity().HasIndex( - d => new { d.Name, d.Breed }).HasName("IX"); + d => new { d.Name, d.Breed }).HasDatabaseName("IX"); modelBuilder.Entity().Property(d => d.Breed).HasColumnName("DogBreed"); VerifyError( @@ -928,8 +928,8 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_with_differen { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - modelBuilder.Entity().HasIndex(c => c.Name).IsUnique().HasName("IX_Animal_Name"); - modelBuilder.Entity().HasIndex(d => d.Name).IsUnique(false).HasName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(c => c.Name).IsUnique().HasDatabaseName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(d => d.Name).IsUnique(false).HasDatabaseName("IX_Animal_Name"); VerifyError( RelationalStrings.DuplicateIndexUniquenessMismatch( @@ -944,7 +944,7 @@ public virtual void Passes_for_incompatible_indexes_within_hierarchy_when_one_na { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - var index1 = modelBuilder.Entity().HasIndex(c => c.Name).IsUnique().HasName("IX_Animal_Name").Metadata; + var index1 = modelBuilder.Entity().HasIndex(c => c.Name).IsUnique().HasDatabaseName("IX_Animal_Name").Metadata; var index2 = modelBuilder.Entity().HasIndex(d => d.Name).IsUnique(false).Metadata; Validate(modelBuilder.Model); @@ -964,13 +964,13 @@ public virtual void Passes_for_compatible_duplicate_index_names_within_hierarchy et => { et.Property(c => c.Breed).HasColumnName("Breed"); - index1 = et.HasIndex(c => c.Breed).HasName("IX_Animal_Breed").Metadata; + index1 = et.HasIndex(c => c.Breed, "IX_Animal_Breed").Metadata; }); modelBuilder.Entity( et => { et.Property(c => c.Breed).HasColumnName("Breed"); - index2 = et.HasIndex(c => c.Breed).HasName("IX_Animal_Breed").Metadata; + index2 = et.HasIndex(c => c.Breed, "IX_Animal_Breed").Metadata; }); Validate(modelBuilder.Model); @@ -1277,8 +1277,9 @@ public void Passes_for_named_index_with_all_properties_not_mapped_to_any_table() modelBuilder.Entity().ToTable(null); modelBuilder.Entity() - .HasIndex(nameof(Animal.Id), nameof(Animal.Name)) - .HasName("IX_AllPropertiesNotMapped"); + .HasIndex( + new[] { nameof(Animal.Id), nameof(Animal.Name) }, + "IX_AllPropertiesNotMapped"); var definition = RelationalResources .LogNamedIndexAllPropertiesNotToMappedToAnyTable( @@ -1321,8 +1322,9 @@ public void Detects_mix_of_index_property_mapped_and_not_mapped_to_any_table_map modelBuilder.Entity().ToTable(null); modelBuilder.Entity().ToTable("Cats"); modelBuilder.Entity() - .HasIndex(nameof(Cat.Identity), nameof(Animal.Name)) - .HasName("IX_MixOfMappedAndUnmappedProperties"); + .HasIndex( + new[] { nameof(Cat.Identity), nameof(Animal.Name) }, + "IX_MixOfMappedAndUnmappedProperties"); var definition = RelationalResources .LogNamedIndexPropertiesBothMappedAndNotMappedToTable( @@ -1384,8 +1386,9 @@ public void Detects_named_index_properties_mapped_to_different_tables_in_TPT_hie modelBuilder.Entity().ToTable("Animals"); modelBuilder.Entity().ToTable("Cats"); modelBuilder.Entity() - .HasIndex(nameof(Animal.Name), nameof(Cat.Identity)) - .HasName("IX_MappedToDifferentTables"); + .HasIndex( + new[] { nameof(Animal.Name), nameof(Cat.Identity) }, + "IX_MappedToDifferentTables"); var definition = RelationalResources .LogNamedIndexPropertiesMappedToNonOverlappingTables( diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs index de5bd1d3ad8..041d7dc0936 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs @@ -395,7 +395,7 @@ public void Can_set_foreign_key_name_for_one_to_one_with_FK_specified() } [ConditionalFact] - public void Default_index_name_is_based_on_index_column_names() + public void Default_index_database_name_is_based_on_index_column_names() { var modelBuilder = CreateConventionModelBuilder(); @@ -416,14 +416,14 @@ public void Default_index_name_is_based_on_index_column_names() } [ConditionalFact] - public void Can_set_index_name() + public void Can_set_index_database_name() { var modelBuilder = CreateConventionModelBuilder(); modelBuilder .Entity() .HasIndex(e => e.Id) - .HasName("Eeeendeeex"); + .HasDatabaseName("Eeeendeeex"); var index = modelBuilder.Model.FindEntityType(typeof(Customer)).GetIndexes().Single(); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index e166b357126..ed598e6fa05 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -443,7 +443,9 @@ private IRelationalModel CreateTestModel(bool mapToTables = false, bool mapToVie .HasForeignKey(o => o.OrderDate).HasPrincipalKey(o => o.Date) .HasConstraintName("FK_DateDetails"); - ob.HasIndex(o => o.OrderDate).HasName("IX_OrderDate"); + // Note: the below is resetting the name of the anonymous index + // created in HasForeignKey() above, not creating a new index. + ob.HasIndex(o => o.OrderDate).HasDatabaseName("IX_OrderDate"); ob.OwnsOne(o => o.Details, odb => { diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index f00efe3ad17..621949b070f 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -3486,7 +3486,7 @@ public void Rename_index() x.ToTable("Donkey", "dbo"); x.Property("Id"); x.Property("Value"); - x.HasIndex("Value").HasName("IX_dbo.Donkey_Value"); + x.HasIndex(new[] { "Value" }, "IX_dbo.Donkey_Value"); }), operations => { @@ -3522,7 +3522,7 @@ public void Alter_index_columns() x.Property("Id"); x.Property("Value"); x.Property("MuleValue"); - x.HasIndex("MuleValue").HasName("IX_Muel_Value"); + x.HasIndex(new[] { "MuleValue" },"IX_Muel_Value"); }), operations => { @@ -4418,7 +4418,7 @@ public void Rename_table_with_index() x.Property("Id"); x.HasKey("Id").HasName("PK_Gnat"); x.Property("Name"); - x.HasIndex("Name").HasName("IX_Gnat_Name"); + x.HasIndex(new[] { "Name" }, "IX_Gnat_Name"); }), operations => { @@ -4476,7 +4476,7 @@ public void Rename_entity_type_with_index() x.Property("Id"); x.HasKey("Id").HasName("PK_Cricket"); x.Property("Name"); - x.HasIndex("Name").HasName("IX_Cricket_Name"); + x.HasIndex("Name").HasDatabaseName("IX_Cricket_Name"); }), operations => { @@ -4572,7 +4572,7 @@ public void Rename_table_with_foreign_key() x.HasKey("Id").HasName("PK_Zonkey"); x.Property("ParentId"); x.HasOne("Zebra").WithMany().HasForeignKey("ParentId").HasConstraintName("FK_Zonkey_Zebra_ParentId"); - x.HasIndex("ParentId").HasName("IX_Zonkey_ParentId"); + x.HasIndex("ParentId").HasDatabaseName("IX_Zonkey_ParentId"); }); }, operations => @@ -4830,7 +4830,7 @@ public void Alter_index_on_subtype() { x.HasBaseType("Animal"); x.Property("Name"); - x.HasIndex("Name").HasName("IX_Animal_Pike_Name"); + x.HasIndex("Name").HasDatabaseName("IX_Animal_Pike_Name"); }); }, operations => @@ -5760,14 +5760,14 @@ public void Add_shared_foreign_key_on_subtypes() x => { x.HasOne("Person").WithMany().HasForeignKey("HunterId").HasConstraintName("FK_Animal_Person_HunterId"); - x.HasIndex("HunterId").HasName("IX_Animal_HunterId"); + x.HasIndex("HunterId").HasDatabaseName("IX_Animal_HunterId"); }); target.Entity( "EndangeredAnimal", x => { x.HasOne("Person").WithMany().HasForeignKey("HunterId").HasConstraintName("FK_Animal_Person_HunterId"); - x.HasIndex("HunterId").HasName("IX_Animal_HunterId"); + x.HasIndex("HunterId").HasDatabaseName("IX_Animal_HunterId"); }); }, operations => @@ -5802,7 +5802,7 @@ public void Add_shared_property_with_foreign_key_on_subtypes() x => { x.HasOne("Person").WithMany().HasForeignKey("HunterId").HasConstraintName("FK_Animal_Person_HunterId"); - x.HasIndex("HunterId").HasName("IX_Animal_HunterId"); + x.HasIndex("HunterId").HasDatabaseName("IX_Animal_HunterId"); }); }, source => { }, @@ -5814,7 +5814,7 @@ public void Add_shared_property_with_foreign_key_on_subtypes() x => { x.HasOne("Person").WithMany().HasForeignKey("HunterId").HasConstraintName("FK_Animal_Person_HunterId"); - x.HasIndex("HunterId").HasName("IX_Animal_HunterId"); + x.HasIndex("HunterId").HasDatabaseName("IX_Animal_HunterId"); }); }, operations => Assert.Equal(0, operations.Count)); @@ -6105,7 +6105,7 @@ public void Add_foreign_key_to_renamed_table() x.Property("Id"); x.HasKey("Id").HasName("PK_Table"); x.Property("ForeignId"); - x.HasIndex("ForeignId").HasName("IX_Table_ForeignId"); + x.HasIndex("ForeignId").HasDatabaseName("IX_Table_ForeignId"); x.HasOne("ReferencedTable").WithMany().HasForeignKey("ForeignId"); }), operations => @@ -6146,7 +6146,7 @@ public void Add_foreign_key_to_renamed_column() x.Property("Id"); x.HasKey("Id").HasName("PK_Table"); x.Property("ForeignId").HasColumnName("RenamedForeignId"); - x.HasIndex("ForeignId").HasName("IX_Table_ForeignId"); + x.HasIndex("ForeignId").HasDatabaseName("IX_Table_ForeignId"); x.HasOne("ReferencedTable").WithMany().HasForeignKey("ForeignId"); }), operations => @@ -6730,7 +6730,7 @@ public void Drop_foreign_key_on_renamed_table() x.Property("Id"); x.HasKey("Id").HasName("PK_Table"); x.Property("ForeignId"); - x.HasIndex("ForeignId").HasName("IX_Table_ForeignId"); + x.HasIndex("ForeignId").HasDatabaseName("IX_Table_ForeignId"); }), operations => { diff --git a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs index 40cc715b1f7..db409bb96dc 100644 --- a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs +++ b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs @@ -40,7 +40,7 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() var property = new Property( "A", typeof(int), null, null, entityType, ConfigurationSource.Convention, ConfigurationSource.Convention); var contextServices = RelationalTestHelpers.Instance.CreateContextServices(model.FinalizeModel()); - var index = new Metadata.Internal.Index(new List { property }, entityType, ConfigurationSource.Convention); + var index = new Metadata.Internal.Index(new List { property }, "IndexName", entityType, ConfigurationSource.Convention); var fakeFactories = new Dictionary> { diff --git a/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs index 78c767d55b7..395b24fbc5d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs @@ -1301,8 +1301,8 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity( b => { - b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique(); - b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex"); + b.HasIndex(u => u.NormalizedUserName).HasDatabaseName("UserNameIndex").IsUnique(); + b.HasIndex(u => u.NormalizedEmail).HasDatabaseName("EmailIndex"); b.ToTable("AspNetUsers"); }); @@ -1327,7 +1327,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity( b => { - b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique(); + b.HasIndex(r => r.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique(); b.ToTable("AspNetRoles"); }); diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlAzure/Model/AdventureWorksContext.cs b/test/EFCore.SqlServer.FunctionalTests/SqlAzure/Model/AdventureWorksContext.cs index 31c6d971d3a..894a1433605 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlAzure/Model/AdventureWorksContext.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlAzure/Model/AdventureWorksContext.cs @@ -26,14 +26,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) e.StateProvince, e.PostalCode, e.CountryRegion - }) - .HasName("IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion"); + }, + "IX_Address_AddressLine1_AddressLine2_City_StateProvince_PostalCode_CountryRegion"); - entity.HasIndex(e => e.StateProvince) - .HasName("IX_Address_StateProvince"); + entity.HasIndex(e => e.StateProvince, "IX_Address_StateProvince"); - entity.HasIndex(e => e.rowguid) - .HasName("AK_Address_rowguid") + entity.HasIndex(e => e.rowguid, "AK_Address_rowguid") .IsUnique(); entity.Property(e => e.ModifiedDate) @@ -46,11 +44,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( entity => { - entity.HasIndex(e => e.EmailAddress) - .HasName("IX_Customer_EmailAddress"); + entity.HasIndex(e => e.EmailAddress, "IX_Customer_EmailAddress"); - entity.HasIndex(e => e.rowguid) - .HasName("AK_Customer_rowguid") + entity.HasIndex(e => e.rowguid, "AK_Customer_rowguid") .IsUnique(); entity.Property(e => e.ModifiedDate) @@ -71,8 +67,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) e => new { e.CustomerID, e.AddressID }) .HasName("PK_CustomerAddress_CustomerID_AddressID"); - entity.HasIndex(e => e.rowguid) - .HasName("AK_CustomerAddress_rowguid") + entity.HasIndex(e => e.rowguid, "AK_CustomerAddress_rowguid") .IsUnique(); entity.Property(e => e.ModifiedDate) @@ -85,16 +80,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( entity => { - entity.HasIndex(e => e.ProductNumber) - .HasName("AK_Product_ProductNumber") + entity.HasIndex(e => e.ProductNumber, "AK_Product_ProductNumber") .IsUnique(); - entity.HasIndex(e => e.rowguid) - .HasName("AK_Product_rowguid") + entity.HasIndex(e => e.rowguid, "AK_Product_rowguid") .IsUnique(); - entity.HasIndex(e => e.Name) - .HasName("AK_Product_Name") + entity.HasIndex(e => e.Name, "AK_Product_Name") .IsUnique(); entity.Property(e => e.DiscontinuedDate).HasColumnType("datetime"); @@ -121,12 +113,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( entity => { - entity.HasIndex(e => e.Name) - .HasName("AK_ProductCategory_Name") + entity.HasIndex(e => e.Name,"AK_ProductCategory_Name") .IsUnique(); - entity.HasIndex(e => e.rowguid) - .HasName("AK_ProductCategory_rowguid") + entity.HasIndex(e => e.rowguid, "AK_ProductCategory_rowguid") .IsUnique(); entity.Property(e => e.ModifiedDate) @@ -139,8 +129,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( entity => { - entity.HasIndex(e => e.rowguid) - .HasName("AK_ProductDescription_rowguid") + entity.HasIndex(e => e.rowguid, "AK_ProductDescription_rowguid") .IsUnique(); entity.Property(e => e.ModifiedDate) @@ -153,12 +142,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity( entity => { - entity.HasIndex(e => e.Name) - .HasName("AK_ProductModel_Name") + entity.HasIndex(e => e.Name, "AK_ProductModel_Name") .IsUnique(); - entity.HasIndex(e => e.rowguid) - .HasName("AK_ProductModel_rowguid") + entity.HasIndex(e => e.rowguid, "AK_ProductModel_rowguid") .IsUnique(); entity.Property(e => e.ModifiedDate) @@ -180,8 +167,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }) .HasName("PK_ProductModelProductDescription_ProductModelID_ProductDescriptionID_Culture"); - entity.HasIndex(e => e.rowguid) - .HasName("AK_ProductModelProductDescription_rowguid") + entity.HasIndex(e => e.rowguid, "AK_ProductModelProductDescription_rowguid") .IsUnique(); entity.Property(e => e.Culture).HasColumnType("nchar(6)"); @@ -200,11 +186,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) e => new { e.SalesOrderID, e.SalesOrderDetailID }) .HasName("PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID"); - entity.HasIndex(e => e.ProductID) - .HasName("IX_SalesOrderDetail_ProductID"); + entity.HasIndex(e => e.ProductID, "IX_SalesOrderDetail_ProductID"); - entity.HasIndex(e => e.rowguid) - .HasName("AK_SalesOrderDetail_rowguid") + entity.HasIndex(e => e.rowguid, "AK_SalesOrderDetail_rowguid") .IsUnique(); entity.Property(e => e.SalesOrderDetailID).ValueGeneratedOnAdd(); @@ -232,15 +216,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasKey(e => e.SalesOrderID) .HasName("PK_SalesOrderHeader_SalesOrderID"); - entity.HasIndex(e => e.CustomerID) - .HasName("IX_SalesOrderHeader_CustomerID"); + entity.HasIndex(e => e.CustomerID, "IX_SalesOrderHeader_CustomerID"); - entity.HasIndex(e => e.SalesOrderNumber) - .HasName("AK_SalesOrderHeader_SalesOrderNumber") + entity.HasIndex(e => e.SalesOrderNumber, + "AK_SalesOrderHeader_SalesOrderNumber") .IsUnique(); - entity.HasIndex(e => e.rowguid) - .HasName("AK_SalesOrderHeader_rowguid") + entity.HasIndex(e => e.rowguid, + "AK_SalesOrderHeader_rowguid") .IsUnique(); entity.Property(e => e.SalesOrderID).UseHiLo("SalesOrderNumber", "SalesLT"); diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index c68e77fbcf8..e2ff7ef1d59 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -240,8 +240,8 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_differently_c { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - modelBuilder.Entity().HasIndex(c => c.Name).HasName("IX_Animal_Name"); - modelBuilder.Entity().HasIndex(d => d.Name).IsClustered().HasName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(c => c.Name).HasDatabaseName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(d => d.Name).IsClustered().HasDatabaseName("IX_Animal_Name"); VerifyError( SqlServerStrings.DuplicateIndexClusteredMismatch( @@ -256,8 +256,8 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_differently_o { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - modelBuilder.Entity().HasIndex(c => c.Name).HasName("IX_Animal_Name"); - modelBuilder.Entity().HasIndex(d => d.Name).IsCreatedOnline().HasName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(c => c.Name).HasDatabaseName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(d => d.Name).IsCreatedOnline().HasDatabaseName("IX_Animal_Name"); VerifyError( SqlServerStrings.DuplicateIndexOnlineMismatch( @@ -272,8 +272,8 @@ public virtual void Detects_duplicate_index_names_within_hierarchy_with_differen { var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - modelBuilder.Entity().HasIndex(c => c.Name).HasName("IX_Animal_Name"); - modelBuilder.Entity().HasIndex(d => d.Name).IncludeProperties(nameof(Dog.Identity)).HasName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(c => c.Name).HasDatabaseName("IX_Animal_Name"); + modelBuilder.Entity().HasIndex(d => d.Name).HasDatabaseName("IX_Animal_Name").IncludeProperties(nameof(Dog.Identity)); VerifyError( SqlServerStrings.DuplicateIndexIncludedMismatch( diff --git a/test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs index 33acf81dc38..17e70acc18d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs @@ -1034,8 +1034,8 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity( b => { - b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique(); - b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex"); + b.HasIndex(u => u.NormalizedUserName).HasDatabaseName("UserNameIndex").IsUnique(); + b.HasIndex(u => u.NormalizedEmail).HasDatabaseName("EmailIndex"); b.ToTable("AspNetUsers"); }); @@ -1060,7 +1060,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity( b => { - b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique(); + b.HasIndex(r => r.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique(); b.ToTable("AspNetRoles"); }); diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index 441dd6f6a71..59253a820c8 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -2812,78 +2812,6 @@ public void ProcessIndexUniquenessChanged( } } - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - [ConditionalTheory] - public void OnIndexNameChanged_calls_conventions_in_order(bool useBuilder, bool useScope) - { - var conventions = new ConventionSet(); - - var convention1 = new IndexNameChangedConvention(terminate: false); - var convention2 = new IndexNameChangedConvention(terminate: true); - var convention3 = new IndexNameChangedConvention(terminate: false); - conventions.IndexNameChangedConventions.Add(convention1); - conventions.IndexNameChangedConventions.Add(convention2); - conventions.IndexNameChangedConventions.Add(convention3); - - var builder = new InternalModelBuilder(new Model(conventions)); - var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); - var index = entityBuilder.HasIndex( - new List { "OrderId" }, ConfigurationSource.Convention).Metadata; - - var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; - - if (useBuilder) - { - index.Builder.HasName("OriginalIndexName", ConfigurationSource.Convention); - } - else - { - index.Name = "OriginalIndexName"; - } - - if (useScope) - { - Assert.Empty(convention1.Calls); - Assert.Empty(convention2.Calls); - scope.Dispose(); - } - - Assert.Equal(new[] { "OriginalIndexName" }, convention1.Calls); - Assert.Equal(new[] { "OriginalIndexName" }, convention2.Calls); - Assert.Empty(convention3.Calls); - - if (useBuilder) - { - index.Builder.HasName("OriginalIndexName", ConfigurationSource.Convention); - } - else - { - index.Name = "OriginalIndexName"; - } - - Assert.Equal(new[] { "OriginalIndexName" }, convention1.Calls); - Assert.Equal(new[] { "OriginalIndexName" }, convention2.Calls); - Assert.Empty(convention3.Calls); - - if (useBuilder) - { - index.Builder.HasName("UpdatedIndexName", ConfigurationSource.Convention); - } - else - { - index.Name = "UpdatedIndexName"; - } - - Assert.Equal(new[] { "OriginalIndexName", "UpdatedIndexName" }, convention1.Calls); - Assert.Equal(new[] { "OriginalIndexName", "UpdatedIndexName" }, convention2.Calls); - Assert.Empty(convention3.Calls); - - Assert.Same(index, entityBuilder.Metadata.RemoveIndex(index.Properties)); - } - private class IndexNameChangedConvention : IIndexNameChangedConvention { private readonly bool _terminate; diff --git a/test/EFCore.Tests/Metadata/Conventions/IndexAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/IndexAttributeConventionTest.cs index aa4bb6d1d7f..18031628ed6 100644 --- a/test/EFCore.Tests/Metadata/Conventions/IndexAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/IndexAttributeConventionTest.cs @@ -34,8 +34,7 @@ public void IndexAttribute_overrides_configuration_from_convention() entityBuilder.PrimaryKey(new List { "Id" }, ConfigurationSource.Convention); var indexProperties = new List { propA.Metadata.Name, propB.Metadata.Name }; - var indexBuilder = entityBuilder.HasIndex(indexProperties, ConfigurationSource.Convention); - indexBuilder.HasName("ConventionalIndexName", ConfigurationSource.Convention); + var indexBuilder = entityBuilder.HasIndex(indexProperties, "IndexOnAAndB", ConfigurationSource.Convention); indexBuilder.IsUnique(false, ConfigurationSource.Convention); RunConvention(modelBuilder); @@ -43,7 +42,6 @@ public void IndexAttribute_overrides_configuration_from_convention() var index = entityBuilder.Metadata.GetIndexes().Single(); Assert.Equal(ConfigurationSource.DataAnnotation, index.GetConfigurationSource()); Assert.Equal("IndexOnAAndB", index.Name); - Assert.Equal(ConfigurationSource.DataAnnotation, index.GetNameConfigurationSource()); Assert.True(index.IsUnique); Assert.Equal(ConfigurationSource.DataAnnotation, index.GetIsUniqueConfigurationSource()); Assert.Collection(index.Properties, @@ -57,16 +55,14 @@ public void IndexAttribute_can_be_overriden_using_explicit_configuration() var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); var entityBuilder = modelBuilder.Entity(); - entityBuilder.HasIndex("A", "B") - .HasName("OverridenIndexName") + entityBuilder.HasIndex(new[] { "A", "B" }, "IndexOnAAndB") .IsUnique(false); modelBuilder.Model.FinalizeModel(); var index = (Metadata.Internal.Index)entityBuilder.Metadata.GetIndexes().Single(); Assert.Equal(ConfigurationSource.Explicit, index.GetConfigurationSource()); - Assert.Equal("OverridenIndexName", index.Name); - Assert.Equal(ConfigurationSource.Explicit, index.GetNameConfigurationSource()); + Assert.Equal("IndexOnAAndB", index.Name); Assert.False(index.IsUnique); Assert.Equal(ConfigurationSource.Explicit, index.GetIsUniqueConfigurationSource()); Assert.Collection(index.Properties, @@ -114,7 +110,6 @@ public void IndexAttribute_can_be_applied_more_than_once_per_entity_type() var index0 = (Metadata.Internal.Index)indexes.First(); Assert.Equal(ConfigurationSource.DataAnnotation, index0.GetConfigurationSource()); Assert.Equal("IndexOnAAndB", index0.Name); - Assert.Equal(ConfigurationSource.DataAnnotation, index0.GetNameConfigurationSource()); Assert.True(index0.IsUnique); Assert.Equal(ConfigurationSource.DataAnnotation, index0.GetIsUniqueConfigurationSource()); Assert.Collection(index0.Properties, @@ -124,7 +119,6 @@ public void IndexAttribute_can_be_applied_more_than_once_per_entity_type() var index1 = (Metadata.Internal.Index)indexes.Skip(1).First(); Assert.Equal(ConfigurationSource.DataAnnotation, index1.GetConfigurationSource()); Assert.Equal("IndexOnBAndC", index1.Name); - Assert.Equal(ConfigurationSource.DataAnnotation, index1.GetNameConfigurationSource()); Assert.False(index1.IsUnique); Assert.Equal(ConfigurationSource.DataAnnotation, index1.GetIsUniqueConfigurationSource()); Assert.Collection(index1.Properties, @@ -147,7 +141,6 @@ public void IndexAttribute_can_be_inherited_from_base_entity_type() var index = (Metadata.Internal.Index)entityBuilder.Metadata.GetIndexes().Single(); Assert.Equal(ConfigurationSource.DataAnnotation, index.GetConfigurationSource()); Assert.Equal("IndexOnAAndB", index.Name); - Assert.Equal(ConfigurationSource.DataAnnotation, index.GetNameConfigurationSource()); Assert.True(index.IsUnique); Assert.Equal(ConfigurationSource.DataAnnotation, index.GetIsUniqueConfigurationSource()); Assert.Collection(index.Properties, diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index d5e6f3571e6..60327e5777d 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -79,6 +79,7 @@ public IForeignKey FindForeignKey(IReadOnlyList properties, IKey prin public IEnumerable GetForeignKeys() => throw new NotImplementedException(); public IIndex FindIndex(IReadOnlyList properties) => throw new NotImplementedException(); + public IIndex FindIndex(string name) => throw new NotImplementedException(); public IEnumerable GetIndexes() => throw new NotImplementedException(); public IProperty FindProperty(string name) => throw new NotImplementedException(); public IEnumerable GetProperties() => throw new NotImplementedException(); @@ -1511,7 +1512,7 @@ public void AddIndex_throws_if_not_from_same_entity() } [ConditionalFact] - public void AddIndex_throws_if_duplicate() + public void AddIndex_throws_if_duplicate_properties() { var model = CreateModel(); var entityType = model.AddEntityType(typeof(Customer)); @@ -1521,12 +1522,103 @@ public void AddIndex_throws_if_duplicate() Assert.Equal( CoreStrings.DuplicateIndex( - "{'" + Customer.IdProperty.Name + "', '" + Customer.NameProperty.Name + "'}", typeof(Customer).Name, + "{'" + Customer.IdProperty.Name + "', '" + Customer.NameProperty.Name + "'}", + typeof(Customer).Name, typeof(Customer).Name), Assert.Throws( () => entityType.AddIndex(new[] { property1, property2 })).Message); } + [ConditionalFact] + public void AddIndex_throws_if_duplicate_name() + { + var model = CreateModel(); + var entityType = model.AddEntityType(typeof(Customer)); + var property1 = entityType.AddProperty(Customer.IdProperty); + var property2 = entityType.AddProperty(Customer.NameProperty); + entityType.AddIndex(new[] { property1 }, "NamedIndex"); + + Assert.Equal( + CoreStrings.DuplicateNamedIndex( + "NamedIndex", + "{'" + Customer.NameProperty.Name + "'}", + typeof(Customer).Name, + typeof(Customer).Name), + Assert.Throws( + () => entityType.AddIndex(new[] { property2 }, "NamedIndex")).Message); + } + + [ConditionalFact] + public void Can_add_multiple_named_indexes_on_the_same_properties() + { + var model = CreateModel(); + var entityType = model.AddEntityType(typeof(Customer)); + var property1 = entityType.AddProperty(Customer.IdProperty); + var property2 = entityType.AddProperty(Customer.NameProperty); + + entityType.AddIndex(new[] { property1, property2 }, "Index1"); + entityType.AddIndex(new[] { property1, property2 }, "Index2"); + } + + [ConditionalFact] + public void RemoveIndex_throws_if_incorrect_properties() + { + var model = CreateModel(); + var entityType = model.AddEntityType(typeof(Customer)); + var property1 = entityType.AddProperty(Customer.IdProperty); + var property2 = entityType.AddProperty(Customer.NameProperty); + entityType.AddIndex(new[] { property1, property2 }); + + var anotherIndex = new Index( + new List { (Property)property2 }, + (EntityType)entityType, + ConfigurationSource.Explicit); + + Assert.Equal( + CoreStrings.IndexWrongType( + "{'" + Customer.NameProperty.Name + "'}", + typeof(Customer).Name, + typeof(Customer).Name), + Assert.Throws( + () => entityType.RemoveIndex(anotherIndex)).Message); + } + + [ConditionalFact] + public void RemoveIndex_throws_if_incorrect_name() + { + var model = CreateModel(); + var entityType = model.AddEntityType(typeof(Customer)); + var property1 = entityType.AddProperty(Customer.IdProperty); + var property2 = entityType.AddProperty(Customer.NameProperty); + entityType.AddIndex(new[] { property1 }, "NamedIndex"); + + var anotherIndex = new Index( + new List { (Property)property1 }, + "NonExistentIndex", + (EntityType)entityType, + ConfigurationSource.Explicit); + + Assert.Equal( + CoreStrings.NamedIndexWrongType("NonExistentIndex", typeof(Customer).Name), + Assert.Throws( + () => entityType.RemoveIndex(anotherIndex)).Message); + } + + [ConditionalFact] + public void Can_remove_named_index_by_name() + { + var model = CreateModel(); + var entityType = model.AddEntityType(typeof(Customer)); + var property1 = entityType.AddProperty(Customer.IdProperty); + entityType.AddIndex(new[] { property1 }, "NamedIndex"); + Assert.Single(entityType.GetIndexes()); + + var index = ((EntityType)entityType).RemoveIndex("NamedIndex"); + + Assert.Equal("NamedIndex", index.Name); + Assert.Empty(entityType.GetIndexes()); + } + [ConditionalFact] public void Can_add_and_remove_properties() { diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index 92596def3c6..3380396149f 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -623,6 +623,123 @@ public void Index_returns_same_instance_for_property_names() indexBuilder, entityBuilder.HasIndex(new[] { Order.IdProperty, Order.CustomerIdProperty }, ConfigurationSource.Explicit)); } + [ConditionalFact] + public void Index_returns_same_instance_if_asked_for_twice() + { + var modelBuilder = CreateModelBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + + var indexBuilder = entityBuilder.HasIndex( + new[] { Order.IdProperty.Name, Order.CustomerIdProperty.Name }, ConfigurationSource.Convention); + + Assert.NotNull(indexBuilder); + Assert.Same( + indexBuilder, + entityBuilder.HasIndex(new[] { Order.IdProperty, Order.CustomerIdProperty }, ConfigurationSource.Explicit)); + } + + [ConditionalFact] + public void Named_index_returns_same_instance_for_clr_properties() + { + var modelBuilder = CreateModelBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + + var indexBuilder = entityBuilder.HasIndex(new[] { Order.IdProperty, Order.CustomerIdProperty }, "TestIndex", ConfigurationSource.Explicit); + + Assert.NotNull(indexBuilder); + Assert.Same( + indexBuilder, + entityBuilder.HasIndex(new[] { Order.IdProperty.Name, Order.CustomerIdProperty.Name }, "TestIndex", ConfigurationSource.DataAnnotation)); + } + + [ConditionalFact] + public void Named_index_returns_same_instance_for_property_names() + { + var modelBuilder = CreateModelBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + + var indexBuilder = entityBuilder.HasIndex( + new[] { Order.IdProperty.Name, Order.CustomerIdProperty.Name }, "TestIndex", ConfigurationSource.Convention); + + Assert.NotNull(indexBuilder); + Assert.Same( + indexBuilder, entityBuilder.HasIndex(new[] { Order.IdProperty, Order.CustomerIdProperty }, "TestIndex", ConfigurationSource.Explicit)); + } + + [ConditionalFact] + public void Named_index_returns_same_instance_if_asked_for_twice() + { + var modelBuilder = CreateModelBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + + var indexBuilder = entityBuilder.HasIndex( + new[] { Order.IdProperty.Name, Order.CustomerIdProperty.Name }, "TestIndex", ConfigurationSource.Convention); + + Assert.NotNull(indexBuilder); + Assert.Same( + indexBuilder, + entityBuilder.HasIndex(new[] { Order.IdProperty, Order.CustomerIdProperty }, "TestIndex", ConfigurationSource.Explicit)); + } + + [ConditionalFact] + public void Named_index_throws_if_try_to_create_a_new_different_index_with_same_name_on_same_type() + { + var modelBuilder = CreateModelBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + + var indexBuilder = entityBuilder.HasIndex( + new[] { Order.IdProperty.Name, Order.CustomerIdProperty.Name }, "NamedIndex", ConfigurationSource.Convention); + + Assert.NotNull(indexBuilder); + Assert.Equal( + CoreStrings.DuplicateNamedIndex("NamedIndex", "{'CustomerId', 'Id'}", typeof(Order).Name, typeof(Order).Name), + Assert.Throws( + () => entityBuilder + .HasIndex(new[] { Order.CustomerIdProperty, Order.IdProperty }, "NamedIndex", ConfigurationSource.Explicit)).Message); + } + + [ConditionalFact] + public void Named_index_throws_if_try_to_create_a_new_different_index_with_same_name_on_derived_type() + { + var modelBuilder = CreateModelBuilder(); + var baseEntityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + var derivedEntityBuilder = modelBuilder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention); + derivedEntityBuilder.HasBaseType(baseEntityBuilder.Metadata, ConfigurationSource.Convention); + derivedEntityBuilder.Property(Order.IdProperty, ConfigurationSource.Convention); + derivedEntityBuilder.Property(Order.CustomerIdProperty, ConfigurationSource.Convention); + + var indexBuilder = baseEntityBuilder.HasIndex( + new[] { Order.IdProperty.Name, Order.CustomerIdProperty.Name }, "NamedIndex", ConfigurationSource.Convention); + + Assert.NotNull(indexBuilder); + Assert.Equal( + CoreStrings.DuplicateNamedIndex("NamedIndex", "{'CustomerId', 'Id'}", typeof(SpecialOrder).Name, typeof(Order).Name), + Assert.Throws( + () => derivedEntityBuilder.HasIndex( + new[] { Order.CustomerIdProperty, Order.IdProperty }, "NamedIndex", ConfigurationSource.Explicit)).Message); + } + + [ConditionalFact] + public void Named_index_throws_if_try_to_create_a_new_different_index_with_same_name_on_base_type() + { + var modelBuilder = CreateModelBuilder(); + var baseEntityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + var derivedEntityBuilder = modelBuilder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention); + derivedEntityBuilder.HasBaseType(baseEntityBuilder.Metadata, ConfigurationSource.Convention); + derivedEntityBuilder.Property(Order.IdProperty, ConfigurationSource.Convention); + derivedEntityBuilder.Property(Order.CustomerIdProperty, ConfigurationSource.Convention); + + var indexBuilder = derivedEntityBuilder.HasIndex( + new[] { Order.IdProperty.Name, Order.CustomerIdProperty.Name }, "NamedIndex", ConfigurationSource.Convention); + + Assert.NotNull(indexBuilder); + Assert.Equal( + CoreStrings.DuplicateNamedIndex("NamedIndex", "{'CustomerId', 'Id'}", typeof(Order).Name, typeof(SpecialOrder).Name), + Assert.Throws( + () => baseEntityBuilder.HasIndex( + new[] { Order.CustomerIdProperty, Order.IdProperty }, "NamedIndex", ConfigurationSource.Explicit)).Message); + } + [ConditionalFact] public void Can_promote_index_to_base() { @@ -639,6 +756,22 @@ public void Can_promote_index_to_base() Assert.Empty(derivedEntityBuilder.Metadata.GetDeclaredIndexes()); } + [ConditionalFact] + public void Can_promote_named_index_to_base() + { + var modelBuilder = CreateModelBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + var derivedEntityBuilder = modelBuilder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention); + derivedEntityBuilder.HasBaseType(entityBuilder.Metadata, ConfigurationSource.Convention); + derivedEntityBuilder.Property(Order.IdProperty, ConfigurationSource.Convention); + derivedEntityBuilder.HasIndex(new[] { Order.IdProperty.Name }, "IndexToPromote", ConfigurationSource.DataAnnotation); + + var indexBuilder = entityBuilder.HasIndex(new[] { Order.IdProperty.Name }, "IndexToPromote", ConfigurationSource.Convention); + Assert.Same(indexBuilder.Metadata.Properties.Single(), entityBuilder.Metadata.FindProperty(Order.IdProperty.Name)); + Assert.Same(indexBuilder.Metadata, entityBuilder.Metadata.FindIndex(indexBuilder.Metadata.Name)); + Assert.Empty(derivedEntityBuilder.Metadata.GetDeclaredIndexes()); + } + [ConditionalFact] public void Can_promote_index_to_base_with_facets() { @@ -657,6 +790,24 @@ public void Can_promote_index_to_base_with_facets() Assert.Empty(derivedEntityBuilder.Metadata.GetDeclaredIndexes()); } + [ConditionalFact] + public void Can_promote_named_index_to_base_with_facets() + { + var modelBuilder = CreateModelBuilder(); + var entityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit); + var derivedEntityBuilder = modelBuilder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention); + derivedEntityBuilder.HasBaseType(entityBuilder.Metadata, ConfigurationSource.Convention); + derivedEntityBuilder.Property(Order.IdProperty, ConfigurationSource.Convention); + derivedEntityBuilder.HasIndex(new[] { Order.IdProperty.Name }, "IndexToPromote", ConfigurationSource.DataAnnotation) + .IsUnique(true, ConfigurationSource.Convention); + + var indexBuilder = entityBuilder.HasIndex(new[] { Order.IdProperty.Name }, "IndexToPromote", ConfigurationSource.Convention); + Assert.Same(indexBuilder.Metadata.Properties.Single(), entityBuilder.Metadata.FindProperty(Order.IdProperty.Name)); + Assert.Same(indexBuilder.Metadata, entityBuilder.Metadata.FindIndex(indexBuilder.Metadata.Name)); + Assert.True(indexBuilder.Metadata.IsUnique); + Assert.Empty(derivedEntityBuilder.Metadata.GetDeclaredIndexes()); + } + [ConditionalFact] public void Can_configure_inherited_index() { diff --git a/test/EFCore.Tests/Metadata/Internal/InternalIndexBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalIndexBuilderTest.cs index c9c0f83c7f0..b1336c7f255 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalIndexBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalIndexBuilderTest.cs @@ -43,40 +43,6 @@ public void Can_only_override_existing_IsUnique_value_explicitly() Assert.False(metadata.IsUnique); } - [ConditionalFact] - public void Can_only_override_lower_source_Name() - { - var builder = CreateInternalIndexBuilder(); - var metadata = builder.Metadata; - - Assert.NotNull(builder.HasName("ConventionIndexName", ConfigurationSource.Convention)); - Assert.NotNull(builder.HasName("AnnotationIndexName", ConfigurationSource.DataAnnotation)); - - Assert.Equal("AnnotationIndexName", metadata.Name); - - Assert.Null(builder.HasName("UpdatedConventionIndexName", ConfigurationSource.Convention)); - - Assert.Equal("AnnotationIndexName", metadata.Name); - } - - [ConditionalFact] - public void Can_only_override_existing_Name_value_explicitly() - { - var builder = CreateInternalIndexBuilder(); - var metadata = builder.Metadata; - metadata.Name = "TestIndexName"; - - Assert.Equal(ConfigurationSource.Explicit, metadata.GetConfigurationSource()); - Assert.NotNull(builder.HasName("TestIndexName", ConfigurationSource.DataAnnotation)); - Assert.Null(builder.HasName("NewIndexName", ConfigurationSource.DataAnnotation)); - - Assert.Equal("TestIndexName", metadata.Name); - - Assert.NotNull(builder.HasName("ExplicitIndexName", ConfigurationSource.Explicit)); - - Assert.Equal("ExplicitIndexName", metadata.Name); - } - private InternalIndexBuilder CreateInternalIndexBuilder() { var modelBuilder = new InternalModelBuilder(new Model()); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 1f5ff552a2e..303670b1bb6 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -474,9 +474,6 @@ public override TestIndexBuilder HasAnnotation(string annotation, objec public override TestIndexBuilder IsUnique(bool isUnique = true) => new GenericTestIndexBuilder(IndexBuilder.IsUnique(isUnique)); - public override TestIndexBuilder HasName(string name) - => new GenericTestIndexBuilder(IndexBuilder.HasName(name)); - IndexBuilder IInfrastructure>.Instance => IndexBuilder; } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index ed2f8eca405..83e521a7d42 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -459,9 +459,6 @@ public override TestIndexBuilder HasAnnotation(string annotation, objec public override TestIndexBuilder IsUnique(bool isUnique = true) => new NonGenericTestIndexBuilder(IndexBuilder.IsUnique(isUnique)); - public override TestIndexBuilder HasName(string name) - => new NonGenericTestIndexBuilder(IndexBuilder.HasName(name)); - IndexBuilder IInfrastructure.Instance => IndexBuilder; } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index f7c75be5b46..7fa1ced5ed8 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -306,7 +306,6 @@ public abstract class TestIndexBuilder public abstract TestIndexBuilder HasAnnotation(string annotation, object value); public abstract TestIndexBuilder IsUnique(bool isUnique = true); - public abstract TestIndexBuilder HasName(string name); } public abstract class TestPropertyBuilder diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index 6505d6d0d88..454716c3898 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -1195,14 +1195,13 @@ public virtual void Can_add_multiple_indexes() var entityBuilder = modelBuilder.Entity(); var firstIndexBuilder = entityBuilder.HasIndex(ix => ix.Id).IsUnique(); - var secondIndexBuilder = entityBuilder.HasIndex(ix => ix.Name).HasName("MyIndex").HasAnnotation("A1", "V1"); + var secondIndexBuilder = entityBuilder.HasIndex(ix => ix.Name).HasAnnotation("A1", "V1"); var entityType = (IEntityType)model.FindEntityType(typeof(Customer)); Assert.Equal(2, entityType.GetIndexes().Count()); Assert.True(firstIndexBuilder.Metadata.IsUnique); Assert.False(((IIndex)secondIndexBuilder.Metadata).IsUnique); - Assert.Equal("MyIndex", secondIndexBuilder.Metadata.Name); Assert.Equal("V1", secondIndexBuilder.Metadata["A1"]); }