a.k.a Comment créer plusieurs colonnes d'identité dans Code First?
En raison des performances de clustering, une recommandation courante consiste à utiliser une colonne entière auto-incrémentée au lieu d'un GUID créé avec newid()
.
Pour déclarer une colonne comme incrémentation automatique, vous devez la spécifier avec l'annotation [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
.
Mais, vous ne pouvez avoir qu'une seule identité dans une table.
Donc, en commençant par un modèle de base comme:
public abstract class ModelBase {
// the primary key
public virtual Guid Id { get; set; }
// a unique autoincrementing key
public virtual int ClusterId { get; set; }
}
comment le configurer pour que:
ClusterId
est auto-incrémentéFYI , si vous voulez le générer automatiquement dans le code, vous pouvez ignorer l'annotation sur le champ Id et faire quelque chose comme:
public abstract class AbstractContext : DbContext {
/// <summary>
/// Custom processing when saving entities in changetracker
/// </summary>
/// <returns></returns>
public override int SaveChanges()
{
// recommended to explicitly set New Guid for appropriate entities -- http://msdn.Microsoft.com/en-us/library/dd283139.aspx
foreach (var entry in ChangeTracker.Entries<ModelBase>().Where(e => e.State == EntityState.Added) ) {
// only generate if property isn't identity...
Type t = entry.Entity.GetType();
var info = t.GetProperty("Id").GetCustomAttributes(
typeof(DatabaseGeneratedAttribute), true).Cast<DatabaseGeneratedAttribute>().Single();
if (info.DatabaseGeneratedOption != DatabaseGeneratedOption.Identity) {
entry.Entity.Id = Guid.NewGuid(); // now we make it
}
}
return base.SaveChanges();
}
}
Cela a fini par fonctionner pour moi, Entity Framework 5.
Déclarez le ClusterId
comme identité (annotation)
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override int ClusterId { get; set; }
Émigrer
Déclarez la propriété pk Id
comme identité après la mise à jour de l'autre
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override Guid Id { get; set; }
Id
est la clé primaire, donc vous n'avez pas besoin de [Key, Required]
Créez le code de migration comme add-migration TrickEfIntoAutogeneratingMultipleColumns
Up()
, dans l'instruction AlterColumn
, indiquez à la base de données de générer automatiquement le GUID en déclarant le defaultSqlValue
AlterColumn(theTable, "Id", c => c.Guid(nullable: false, identity: true, defaultValueSql: "newid()"));
Cela semble "tromper" EF, dans le sens où il suppose que les deux colonnes sont des identités et réagit en conséquence. Pendant la migration, il essaie de faire une autre colonne une identité, mais ne se soucie apparemment pas de l'échec silencieux - vous vous retrouvez avec l'une marquée comme identité et l'autre avec une valeur par défaut.
Pendant le fonctionnement normal du code, lorsque EF passe par les étapes SaveChanges/ChangeTracking, car il voit la propriété Id
comme une identité, il fait tout --- chose "assign temporaire key" , de sorte qu'il soit n'essaie pas d'utiliser la valeur par défaut 0000000 ... et laisse à la place la base de données la générer à l'aide de la fonction de valeur par défaut que vous avez spécifiée.
(J'aurais pensé qu'annoter ce champ comme Computed
aurait accompli la même chose, mais ... les erreurs que j'ai mentionnées dans la question ... boo ...)
Et, parce que le champ ClusterId
est également une identité dans le code, et est vraiment une identité dans la base de données, il s'auto-incrémente également.