web-dev-qa-db-fra.com

Code Entity Framework utilisant d'abord Guid comme identité avec une autre colonne d'identité

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:

  1. Guid est généré automatiquement par la base de données, pas le code
  2. ClusterId est auto-incrémenté
  3. Entity Framework Code First ne génère pas toutes sortes d'erreurs telles que:
    • Les modifications apportées aux tables où une colonne de clé primaire a la propriété 'StoreGeneratedPattern' définie sur 'Calculé' ne sont pas prises en charge. Utilisez plutôt le modèle 'Identité'.

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();
  }

}
36
drzaus

Cela a fini par fonctionner pour moi, Entity Framework 5.

  1. Désactiver les migrations automatiques
  2. Migrez pour créer la table initiale, sans fioritures
  3. Déclarez le ClusterId comme identité (annotation)

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public override int ClusterId { get; set; }
    
  4. Émigrer

  5. 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; }
    
    • bonus : EF semble supposer que Id est la clé primaire, donc vous n'avez pas besoin de [Key, Required]
  6. Créez le code de migration comme add-migration TrickEfIntoAutogeneratingMultipleColumns

  7. Dans la méthode 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()"));
  8. Émigrer

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.

36
drzaus