In Entity Framework 6.1 (non Core), come posso utilizzare l’attributo IndexAttribute per definire un indice cluster?

Entity Framework 6.1 (code-first) ha aggiunto la possibilità di aggiungere indici tramite l’ IndexAttribute . L’attributo accetta un parametro per specificare se l’indice deve essere cluster o non cluster.

Allo stesso tempo, AFAIK, Entity Framework richiede che ogni quadro abbia una chiave primaria (annotata con KeyAttribute ) e che la chiave primaria sia sempre creata come una chiave di gruppo.

Pertanto, non appena applico IndexAttribute con IsClustered = true , viene visualizzato un errore perché, a causa della chiave, esiste già un indice cluster.

Quindi, come posso creare un indice cluster che non sia la chiave primaria utilizzando l’ IndexAttribute ? La proprietà IsClustered di IndexAttribute utilizzabile?

(Per un po ‘più di contesto: sto mappando una tabella che viene utilizzata solo per la lettura tramite query LINQ. Non è necessario inserire, aggiornare o eliminare effettivamente entity framework da quella tabella. Pertanto, non ho bisogno di una chiave primaria Idealmente, mi piacerebbe una tabella senza una chiave primaria, ma con un indice cluster non univoco ottimizzato per la lettura.)

Modifica (2014-04-11): vedere anche https://entityframework.codeplex.com/workitem/2212 .

Può esistere un solo indice cluster su una tabella e, per impostazione predefinita, Entity Framework / Sql Server lo inserisce sulla chiave primaria.

Quindi, a che serve l’attributo IsClustered su un indice che non è la chiave primaria? Buona domanda! (+1)

Questa class:

 public class Blog { [Key()] public int Id { get; set; } [MaxLength(256)]//Need to limit size of column for clustered indexes public string Title { get; set; } [Index("IdAndRating", IsClustered = true)] public int Rating { get; set; } } 

genererà questa migrazione:

  public override void Up() { CreateTable( "dbo.Blogs", c => new { Id = c.Int(nullable: false, identity: true), Title = c.String(maxLength: 256), Rating = c.Int(nullable: false), }); .PrimaryKey(t => t.Id) .Index(t => t.Rating, clustered: true, name: "IdAndRating"); } 

Alter la migrazione a questo:

  public override void Up() { CreateTable( "dbo.Blogs", c => new { Id = c.Int(nullable: false, identity: true), Title = c.String(maxLength: 256), Rating = c.Int(nullable: false), }); CreateIndex("dbo.Blogs", new[] { "Rating", "Title" }, clustered: true, name: "IdAndRating"); } 

E questo dovrebbe creare la tua tabella senza una chiave primaria ma con l’indice cluster sulle altre colonne

MODIFICA Nello scenario in cui non è necessario inserire, aggiornare o eliminare dati, non è necessaria un’ quadro completa, è ansible utilizzare query sql raw per popolare le classi. Dovresti aggiungere il tuo sql alla migrazione per creare la tabella perché EF non lo automatizzerà, ma ciò significa che puoi creare la tabella e l’indice come vuoi tu.

È ansible derivare la propria class da SqlServerMigrationSqlGenerator e modificare la creazione di pk qui:

 public class NonClusteredPrimaryKeySqlMigrationSqlGenerator : SqlServerMigrationSqlGenerator { protected override void Generate(System.Data.Entity.Migrations.Model.AddPrimaryKeyOperation addPrimaryKeyOperation) { addPrimaryKeyOperation.IsClustered = false; base.Generate(addPrimaryKeyOperation); } protected override void Generate(System.Data.Entity.Migrations.Model.CreateTableOperation createTableOperation) { createTableOperation.PrimaryKey.IsClustered = false; base.Generate(createTableOperation); } protected override void Generate(System.Data.Entity.Migrations.Model.MoveTableOperation moveTableOperation) { moveTableOperation.CreateTableOperation.PrimaryKey.IsClustered = false; base.Generate(moveTableOperation); } 

esempio completo qui https://entityframework.codeplex.com/workitem/2163

Dicendoti la verità, l’IndexAttribute è totalmente ridondante e non adatto allo sviluppo della professione. Mancano di funzionalità di base e si concentrano su cose che hanno poco senso.

Perché? Perché non può mai e dovrebbe essere flessibile come uno script di costruzione. L’indice cluster è solo una cosa – la prossima cosa che mi mancherebbe è un indice filtrato, per lo più in forma di “Indice univoco per non null, indice non univoco per null” su un campo, che mi capita di usare molto regolarmente per facoltativo codici univoci (perché in SQL Server un NULL è uguale a un altro NULL nella generazione SQL, quindi puoi avere solo un NULL alla volta in un indice univoco).

Se fossi in te, starei lontano dalla generazione di database – e dalle migrazioni – e userei un approccio classico di impostazione / migrazione degli script. È qualcosa in cui è ansible eseguire migrazioni di più passaggi più complesse senza possibilità di perdita. EF non gestisce nulla se non gli scenari più basilari – e in queste aree dubito che sia sufficiente. Può essere perché anche io lavoro principalmente su database di grandi dimensioni in cui eseguiamo le nostre modifiche con molta attenzione: aggiungere un indice può richiedere del tempo quando si raggiunge un numero a doppia cifra di miliardi di righe (! 0+).

Preferirei che gli sviluppatori si concentrassero su alcune delle aree mancanti che non possono essere facilmente lavorate, come le prestazioni, come le funzioni principali di ORM (migliori enumerazioni, cache di secondo livello, API di eliminazione bulk, più inserimenti e aggiornamenti delle prestazioni – tutto ciò che sono fattibili). Il codice prima è bello. Il codice prima di generare e mantenere il database è – doloroso fuori da scenari estremamente semplici.

Di seguito è riportato il codice basato sulla risposta di Raditch che ha funzionato per me. Ciò consente alle chiavi primarie di essere raggruppate in modo predefinito. Potrebbe aver bisogno di essere ottimizzato poiché non usiamo le migrazioni ef integrate per gestire effettivamente le modifiche

 public class NonClusteredPrimaryKeySqlMigrationSqlGenerator : SqlServerMigrationSqlGenerator { public override IEnumerable Generate(IEnumerable migrationOperations, string providerManifestToken) { var primaries = migrationOperations.OfType().Where(x => x.PrimaryKey.IsClustered).Select(x => x.PrimaryKey).ToList(); var indexes = migrationOperations.OfType().Where(x => x.IsClustered).ToList(); foreach (var index in indexes) { var primary = primaries.Where(x => x.Table == index.Table).SingleOrDefault(); if (primary != null) { primary.IsClustered = false; } } return base.Generate(migrationOperations, providerManifestToken); } } public class EFCustomConfiguration : DbConfiguration { public EFCustomConfiguration() { SetMigrationSqlGenerator("System.Data.SqlClient", () => new NonClusteredPrimaryKeySqlMigrationSqlGenerator()); } } 

Scrivo qui la mia soluzione se qualcuno ancora interessato a questo argomento. Sotto il codice cambia l’output del comando add-migration.

 public class CustomMigrationCodeGenerator : CSharpMigrationCodeGenerator { protected override void Generate(CreateTableOperation createTableOperation, IndentedTextWriter writer) { if (createTableOperation.Columns.Any(x => x.Name == "Index") && createTableOperation.Columns.Any(x => x.Name == "Id")) { if (createTableOperation.PrimaryKey != null) { createTableOperation.PrimaryKey.IsClustered = false; } } base.Generate(createTableOperation, writer); } } 

È ansible registrare questo generatore nella configurazione di migrazione:

 internal sealed class Configuration : DbMigrationsConfiguration { public Configuration() { AutomaticMigrationsEnabled = false; CodeGenerator = new CustomMigrationCodeGenerator(); SetSqlGenerator("System.Data.SqlClient", new CustomMigrationSqlGenerator()); } protected override void Seed(Ubrasoft.Freeman.WebApi.Db.MainDb context) { } } 

Ed ecco il codice di migrazione generato:

 public override void Up() { CreateTable( "Tenant.Tenant", c => new { Id = c.Guid(nullable: false), TenantNo = c.Byte(nullable: false), Name = c.String(nullable: false, maxLength: 20), Index = c.Int(nullable: false, identity: true), CreatedDate = c.DateTime(nullable: false, precision: 0, storeType: "datetime2"), UpdatedDate = c.DateTime(nullable: false, precision: 0, storeType: "datetime2"), IsDeleted = c.Boolean(nullable: false), }) .PrimaryKey(t => t.Id, clustered: false) .Index(t => t.Index, unique: true, clustered: true); } 

Ecco l’articolo su MigrationCodeGenerator personalizzato.