Synlighedsadvarsel :Lad være med at svare den anden. Det vil give forkerte værdier. Læs videre for, hvorfor det er forkert.
Givet den kludge, der er nødvendig for at lave UPDATE
med OUTPUT
arbejde i SQL Server 2008 R2, ændrede jeg min forespørgsel fra:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
til:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
Grundlæggende holdt jeg op med at bruge OUTPUT
. Dette er ikke så slemt som selve Entity Framework bruger det samme hack!
Forhåbentlig 2012 2014 2016 2018 2019 2020 vil have en bedre implementering.
Opdatering:Brug af OUTPUT er skadeligt
Det problem, vi startede med, var at prøve at bruge OUTPUT
klausul for at hente "efter" værdier i en tabel:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
Det rammer så den velkendte begrænsning ("vil ikke rette" bug) i SQL Server:
Måltabellen 'BatchReports' i DML-sætningen kan ikke have nogen aktiverede triggere, hvis sætningen indeholder en OUTPUT-sætning uden INTO-sætning
Løsningsforsøg #1
Så vi prøver noget, hvor vi vil bruge en mellemliggende TABLE
variabel for at holde OUTPUT
resultater:
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
Bortset fra at det mislykkes, fordi du ikke har tilladelse til at indsætte et timestamp
ind i tabellen (selv en midlertidig tabelvariabel).
Løsningsforsøg #2
Vi ved i al hemmelighed, at et timestamp
er faktisk et 64-bit (alias 8 byte) heltal uden fortegn. Vi kan ændre vores midlertidige tabeldefinition til at bruge binary(8)
i stedet for 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
Og det virker, bortset fra at værdien er forkert .
Tidsstemplet RowVersion
vi returnerer er ikke værdien af tidsstemplet, som det eksisterede efter OPDATERING fuldført:
- returneret tidsstempel :
0x0000000001B71692
- faktisk tidsstempel :
0x0000000001B71693
Det er fordi værdierne OUTPUT
i vores tabel er ikke værdierne, som de var i slutningen af UPDATE-sætningen:
- UPDATE-erklæring starter
- ændrer række
- tidsstemplet er opdateret (f.eks. 2 → 3)
- OUTPUT henter nyt tidsstempel (dvs. 3)
- trigger kører
- ændrer række igen
- tidsstemplet er opdateret (f.eks. 3 → 4)
- ændrer række igen
- ændrer række
- OPDATERING er fuldført
- OUTPUT returnerer 3 (den forkerte værdi)
Det betyder:
- Vi får ikke tidsstemplet, da det findes i slutningen af UPDATE-sætningen (4 )
- I stedet får vi tidsstemplet, som det var i den ubestemte midte af UPDATE-sætningen (3 )
- Vi får ikke det korrekte tidsstempel
Det samme gælder enhver trigger, der ændrer enhver værdi i rækken. OUTPUT
vil ikke OUTPUT værdien fra slutningen af OPDATERING.
Dette betyder, at du aldrig kan stole på, at OUTPUT returnerer nogen korrekte værdier.
Denne smertefulde virkelighed er dokumenteret i BOL:
Kolonner, der returneres fra OUTPUT, afspejler dataene, som de er, efter INSERT-, UPDATE- eller DELETE-sætningen er fuldført, men før triggere udføres.
Hvordan løste Entity Framework det?
.NET Entity Framework bruger rækkeversion til Optimistisk samtidighed. EF afhænger af at kende værdien af timestamp
som det eksisterer efter de har udstedt en OPDATERING.
Da du ikke kan bruge OUTPUT
for alle vigtige data bruger Microsofts Entity Framework den samme løsning, som jeg gør:
Løsning #3 - Endelig - Brug ikke OUTPUT-sætning
For at hente efter værdier, Entity Framework-spørgsmål:
UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))
SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1
Brug ikke OUTPUT
.
Ja, den lider af en racetilstand, men det er det bedste SQL Server kan gøre.
Hvad med INSERTs
Gør hvad Entity Framework gør:
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
Igen bruger de en SELECT
sætning for at læse rækken, i stedet for at stole på OUTPUT-sætningen.