2015-10-01 21 views
19

Uso il codice EF 6.1.x Primo.Entity Framework Filter Index

Ho letto che un indice con espressione filtro non è supportato da EF più recente.

V'è inoltre alcuna soluzione su SO:

EF 6.1 Unique Nullable Index

Un anno dopo, qual è il modo di lavoro per far funzionare un indice filtro con il codice prima e DbMigrations?

CREATE UNIQUE NONCLUSTERED INDEX [IX_DefaultLanguageApplicationId] ON [dbo].[Languages] 
(
    [IsDefaultLanguage] ASC, 
    [ApplicationId] ASC, 
) 
WHERE ([IsDefaultLanguage]=(1)) 
+0

Articolo interessante: http://stackoverflow.com/questions/29922099/how-to-add-an-index-on-multiple-columns-with-asc-desc-sort-using-the-fluent-api – Elisabeth

risposta

20

In EF 6.1, il modo di lavoro per fare il questo lavoro con il codice prima e DbMigrations è quello di utilizzare il metodo Sql nella classe DbMigration:

public partial class AddIndexes : DbMigration 
{ 
    public override void Up() 
    { 
     Sql(@"CREATE UNIQUE NONCLUSTERED INDEX 
      [IX_DefaultLanguageApplicationId] ON [dbo].[Languages] 
      (
       [IsDefaultLanguage] ASC, 
       [ApplicationId] ASC 
      ) 
      WHERE ([IsDefaultLanguage]=(1))"); 

    } 

    public override void Down() 
    { 
     DropIndex("dbo.Languages", "IX_DefaultLanguageApplicationId"); 
    } 
} 

ma mi rendo conto che probabilmente si sta chiedendo se è possibile create an index using the IndexAttribute introduced in 6.1, ma con un filtro - la risposta è "No"

Quasi un duplicato di: Entity Framework 6.1 - Create index with INCLUDE statement

+0

Sì, sto cercando http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribute/ MA ho saputo che non è ancora possibile. Ancora grazie! – Elisabeth

+0

ho aggiunto questa classe parziale e ho fatto "update-database", quindi tutte le migrazioni sono state esplicitamente applicate MA questo nuovo indice non è stato creato nel database ??? – Elisabeth

+0

Ok ho creato prima una migrazione vuota MA poi ho ricevuto questo errore: Numero errore: 102, stato: 1, classe: 15 Sintassi errata vicino a ')'. Vorresti correggere l'errore ")"? – Elisabeth

1

So che il post originale si riferiva alla versione 6.1 di EF, ma dopo alcune ricerche ho trovato un modo per aggiungere un metodo di estensione per gli indici filtrati alla fluent api di EF Core (versione 1.1). Forse qualcuno troverà questo utile (e forse c'è un modo per implementarlo anche nelle versioni precedenti). Devo avvertirti comunque. Poiché questa soluzione utilizza classi dagli spazi dei nomi Microsoft.EntityFrameworkCore.Migrations.Internal e Microsoft.EntityFrameworkCore.Infrastructure, non è garantito che questo codice funzioni dopo l'aggiornamento di EF. C'è un massaggio incluso in una sintesi di ogni classe all'interno di questi spazi dei nomi dicendo che

This API may change or be removed in future releases

, così siete stati avvertiti.

Ma al punto.

Per prima cosa è necessario creare un metodo di estensione standard per IndexBuilder. La sua responsabilità principale sarà aggiungere una nuova annotazione con la condizione all'indice costruito. Uno userà questo metodo in seguito con l'api fluente. Per non chiamare la nostra annotazione SqlServer:FilteredIndex.

static class FilteredIndexExtension 
{ 
    public static IndexBuilder Filtered(this IndexBuilder indexBuilder, string condition) 
    { 
     indexBuilder.HasAnnotation("SqlServer:FilteredIndex", condition); 

     return indexBuilder; 
    } 
} 

Quindi è necessario consentire l'inclusione di questa annotazione all'interno delle migrazioni. È necessario sovrascrivere il comportamento predefinito di SqlServerMigrationsAnnotationProvider per i programmi di creazione di indici.

class ExtendedSqlServerMigrationsAnnotationProvider : SqlServerMigrationsAnnotationProvider 
{ 
    public override IEnumerable<IAnnotation> For(IIndex index) 
    { 
     var baseAnnotations = base.For(index); 
     var customAnnotatinos = index.GetAnnotations().Where(a => a.Name == "SqlServer:FilteredIndex"); 

     return baseAnnotations.Concat(customAnnotatinos); 
    } 
} 

Ora arriva la parte più difficile. Dobbiamo ignorare il comportamento predefinito di SqlServerMigrationsSqlGenerator per quanto riguarda gli indici.

class ExtendedSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator 
{ 
    public ExtendedSqlServerMigrationsSqlGenerator(IRelationalCommandBuilderFactory commandBuilderFactory, ISqlGenerationHelper sqlGenerationHelper, IRelationalTypeMapper typeMapper, IRelationalAnnotationProvider annotations, IMigrationsAnnotationProvider migrationsAnnotations) : base(commandBuilderFactory, sqlGenerationHelper, typeMapper, annotations, migrationsAnnotations) 
    { 
    } 

    protected override void Generate(CreateIndexOperation operation, IModel model, MigrationCommandListBuilder builder, bool terminate) 
    { 
     base.Generate(operation, model, builder, false); 

     var filteredIndexCondition = operation.FindAnnotation("SqlServer:FilteredIndex"); 

     if (filteredIndexCondition != null) 
      builder.Append($" WHERE {filteredIndexCondition.Value}"); 

     if (terminate) 
     { 
      builder.AppendLine(SqlGenerationHelper.StatementTerminator); 
      EndStatement(builder); 
     } 
    } 
} 

Come potete vedere, stiamo chiamando il generatore di base qui, così la nostra condizione sarà aggiunto alla fine di esso senza alterarla. Dobbiamo ricordare di non terminare l'istruzione SQL di base qui (l'ultimo argomento passato al metodo base.Generate è false). Se la nostra annotazione è impostata, possiamo aggiungere il suo valore dopo la clausola WHERE alla fine dell'istruzione SQL. Dopodiché, a seconda dell'argomento passato a questo metodo, possiamo finalmente terminare l'istruzione o lasciarla così com'è.

Per tutte queste parti al lavoro, è necessario sostituire i vecchi servizi con le loro nuove versioni sostituendo il metodo OnConfiguring del nostro DbContext.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 
    { 
     optionsBuilder.ReplaceService<SqlServerMigrationsAnnotationProvider, ExtendedSqlServerMigrationsAnnotationProvider>(); 
     optionsBuilder.ReplaceService<SqlServerMigrationsSqlGenerator, ExtendedSqlServerMigrationsSqlGenerator>(); 
    } 

Ora possiamo usare il nostro metodo di estensione in questo modo:

builder.HasIndex(a => a.Identity).IsUnique().Filtered("[End] IS NULL"); 

genererà la migrazione in questo modo:

migrationBuilder.CreateIndex(
      name: "IX_Activities_Identity", 
      table: "Activities", 
      column: "Identity", 
      unique: true) 
      .Annotation("SqlServer:FilteredIndex", "[End] IS NULL"); 

E dopo aver chiamato Script-Migration commad in Console Package Manager vedremo un SQL risultante come questo:

CREATE UNIQUE INDEX [IX_Activities_Identity] ON [Activities] ([Identity]) WHERE [End] IS NULL; 

Questo metodo può essere effettivamente utilizzato per includere qualsiasi generatore SQL personalizzato in ef core fluent api. Almeno fino a quando l'API EF rimane la stessa.