web-dev-qa-db-fra.com

Pourquoi cet insert EF avec IDENTITY_INSERT ne fonctionne-t-il pas?

C'est la requête:

using (var db = new AppDbContext())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
}

Lorsqu'elle est exécutée, la Id de l'enregistrement inséré, sur une nouvelle table, est toujours 1.

NOUVEAU: Lorsque j'utilise la transaction ou la réponse de TGlatzer, j'obtiens l'exception:

La valeur explicite doit être spécifiée pour la colonne d'identité dans la table 'Items' soit lorsque IDENTITY_INSERT est défini sur ON ou lorsqu'un utilisateur de réplication est insertion dans une colonne d'identité NOT FOR REPLICATION.

8
ProfK

Cela ne doit jamais être utilisé dans le code de production, c'est juste pour le plaisir  

Je ne suggère pas cela parce que c'est un bidouillage fou, mais quand même.

Je pense que nous pouvons y parvenir en interceptant la commande SQL et en modifiant le texte de la commande.
(vous pouvez hériter de DbCommandInterceptor et de ReaderExecuting)

Je n'ai pas d'exemple de travail pour le moment et je dois y aller mais je pense que c'est faisable

Exemple de code

    public class MyDbInterceptor : DbCommandInterceptor
    {
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {

            if (is your table)
            {
                command.CommandText = "Set Identoty off ,update insert into ,Set Identity off"
                return;
            }
            base.ReaderExecuting(command, interceptionContext);

        }

    }

Les ORM sont une belle abstraction et je les aime beaucoup, mais je ne pense pas que ce soit logique d'essayer de les "bidouiller" pour supporter des opérations de niveau inférieur (plus proche de la base de données).
J'essaie d'éviter les processus stockés mais je pense que dans ce cas (comme vous l'avez dit exceptionnel) je pense que vous devriez en utiliser un

1
George Vovos

Selon cette précédente Question vous devez commencer une transaction de votre contexte. Après avoir enregistré la modification, vous devez également reformuler la colonne Insertion d'identité et, enfin, vous devez valider la transaction. 

using (var db = new AppDbContext())
using (var transaction = db .Database.BeginTransaction())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
    transaction.Commit();
}
7
gdmanandamohon

Pour forcer EF en écrivant l'ID de votre entité, vous devez configurer l'ID de manière à ce qu'il ne soit pas généré, sinon EF ne l'inclura jamais dans l'instruction d'insertion.

Vous devez donc modifier le modèle à la volée et configurer l'ID d'entité selon vos besoins.
Le problème est que le modèle est mis en cache et qu'il est assez difficile de le changer à la volée (je suis sûr que je l'ai fait mais en fait je ne trouve pas le code, probablement je l'ai jeté). Le moyen le plus rapide consiste à créer deux contextes différents dans lesquels vous configurez votre entité de deux manières différentes, sous la forme DatabaseGeneratedOption.None (lorsque vous devez écrire l'ID) et DatabaseGeneratedOption.Identity (lorsque vous avez besoin de l'ID de numérotation automatique).

2
bubi

Je n'ai pas honoré les balises de la question disant qu'il s'agit de EF6.
Cette réponse fonctionnera pour EF Core

Le véritable coupable n’est pas la transaction manquante, mais le petit inconvénient que Database.ExectueSqlCommand() ne gardera pas la connexion ouverte, même si elle n’a pas été explicitement ouverte auparavant.

using (var db = new AppDbContext())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.OpenConnection();
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
}

fera aussi, puisque SET IDENTITY_INSERT [...] ON/OFF sera lié à votre connexion.

1
TGlatzer

J'ai eu un problème très similaire.

La solution était quelque chose comme:

db.Database.ExecuteSqlCommand("disable trigger all on  myTable ;") 
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable  ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable  OFF");
db.Database.ExecuteSqlCommand("enable trigger all on  myTable ;") 

Dans mon cas, le message Explicit value must be specified for identity... était dû à l'insertion d'un déclencheur appelé et insérant autre chose.

ALTER TABLE myTable NOCHECK CONSTRAINT all

Peut aussi être utile

0
Madlozoz

Même si vous désactivez IDENTITY_INSERT, vous venez de dire à SQL que je vous enverrai Identity, vous n'avez pas dit au framework entity de vous envoyer Identity au serveur SQL.

Donc, fondamentalement, vous devez créer DbContext comme indiqué ci-dessous.

// your existing context
public abstract class BaseAppDbContext : DbContext { 


    private readonly bool turnOfIdentity = false;
    protected AppDbContext(bool turnOfIdentity = false){
        this.turnOfIdentity = turnOfIdentity;
    }


    public DbSet<IdentityItem> IdentityItems {get;set;}

    protected override void OnModelCreating(DbModelBuilder modelBuilder){
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<IdentityItem>()
           .HasKey( i=> i.Id )

           // BK added the "Property" line.
           .Property(e => e.Id)
           .HasDatabaseGeneratedOption(
               turnOfIdentity ?
                   DatabaseGeneratedOption.None,
                   DatabaseGeneratedOption.Identity
           );

    }
}

public class IdentityItem{

}


public class AppDbContext: BaseAppDbContext{
    public AppDbContext(): base(false){}
}

public class AppDbContextWithIdentity : BaseAppDbContext{
    public AppDbContext(): base(true){}
}

Maintenant, utilisez-le de cette façon ...

using (var db = new AppDbContextWithIdentity())
{
    using(var tx = db.Database.BeginTransaction()){
       var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
       db.IdentityItems.Add(item);
       db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
       db.SaveChanges();
       db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
       tx.Commit();
    }
}
0
Akash Kava