Comment puis-je faire une instruction INSERT générée par Entity Framework pour inclure une colonne IDENTITY générée par une database?

C'est Entity Framework 6.1.3

La table SQL Server possède une key composite à deux colonnes.

ID INT Identity(1,1) NOT NULL VERSION INT NOT NULL 

L'insertion d'un nouvel logging fonctionne parce que je ne définis pas l'ID sur mon object; Je n'ai mis que la VERSION. Donc, un nouvel logging ressemblerait à ceci:

 ID VERSION 1 1 

Parfait! La database génère l'ID car la colonne est configurée avec Identity et mon model est décoré avec [DatabaseGenerated (DatabaseGeneratedOption.Identity)].

Mais maintenant je dois insert une autre ligne avec le même ID mais une VERSION différente; d'où la key composite. Donc, je m'attendrais à ce que la deuxième rangée soit:

 ID Version 1 1 1 2 <- second row has same ID and different version 

J'ai besoin de cela pour travailler dans les deux sens, car il y a le scénario où un nouvel ID devrait être généré automatiquement par la database, et l'autre scénario où j'ai le même ID mais une VERSION différente.

Le problème: Parce que mon code-First model a l'ID configuré avec DatabaseGeneratedOption.Identity, lorsque je définis la propriété ID sur mon object, mon SaveChanges génère l'instruction d'insertion sans l'ID!

(devises de diagnostic dans VS montre que Entity Framework a généré cette instruction)

 ADO.NET: Execute Reader "INSERT [dbo].[T1]([Version], ... VALUES (@0, ...) 

Notez l'omission de l'ID. Parce que j'ai explicitement défini l'ID sur mon object, je m'attendais à voir cette déclaration à la place.

 INSERT [dbo].[T1]([ID], [Version], ... VALUES (@0, @1, ...) 

C'est ce que j'essaie d'accomplir.

La question est: Comment puis-je faire Entity Framework inclure cette colonne ID dans son instruction d'insertion générée d'une manière élégante?

Je ne veux pas utiliser une procédure stockée ou un code dur une instruction SQL ou hacker l'instruction d'insertion en "serrant" dans la colonne.

S'il n'y a aucun moyen, je sais que je devrais complètement supprimer l'utilisation de l'identité et définir mes propres identifiants, que j'essaie d'éviter.

De plus, mon SaveChanges () utilise déjà SET IDENTITY_INSERT ON / OFF, donc ce n'est pas un problème.

Voici la partie pertinente de mon model: (j'ai omis d'autres propriétés)

 [Key, Column(Order = 0)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } [Key, Column(Order = 1)] public int VERSION { get; set; } 

Une piste que j'ai explorée était de réinitialiser mon DbContext avec une variante de OnModelCreating, mais cela n'a pas fait de différence. Bien sûr, dans cette révision, j'ai supprimé le décorateur DatabaseGenerated de ma propriété ID dans la class. J'ai inséré ceci dans OnModelCreating:

 if (this.AllowIdentityInsert) { modelBuilder.Entity<T1>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } else { modelBuilder.Entity<T1>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } 

Si je pouvais contrôler le model avec succès en changeant la propriété ID à DatabaseGeneratedOption à None avant mes SaveChanges, cela pourrait fonctionner et être une solution élégante.

Quelqu'un at-il rencontré cette situation et trouvé une bonne solution? Merci beaucoup pour votre consortingbution ou vos suggestions.

Généralement, vous ne voulez pas utiliser une colonne d'identité de cette manière, mais je suppose que si vous utilisez une key composite, vous le pouvez. Le problème auquel vous devrez faire face pour insert votre deuxième logging est que vous devrez activer et désactiver IDENTITY_INSERT . Donc, en pensant au SQL de celui-ci est un exemple pour vous montrer ce qui doit être fait pour accomplir la tâche.

 IF OBJECT_ID('tempdb..#TblName') IS NOT NULL BEGIN DROP TABLE #TblName END CREATE TABLE #TblName ( ID INT IDENTITY(1,1) NOT NULL, Version INT NOT NULL ) INSERT INTO #TblName (Version) VALUES (1) SET IDENTITY_INSERT #TblName ON INSERT INTO #TblName (ID, Version) VALUES (1,2) SET IDENTITY_INSERT #TblName OFF SELECT * FROM #TblName 

Une design plus typique consiste à maintenir une table de journal via un triggersur et à y stocker l'historique. Parce que dans cette table il n'aurait pas besoin de la colonne d'identité simplement un autre INT.

Il existe quelques autres designs de table 2 pour contourner la limitation, mais vous pouvez également vouloir créer SQL SEQUENCE https://msdn.microsoft.com/en-us/library/ff878058.aspx et au lieu d'utiliser IDENTITY sur le Colonne ID une SEQUENCE lorsque vous en avez besoin et en insérant toujours la valeur. Si vous utilisez une SEQUENCE vous aurez l'avantage supplémentaire d'être en mesure d'append une autre colonne IDENTITY qui sera généralement un ID de table locale, plutôt que de se fier uniquement à la key composite.

Ok, voici (pour moi) une façon très intéressante de contourner votre problème d'IDENTITÉ et de maintenir une "version incrémentée". Vous pouvez utiliser une vue Mise à jour possible au lieu d'utiliser directement votre table. Vous devez utiliser 2 SEQUENCES une pour ID et une pour VersionId , puis pour get Version, vous utiliserez ROW_NUMBER() dans la view . Vous pouvez étendre cette solution en ajoutant le INSTEAD OF INSERT/UPDATE sortinggger pour gérer le paramétrage de l'IDS plus automatiquement, mais je n'aime généralement pas les sortingggers. En tout cas, voici pour moi une solution intéressante:

 CREATE TABLE dbo.yourTable ( TableId INT NOT NULL IDENTITY(1,1) ,Id INT NOT NULL ,VersionId INT NOT NULL ,Col VARCHAR(100) NOT NULL ,PRIMARY KEY (Id, VersionId) ) GO CREATE SEQUENCE dbo.SEQ_yourTableIdBy1 START WITH 1 INCREMENT BY 1; GO CREATE SEQUENCE dbo.SEQ_yourTableVersionIdBy1 START WITH 1 INCREMENT BY 1; GO CREATE VIEW dbo.yourTable_v AS SELECT Id ,VersionId ,Version = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY VersionId) ,Col ,LatestVersion = CASE WHEN ROW_NUMBER() OVER (PARTITION BY Id ORDER BY VersionId DESC) = 1 THEN 1 ELSE 0 END FROM dbo.yourTable GO --New Record INSERT INTO dbo.yourTable_v(Id, VersionId, Col) VALUES (NEXT VALUE FOR dbo.SEQ_yourTableIdBy1, NEXT VALUE FOR dbo.SEQ_yourTableVersionIdBy1, 'A') SELECT * FROM dbo.yourTable_v --Change To Existing Record INSERT INTO dbo.yourTable_v(Id, VersionId, Col) VALUES (1, NEXT VALUE FOR dbo.SEQ_yourTableVersionIdBy1, 'B') SELECT * FROM dbo.yourTable_v 

lien montrant comment cela fonctionne http://rextester.com/GBHG23338

Pour faire Entity Framework croire que la vue est une table, vous devrez peut-être changer la définition de key et le type d'entité ici est un blog MSDN sur le sujet. https://blogs.msdn.microsoft.com/alexj/2009/09/01/tip-34-how-to-work-with-updatable-views/

Avantages:

  • cela ne va pas se casser si 2 personnes essayent de se soumettre simultanément etc.
    • Entity Framework pensera qu'il s'agit d'une table normale une fois que vous l'aurez simulé en suivant le lien.
    • Bien que VersionId ait été incrémenté sur tous les loggings, il présentera toujours une Version + 1 agréable à utiliser dans votre application.
    • Et vous pouvez facilement append une dernière colonne de version à utiliser dans votre application / requêtes.