Avertissement de visibilité : N'est-ce pas le autre réponse . Il donnera des valeurs incorrectes. Lisez la suite pour savoir pourquoi c'est faux.
Compte tenu de la complexité nécessaire pour faire UPDATE
avec OUTPUT
travailler dans SQL Server 2008 R2, j'ai changé ma requête de :
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
à :
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
En gros, j'ai arrêté d'utiliser OUTPUT
. Ce n'est pas si mal que Entity Framework lui-même utilise ce même hack !
Avec un peu de chance, 2012 2014 2016 2018 2019 2020 aura une meilleure mise en œuvre.
Mise à jour : l'utilisation de OUTPUT est nuisible
Le problème que nous avons rencontré au départ était d'essayer d'utiliser la fonction OUTPUT
pour récupérer le "après" valeurs dans un tableau :
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
On se heurte alors à la limitation bien connue ( "won't-fix" bogue) dans SQL Server :
La table cible 'BatchReports' de l'instruction DML ne peut pas avoir de déclencheurs activés si l'instruction contient une clause OUTPUT sans clause INTO.
Tentative de contournement n°1
Donc nous essayons quelque chose où nous utiliserons un intermédiaire TABLE
pour contenir la variable OUTPUT
les résultats :
DECLARE @t TABLE (
LastModifiedDate datetime,
RowVersion timestamp,
BatchReportID int
)
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid
SELECT * FROM @t
Sauf que cela échoue parce que vous n'êtes pas autorisé à insérer une timestamp
dans la table (même une variable de table temporaire).
Tentative de contournement n°2
Nous savons secrètement qu'un timestamp
est en fait un entier non signé de 64 bits (alias 8 octets). Nous pouvons modifier notre définition de la table temporaire pour utiliser binary(8)
plutôt que timestamp
:
DECLARE @t TABLE (
LastModifiedDate datetime,
RowVersion binary(8),
BatchReportID int
)
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid
SELECT * FROM @t
Et ça marche, sauf que les valeurs sont fausses .
L'horodatage RowVersion
que nous retournons n'est pas la valeur de l'horodatage tel qu'il existait après la fin de l'UPDATE :
-
date de retour :
0x0000000001B71692
-
horodatage réel :
0x0000000001B71693
C'est parce que les valeurs OUTPUT
dans notre tableau sont pas les valeurs telles qu'elles étaient à la fin de l'instruction UPDATE :
- L'instruction UPDATE commence
- modifie la ligne
- l'horodatage est mis à jour (par exemple, 2 → 3)
- OUTPUT récupère le nouvel horodatage (c'est-à-dire 3)
- déclencheurs
- modifie à nouveau la ligne
- l'horodatage est mis à jour (par exemple, 3 → 4)
- Déclaration UPDATE complète
- retours OUTPUT 3 (la mauvaise valeur)
Cela signifie :
- Nous n'obtenons pas l'horodatage tel qu'il existe à la fin de l'instruction UPDATE ( 4 )
- Au lieu de cela, nous obtenons l'horodatage tel qu'il était au milieu indéterminé de l'instruction UPDATE ( 3 )
- Nous n'obtenons pas l'horodatage correct
Il en va de même pour tout qui modifie tout dans la ligne. Le site OUTPUT
ne sortira pas la valeur à la fin de l'UPDATE.
Cela signifie que vous ne pouvez pas faire confiance à OUTPUT pour retourner des valeurs correctes.
Cette réalité douloureuse est documentée dans le BOL :
Les colonnes renvoyées par OUTPUT reflètent les données telles qu'elles sont après la fin de l'instruction INSERT, UPDATE ou DELETE, mais avant l'exécution des déclencheurs.
Comment Entity Framework a-t-il résolu ce problème ?
L'Entity Framework de .NET utilise rowversion pour la Concurrence Optimiste. L'EF dépend de la connaissance de la valeur de la variable timestamp
tel qu'il existe après qu'ils aient émis un UPDATE.
Puisque vous ne pouvez pas utiliser OUTPUT
pour toute donnée importante, Entity Framework de Microsoft utilise la même solution que moi :
Solution #3 - Final - Ne pas utiliser la clause OUTPUT
Afin de récupérer le après les valeurs, les problèmes d'Entity Framework :
UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))
SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1
N'utilisez pas OUTPUT
.
Oui, il souffre d'une condition de course, mais c'est le mieux que SQL Server puisse faire.
Qu'en est-il des INSERTS ?
Faites ce que fait Entity Framework :
SET NOCOUNT ON;
DECLARE @generated_keys table([CustomerID] int)
INSERT Customers (FirstName, LastName)
OUTPUT inserted.[CustomerID] INTO @generated_keys
VALUES ('Steve', 'Brown')
SELECT t.[CustomerID], t.[CustomerGuid], t.[RowVersion], t.[CreatedDate]
FROM @generated_keys AS g
INNER JOIN Customers AS t
ON g.[CustomerGUID] = t.[CustomerGUID]
WHERE @@ROWCOUNT > 0
Encore une fois, ils utilisent un SELECT
pour lire la ligne, plutôt que de faire confiance à la clause OUTPUT.