Scaffold error when two indexes exist on the same column set
See original GitHub issueIn https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/228, @berets76 describes an issue with the way indices are managed during scaffolding.
PostgreSQL supports multiple index “methods”, and the Npgsql provider represents these via a simple string annotation on the index - all indices in a scaffolded DatabaseModel contain this annotation (note that if the default method is detected, the annotation is later eliminated by convention in NpgsqlAnnotationCodeGenerator
).
Now, consider the following database schema:
CREATE TABLE data
(
id integer PRIMARY KEY,
name character(3)
);
CREATE INDEX index1 ON data (name);
CREATE INDEX index2 ON data (name DESC);
Two indices exist on the same column, which should be fine. However, trying to scaffold this throws the following:
System.InvalidOperationException: The annotation 'Npgsql:IndexMethod' cannot be added because an annotation with the same name already exists.
at Microsoft.EntityFrameworkCore.Infrastructure.Annotatable.AddAnnotation(String name, Annotation annotation)
at Microsoft.EntityFrameworkCore.MutableAnnotatableExtensions.AddAnnotations(IMutableAnnotatable annotatable, IEnumerable`1 annotations)
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.RelationalScaffoldingModelFactory.VisitIndex(EntityTypeBuilder builder, DatabaseIndex index) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Scaffolding\Internal\RelationalScaffoldingModelFactory.cs:line 607
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.RelationalScaffoldingModelFactory.VisitIndexes(EntityTypeBuilder builder, ICollection`1 indexes) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Scaffolding\Internal\RelationalScaffoldingModelFactory.cs:line 560
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.RelationalScaffoldingModelFactory.VisitTable(ModelBuilder modelBuilder, DatabaseTable table) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Scaffolding\Internal\RelationalScaffoldingModelFactory.cs:line 323
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.RelationalScaffoldingModelFactory.VisitTables(ModelBuilder modelBuilder, ICollection`1 tables) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Scaffolding\Internal\RelationalScaffoldingModelFactory.cs:line 279
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.RelationalScaffoldingModelFactory.VisitDatabaseModel(ModelBuilder modelBuilder, DatabaseModel databaseModel) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Scaffolding\Internal\RelationalScaffoldingModelFactory.cs:line 182
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.RelationalScaffoldingModelFactory.Create(DatabaseModel databaseModel, Boolean useDatabaseNames) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Scaffolding\Internal\RelationalScaffoldingModelFactory.cs:line 99
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.ReverseEngineerScaffolder.ScaffoldModel(String connectionString, IEnumerable`1 tables, IEnumerable`1 schemas, String namespace, String language, String contextDir, String contextName, ModelReverseEngineerOptions modelOptions, ModelCodeGenerationOptions codeOptions) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Scaffolding\Internal\ReverseEngineerScaffolder.cs:line 97
at Microsoft.EntityFrameworkCore.Design.Internal.DatabaseOperations.ScaffoldContext(String provider, String connectionString, String outputDir, String outputContextDir, String dbContextClassName, IEnumerable`1 schemas, IEnumerable`1 tables, Boolean useDataAnnotations, Boolean overwriteFiles, Boolean useDatabaseNames) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Design\Internal\DatabaseOperations.cs:line 94
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.ScaffoldContextImpl(String provider, String connectionString, String outputDir, String outputDbContextDir, String dbContextClassName, IEnumerable`1 schemaFilters, IEnumerable`1 tableFilters, Boolean useDataAnnotations, Boolean overwriteFiles, Boolean useDatabaseNames) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Design\OperationExecutor.cs:line 463
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.ScaffoldContext.<>c__DisplayClass0_1.<.ctor>b__0() in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Design\OperationExecutor.cs:line 445
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0() in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Design\OperationExecutor.cs:line 555
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action) in C:\b\w\33bdfc1cae7b2a38\modules\EntityFrameworkCore\src\EFCore.Design\Design\OperationExecutor.cs:line 538
It seems that at some point during the scaffolding process, the two indices provided by the DatabaseModel get “collapsed” to one, and we get the error since the annotation is already set. This is probably because at some point EF Core looks up the index, keying only by the column list, and omits other index characteristics (e.g ascending/descending).
Thanks again to @berets76 for reproducing and analyzing, I only provided the writeup.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:5
- Comments:23 (19 by maintainers)
Named indexes are now supported, so this can be fixed properly
@divega an index on
tolower()
in PostgreSQL would be an expression index. There is no specific support for those in the Npgsql provider (see https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/119) but they can be created with raw SQL.To summarize, I think there are several legitimate reasons why someone would have multiple indices on the same column set; between expression indices,
ASC
/DESC
, index methods (a concept specific to PostgreSQL) I think there’s enough material. At the moment, when scaffolding EF Core will retain the last index present on the database model, and silently discard the others.