Zichtbaarheidswaarschuwing :Geef de ander geen antwoord. Het geeft onjuiste waarden. Lees verder voor waarom het verkeerd is.
Gezien de kludge die nodig is om UPDATE
. te maken met OUTPUT
werk in SQL Server 2008 R2, ik heb mijn vraag gewijzigd van:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
naar:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
Eigenlijk ben ik gestopt met het gebruik van OUTPUT
. Dit is niet zo erg als Entity Framework zelf gebruikt dezelfde hack!
Hopelijk 2012 2014 2016 2018 2019 2020 zal een betere implementatie hebben.
Update:het gebruik van OUTPUT is schadelijk
Het probleem waarmee we begonnen was het gebruik van de OUTPUT
clausule om de "na" . op te halen waarden in een tabel:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
Dat raakt dan de bekende beperking ("will't-fix" bug) in SQL Server:
De doeltabel 'BatchReports' van de DML-instructie mag geen geactiveerde triggers hebben als de instructie een OUTPUT-clausule bevat zonder INTO-clausule
Oplossingpoging #1
Dus we proberen iets waarbij we een tussenliggende TABLE
. zullen gebruiken variabele om de OUTPUT
. vast te houden resultaten:
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
Behalve dat dat niet lukt omdat je geen timestamp
mag invoegen in de tabel (zelfs een tijdelijke tabelvariabele).
Oplossingpoging #2
We weten stiekem dat een timestamp
is eigenlijk een 64-bits (ook bekend als 8 byte) geheel getal zonder teken. We kunnen onze tijdelijke tabeldefinitie wijzigen om binary(8)
te gebruiken in plaats van 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
En dat werkt, behalve dat de waarde verkeerd is .
Het tijdstempel RowVersion
we return is niet de waarde van het tijdstempel zoals het bestond nadat de UPDATE voltooid was:
- tijdstempel geretourneerd :
0x0000000001B71692
- werkelijke tijdstempel :
0x0000000001B71693
Dat komt omdat de waarden OUTPUT
in onze tabel zijn niet de waarden zoals ze waren aan het einde van de UPDATE-instructie:
- UPDATE-instructie die begint
- wijzigt rij
- tijdstempel is bijgewerkt (bijv. 2 → 3)
- OUTPUT haalt nieuwe tijdstempel op (d.w.z. 3)
- trigger loopt
- wijzigt rij opnieuw
- tijdstempel is bijgewerkt (bijv. 3 → 4)
- wijzigt rij opnieuw
- wijzigt rij
- UPDATE-instructie voltooid
- OUTPUT retourneert 3 (de verkeerde waarde)
Dit betekent:
- We krijgen de tijdstempel niet zoals deze bestaat aan het einde van de UPDATE-instructie (4 )
- In plaats daarvan krijgen we de tijdstempel zoals deze was in het onbepaalde midden van de UPDATE-instructie (3 )
- We krijgen niet de juiste tijdstempel
Hetzelfde geldt voor elke trigger die elke . wijzigt waarde in de rij. De OUTPUT
zal de waarde niet UITVOEREN vanaf het einde van de UPDATE.
Dit betekent dat je OUTPUT niet kunt vertrouwen om ooit de juiste waarden te retourneren.
Deze pijnlijke realiteit is vastgelegd in de BOL:
Kolommen die worden geretourneerd door OUTPUT geven de gegevens weer zoals deze zijn nadat de INSERT-, UPDATE- of DELETE-instructie is voltooid, maar voordat triggers worden uitgevoerd.
Hoe loste Entity Framework het op?
Het .NET Entity Framework gebruikt rowversion voor Optimistic Concurrency. De EF is afhankelijk van het kennen van de waarde van de timestamp
zoals het bestaat nadat ze een UPDATE hebben uitgegeven.
Aangezien u OUTPUT
. niet kunt gebruiken voor alle belangrijke gegevens gebruikt het Entity Framework van Microsoft dezelfde oplossing als ik:
Oplossing #3 - Laatste - Gebruik de OUTPUT-clausule niet
Om de na . op te halen waarden, Entity Framework-problemen:
UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))
SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1
Gebruik geen OUTPUT
.
Ja, het lijdt aan een raceconditie, maar dat is het beste wat SQL Server kan doen.
Hoe zit het met INSERT's
Doe wat Entity Framework doet:
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
Nogmaals, ze gebruiken een SELECT
statement om de rij te lezen, in plaats van vertrouwen te stellen in de OUTPUT-clausule.