Au lieu de déclencher dans SQL Server perd SCOPE_IDENTITY?
J'ai une table où j'ai créé un INSTEAD OF
déclencheur pour faire respecter certaines règles de gestion.
Le problème est que lorsque j'insère des données dans cette table, SCOPE_IDENTITY()
renvoie une NULL
valeur, plutôt que de la réelle identité insérée.
Insert + Champ d'application du code
INSERT INTO [dbo].[Payment]([DateFrom], [DateTo], [CustomerId], [AdminId])
VALUES ('2009-01-20', '2009-01-31', 6, 1)
SELECT SCOPE_IDENTITY()
Déclencheur:
CREATE TRIGGER [dbo].[TR_Payments_Insert]
ON [dbo].[Payment]
INSTEAD OF INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF NOT EXISTS(SELECT 1 FROM dbo.Payment p
INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
WHERE (i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo)
) AND NOT EXISTS (SELECT 1 FROM Inserted p
INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
WHERE (i.DateFrom <> p.DateFrom AND i.DateTo <> p.DateTo) AND
((i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo))
)
BEGIN
INSERT INTO dbo.Payment (DateFrom, DateTo, CustomerId, AdminId)
SELECT DateFrom, DateTo, CustomerId, AdminId
FROM Inserted
END
ELSE
BEGIN
ROLLBACK TRANSACTION
END
END
Le code a travaillé avant la création de ce déclencheur. Je suis à l'aide de LINQ to SQL en C#. Je ne vois pas un moyen de changer de SCOPE_IDENTITY
à @@IDENTITY
. Comment puis-je faire ce travail?
- Je prends cette table de travail avant que vous avez inséré la PLACE DE l'état? Avez-vous vérifié votre champ de Clé Primaire pour s'assurer qu'il a une Identité cahier des charges?
- Peut-on voir le SQL?
- Oui et oui, c'est en effet (vous pouvez voir le code maintenant - il ne l'insertion d'une ligne et c'est une automatique d'identité lorsqu'elle est insérée).
- Pourquoi ne pas utiliser un AVANT déclencheur d'INSERTION qui produit une erreur si les règles ne sont pas satisfaits, plutôt que au LIEU DE?
- parce que pour ce que j'ai pu voir de SQL Server n'a pas une telle chose - même si je dois admettre que je n'ai pas directement en essayer un, mais que, hors d'une recherche sur Google, il peut être bien sûr que j'ai trouvé non valide ou périmée ressources - est-il en fait une telle chose?
- Nous avons utilisé ces déclencheurs dans SQL Server 2000 qui semble être le mode par défaut, déclarant simplement le déclencheur "create trigger TG_xxx sur dbo.tablename pour insérer, mettre à jour aussi ....". Nous avons logique pour des conditions de test et d'appeler "raiserror()" puis faites "rollback transaction" si la validation échoue. Après avoir dit cela, nous utilisons un SP pour générer des Id plutôt que l'identité --- cela pourrait être la raison pour laquelle, le gars qui a insisté pour nous faire de cette façon est un peu vague comme pour le raisonnement. Mais alors qu'il avait pris l'habitude avant de scope_identity() a été mis en œuvre.
- Veuillez considérer Aaron Alton suggestion pour l'utilisation de la sortie pour obtenir le indetity valeurs.
- araqnid, votre méthode est beaucoup plus dangereux pour l'intégrité des données que d'utiliser des champs identité. Vous pouvez créer de réels problèmes avec insertions simultanées si vous n'êtes pas très prudent.
- Oui, l'ID de génération SP fait un peu de travail en plus exclusivement de verrouillage de la table contenant le numéro suivant. Qui elle-même provoque des problèmes de contention. Je ne suis pas fan d'elle, soit (et je n'ai pas l'écrire et ne le recommande pas).
Vous devez vous connecter pour publier un commentaire.
Utilisation
@@identity
au lieu descope_identity()
.Tout
scope_identity()
retourne la dernière création de l'id dans le champ d'application actuel,@@identity
retourne la dernière création de l'id de la session en cours.La
scope_identity()
fonction est normalement recommandé au cours de la@@identity
champ, comme vous ne voulez pas des déclencheurs pour interférer avec l'id, mais dans ce cas, vous ne.Puisque vous êtes sur SQL 2008, je vous recommande fortement d'utiliser la SORTIE de la clause de la place de l'un de la coutume fonctions d'identité. SCOPE_IDENTITY a actuellement quelques problèmes avec les requêtes en parallèle qui me déconseille totalement. @@Identity n'est pas, mais il n'est toujours pas aussi explicite, et la souplesse de la SORTIE. Plus SORTIE gère de multiples insertions de lignes. Jetez un oeil à le BOL de l'article qui a quelques bons exemples.
INSERT TheTable ... | SELECT @LastID = Scope_Identity(), @Rows = @@RowCount | SELECT FROM TheTable WHERE ID BETWEEN @LastID - @Rows + 1 AND @LastID
J'ai eu de sérieuses réserves au sujet de l'utilisation de @@identity, car il peut renvoyer la mauvaise réponse.
Mais il y a une solution pour forcer @@identity pour avoir le scope_identity() valeur.
Juste pour être complet, je vais d'abord la liste d'un couple d'autres solutions de contournement pour ce problème, je l'ai vu sur le web:
Faire la gâchette renvoyer un ensemble de lignes. Puis, dans un emballage SP qui effectue l'insertion, ne
INSERT Table1 EXEC sp_ExecuteSQL ...
encore à une autre table. Puis scope_identity() fonctionne. Cela est ennuyeux, car il nécessite SQL dynamique qui est une douleur. Sachez également que le SQL dynamique s'exécute sous les autorisations de l'utilisateur appelant le SP plutôt que les autorisations du propriétaire de la SP. Si le client peut insérer à la table, il doit encore avoir cette autorisation, il suffit de savoir que vous pourriez rencontrer des problèmes si vous refuser l'autorisation d'insérer directement à la table.Si il y a un autre candidat à la clé, obtenir l'identité de la ligne insérée(s) à l'aide de ces touches. Par exemple, si le Nom a un index unique sur elle, alors vous pouvez insérer, puis sélectionnez le (max pour plusieurs lignes) ID de la table que vous venez d'insérer à l'aide de Nom. Tandis que ceci peut avoir des problèmes de concurrence d'accès si une autre session supprime la ligne que vous venez d'insérer, c'est pas pire que dans la situation d'origine si quelqu'un a supprimé de votre ligne avant l'application pourrait utiliser.
Maintenant, voici comment faire pour définitivement faire de votre détente sans danger pour @@Identity pour renvoyer la valeur correcte, même si votre SP ou un autre déclencheur insère dans une identité de roulement de la table après le principal insérer.
Aussi, merci de mettre des commentaires dans votre code sur ce que vous faites et pourquoi, afin que les futurs visiteurs à la détente n'est pas de casser des choses ou de perdre du temps à essayer de le comprendre.
Normalement, l'insertion supplémentaire à la table d'audit serait tout casser, parce que depuis qu'il a une colonne d'identité, puis @@Identity permet de retourner la valeur plutôt que sur celui de l'insertion de MyTable. Cependant, la dernière sélection crée une nouvelle valeur de @@Identity qui est correcte, basée sur le Scope_Identity() que nous avons sauvé plus tôt. Cela a également des preuves contre d'éventuelles supplémentaires APRÈS le déclenchement sur la table MyTable.
Mise à jour:
Je viens de remarquer qu'un déclencheur INSTEAD OF n'est pas nécessaire ici. C'est tout ce que vous cherchez:
Bien sûr, cela permet scope_identity() pour continuer à travailler. Le seul inconvénient est qu'un restaurée insert sur une table d'identité ne consomment de l'identité, les valeurs utilisées (l'identité de la valeur est incrémentée par le nombre de lignes dans l'insert tentative).
J'ai été regarder cela pendant quelques minutes et de ne pas avoir de certitude absolue en ce moment, mais je pense cela préserve le sens d'une inclusif, l'heure de début et exclusif de fin des temps. Si l'heure de fin a été compris (ce qui serait bizarre pour moi), puis les comparaisons auraient besoin d'utiliser <= au lieu de <.
Problème principal : Déclencheur et Entity framework travaillent tous les deux dans diffrent portée.
Le problème, c'est que si vous générez de nouveaux PK valeur de déclenchement, il est de portée différente. Donc cette commande ne retourne aucune ligne, et EF lève une exception.
La solution est d'ajouter l'instruction SELECT suivante à la fin de votre Déclencheur:
à la place de * vous pouvez parler de tous le nom de la colonne, y compris
Comme araqnid commenté, le déclencheur semble annuler la transaction lorsqu'une condition est remplie. Vous pouvez le faire plus facile avec un APRÈS avoir VIS de déclenchement:
Ensuite, vous pouvez utiliser SCOPE_IDENTITY() de nouveau, car l'INSERTION ne se fait plus dans le déclencheur.
L'état lui-même semble laisser deux lignes identiques passé, si ils sont dans la même insertion. Avec l'APRÈS déclencheur d'INSERTION, vous pouvez réécrire la condition comme:
Et il va attraper des lignes en double, parce que maintenant on peut les différencier selon l'Id. Il fonctionne également si vous supprimez une ligne et de la remplacer par une autre ligne dans la même instruction.
De toute façon, si vous voulez mon avis, éloignez-vous de les déclencheurs tout à fait. Comme vous pouvez le voir, même pour cet exemple, ils sont très complexes. Faire de l'insertion par le biais d'une procédure stockée. Ils sont plus simples et plus rapides que les déclencheurs:
Un peu en retard à la fête, mais j'étais à la recherche sur cette question moi-même. Une solution de contournement consiste à créer une table temporaire dans la procédure d'appel où l'insert est en cours, insérez le champ d'application de l'identité dans la table temporaire de l'intérieur de l'au lieu de déclencher, puis lire la valeur d'identité de la temp de la table une fois que l'insertion est terminée.
Dans la procédure:
Au lieu de déclenchement:
Probablement vous voulez l'appeler autre chose que #temp pour la sécurité de saké (quelque chose de long et aléatoire assez que personne d'autre ne l'utiliser: #temp1234235234563785635).