Varování viditelnosti :Neodpovídejte. Udává nesprávné hodnoty. Přečtěte si, proč je to špatně.
Vzhledem k objemu potřebnému k provedení UPDATE
s OUTPUT
práce v SQL Server 2008 R2, změnil jsem svůj dotaz z:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
komu:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
V podstatě jsem přestal používat OUTPUT
. Není to tak špatné jako samotný Entity Framework používá stejný hack!
Doufám, že 2012 2014 2016 2018 2019 2020 bude mít lepší implementaci.
Aktualizace:používání OUTPUT je škodlivé
Problém, se kterým jsme začali, byl pokus použít OUTPUT
klauzule k načtení "po" hodnoty v tabulce:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
To pak naráží na dobře známé omezení („neopraví“ bug) na serveru SQL:
Cílová tabulka 'BatchReports' příkazu DML nemůže mít žádné povolené spouštěče, pokud příkaz obsahuje klauzuli OUTPUT bez klauzule INTO
Pokus o řešení č. 1
Zkusíme tedy něco, kde použijeme mezilehlou TABLE
proměnnou pro uložení OUTPUT
výsledky:
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
Až na to, že to selže, protože nemáte povoleno vložit timestamp
do tabulky (dokonce i dočasné proměnné tabulky).
Pokus o řešení č. 2
Tajně víme, že timestamp
je ve skutečnosti 64bitové (aka 8 bajtů) celé číslo bez znaménka. Můžeme změnit naši definici dočasné tabulky tak, aby používala binary(8)
spíše než 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
A to funguje,až na to, že hodnoty jsou špatné .
Časové razítko RowVersion
we return není hodnota časového razítka, jak existovala po dokončení UPDATE:
- vrácené časové razítko :
0x0000000001B71692
- skutečné časové razítko :
0x0000000001B71693
Je to proto, že hodnoty OUTPUT
do naší tabulky nejsou hodnoty tak, jak byly na konci příkazu UPDATE:
- Spuštění příkazu UPDATE
- upraví řádek
- Časové razítko je aktualizováno (např. 2 → 3)
- OUTPUT načte nové časové razítko (tj. 3)
- běhy spuštění
- znovu upraví řádek
- Časové razítko je aktualizováno (např. 3 → 4)
- znovu upraví řádek
- upraví řádek
- Prohlášení UPDATE dokončeno
- OUTPUT vrací 3 (nesprávná hodnota)
To znamená:
- Nezískáme časové razítko tak, jak existuje na konci příkazu UPDATE (4 )
- Místo toho dostaneme časové razítko tak, jak bylo v neurčitém středu příkazu UPDATE (3 )
- Nedostali jsme správné časové razítko
Totéž platí pro jakékoli spouštěč, který upravuje jakýkoli hodnota v řádku. OUTPUT
nebude VYSTUPOVAT hodnotu ke konci AKTUALIZACE.
To znamená, že nemůžete důvěřovat OUTPUT, že vrátí nějaké správné hodnoty.
Tato bolestivá realita je zdokumentována v BOL:
Sloupce vrácené z OUTPUT odrážejí data tak, jak jsou po dokončení příkazů INSERT, UPDATE nebo DELETE, ale před provedením spouštěčů.
Jak to Entity Framework vyřešil?
.NET Entity Framework používá rowversion pro Optimistic Concurrency. EF závisí na znalosti hodnoty timestamp
jak existuje poté, co vydají UPDATE.
Protože nemůžete použít OUTPUT
pro všechna důležitá data používá Entity Framework společnosti Microsoft stejné řešení jako já:
Řešení č. 3 – Konečné – Nepoužívejte klauzuli OUTPUT
Chcete-li načíst po hodnoty, problémy 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
Nepoužívejte OUTPUT
.
Ano, trpí sporem, ale to je to nejlepší, co může SQL Server udělat.
A co INSERTs
Dělejte to, co dělá 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
Opět používají SELECT
k přečtení řádku, místo aby klauzuli OUTPUT důvěřoval.