Sichtbarkeitswarnung :Nicht die andere Antwort. Es werden falsche Werte ausgegeben. Lesen Sie weiter, warum es falsch ist.
Angesichts des Klodges, der benötigt wird, um UPDATE
zu erstellen mit OUTPUT
Arbeit in SQL Server 2008 R2, ich habe meine Abfrage geändert von:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
zu:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
Grundsätzlich habe ich aufgehört, OUTPUT
zu verwenden . Das ist nicht so schlimm wie Entity Framework selbst verwendet genau diesen Hack!
Hoffentlich 2012 2014 2016 2018 2019 2020 wird es eine bessere Umsetzung geben.
Aktualisierung:Die Verwendung von OUTPUT ist schädlich
Das Problem, mit dem wir begonnen haben, war der Versuch, OUTPUT
zu verwenden -Klausel, um das "after" abzurufen Werte in einer Tabelle:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
Das trifft dann auf die bekannte Einschränkung ("wird nicht behoben"). Fehler) in SQL Server:
Die Zieltabelle 'BatchReports' der DML-Anweisung kann keine aktivierten Trigger haben, wenn die Anweisung eine OUTPUT-Klausel ohne INTO-Klausel enthält
Umgehungsversuch Nr. 1
Also versuchen wir etwas, wo wir eine Zwischen-TABLE
verwenden Variable, die OUTPUT
enthält Ergebnisse:
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
Nur dass das fehlschlägt, weil Sie keinen timestamp
einfügen dürfen in die Tabelle (sogar eine temporäre Tabellenvariable).
Umgehungsversuch Nr. 2
Wir wissen insgeheim, dass ein timestamp
ist eigentlich eine vorzeichenlose 64-Bit-Ganzzahl (auch bekannt als 8 Byte). Wir können unsere temporäre Tabellendefinition ändern, um binary(8)
zu verwenden statt 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
Und das funktioniert, außer dass der Wert falsch ist .
Der Zeitstempel RowVersion
Wir geben nicht den Wert des Zeitstempels zurück, wie er nach Abschluss des UPDATE bestand:
- zurückgegebener Zeitstempel :
0x0000000001B71692
- tatsächlicher Zeitstempel :
0x0000000001B71693
Das liegt daran, dass die Werte OUTPUT
in unsere Tabelle sind nicht die Werte, wie sie am Ende der UPDATE-Anweisung standen:
- UPDATE-Anweisung ab
- ändert Zeile
- Zeitstempel wird aktualisiert (z. B. 2 → 3)
- OUTPUT ruft den neuen Zeitstempel (z. B. 3) ab
- Auslöser läuft
- Ändert die Zeile erneut
- Zeitstempel wird aktualisiert (z. B. 3 → 4)
- Ändert die Zeile erneut
- ändert Zeile
- UPDATE-Anweisung abgeschlossen
- AUSGABE gibt 3 zurück (der falsche Wert)
Das bedeutet:
- Wir erhalten den Zeitstempel nicht so, wie er am Ende der UPDATE-Anweisung steht (4 )
- Stattdessen erhalten wir den Zeitstempel, wie er in der unbestimmten Mitte der UPDATE-Anweisung stand (3 )
- Wir erhalten nicht den korrekten Zeitstempel
Dasselbe gilt für any Auslöser, der alle modifiziert Wert in der Zeile. Die OUTPUT
wird den Wert am Ende des UPDATE nicht AUSGEBEN.
Das bedeutet, dass Sie sich nicht darauf verlassen können, dass OUTPUT jemals korrekte Werte zurückgibt.
Diese schmerzhafte Realität ist im BOL dokumentiert:
Von OUTPUT zurückgegebene Spalten geben die Daten wieder, wie sie sind, nachdem die INSERT-, UPDATE- oder DELETE-Anweisung abgeschlossen wurde, aber bevor Trigger ausgeführt werden.
Wie hat Entity Framework es gelöst?
Das .NET Entity Framework verwendet rowversion für optimistische Parallelität. Die EF hängt davon ab, den Wert des timestamp
zu kennen wie es existiert, nachdem sie ein UPDATE herausgegeben haben.
Da Sie OUTPUT
nicht verwenden können Für alle wichtigen Daten verwendet Microsofts Entity Framework die gleiche Problemumgehung wie ich:
Problemumgehung Nr. 3 - Endgültig - Verwenden Sie keine OUTPUT-Klausel
Um das after abzurufen Werte, Entity Framework-Probleme:
UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))
SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1
Verwenden Sie nicht OUTPUT
.
Ja, es leidet unter einer Racebedingung, aber das ist das Beste, was SQL Server tun kann.
Was ist mit INSERTs
Tun Sie, was Entity Framework tut:
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
Auch hier verwenden sie ein SELECT
-Anweisung, die Zeile zu lesen, anstatt der OUTPUT-Klausel zu vertrauen.