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