Avertissement de visibilité :Ne répondez pas à l'autre. Cela donnera des valeurs incorrectes. Lisez la suite pour savoir pourquoi c'est faux.
Étant donné le travail nécessaire pour faire UPDATE
avec OUTPUT
travail 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 !
Espérons 2012 2014 2016 2018 2019 2020 aura une meilleure mise en œuvre.
Mise à jour :l'utilisation de OUTPUT est nuisible
Le problème avec lequel nous avons commencé était d'essayer d'utiliser le OUTPUT
clause 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
Cela atteint 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 #1
Nous essayons donc quelque chose où nous utiliserons une TABLE
intermédiaire variable pour contenir le OUTPUT
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 car vous n'êtes pas autorisé à insérer un timestamp
dans la table (même une variable de table temporaire).
Tentative de contournement #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 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 la valeur est fausse .
L'horodatage RowVersion
nous renvoyons n'est pas la valeur de l'horodatage tel qu'il existait après la MISE À JOUR :
- horodatage renvoyé :
0x0000000001B71692
- horodatage réel :
0x0000000001B71693
C'est parce que les valeurs OUTPUT
dans notre table ne sont pas les valeurs telles qu'elles étaient à la fin de l'instruction UPDATE :
- Début de l'instruction UPDATE
- modifie la ligne
- l'horodatage est mis à jour (par exemple 2 → 3)
- OUTPUT récupère le nouvel horodatage (c'est-à-dire 3)
- le déclencheur s'exécute
- modifie à nouveau la ligne
- l'horodatage est mis à jour (par exemple 3 → 4)
- modifie à nouveau la ligne
- modifie la ligne
- Instruction UPDATE terminée
- OUTPUT renvoie 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 se trouvait au milieu indéterminé de l'instruction UPDATE (3 )
- Nous n'obtenons pas le bon horodatage
Il en va de même pour tout déclencheur qui modifie tout valeur dans la ligne. Le OUTPUT
ne SORTIRA PAS la valeur à la fin de la MISE À JOUR.
Cela signifie que vous ne pouvez pas faire confiance à OUTPUT pour renvoyer des valeurs correctes.
Cette douloureuse réalité est documentée dans le BOL :
Les colonnes renvoyées par OUTPUT reflètent les données telles qu'elles sont après l'exécution de l'instruction INSERT, UPDATE ou DELETE, mais avant l'exécution des déclencheurs.
Comment Entity Framework a-t-il résolu le problème ?
Le .NET Entity Framework utilise rowversion pour Optimistic Concurrency. L'EF dépend de la connaissance de la valeur du timestamp
tel qu'il existe après la publication d'une MISE À JOUR.
Puisque vous ne pouvez pas utiliser OUTPUT
pour toutes les données importantes, Entity Framework de Microsoft utilise la même solution de contournement que moi :
Solution #3 - Finale - Ne pas utiliser la clause OUTPUT
Afin de récupérer l'après valeurs, 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 concurrence, 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.