Предупреждение за видимост :Не отговаряйте на другия. Ще даде неправилни стойности. Прочетете защо е грешно.
Като се има предвид kludge, необходим за извършване на UPDATE
с OUTPUT
работи в SQL Server 2008 R2, промених заявката си от:
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
По принцип спрях да използвам OUTPUT
. Това не е толкова лошо, колкото самата Entity Framework използва същия този хак!
Надяваме се 2012 2014 2016 2018 2019 2020 г. ще има по-добро изпълнение.
Актуализация:използването на OUTPUT е вредно
Проблемът, с който започнахме, беше да се опитаме да използваме OUTPUT
клауза за извличане на "след" стойности в таблица:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
Това след това достига ограничението за добре познаване („няма да се поправи“ грешка) в SQL Server:
Целевата таблица „BatchReports“ на DML израза не може да има никакви активирани задействания, ако изразът съдържа клауза OUTPUT без клауза INTO
Опит за заобикаляне №1
Така че опитваме нещо, където ще използваме междинна TABLE
променлива за задържане на OUTPUT
резултати:
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
Само дето това е неуспешно, защото не ви е позволено да вмъкнете timestamp
в таблицата (дори и временна променлива на таблица).
Опит за заобикаляне #2
Тайно знаем, че timestamp
всъщност е 64-битово (известно още като 8 байта) цяло число без знак. Можем да променим нашата временна дефиниция на таблица, за да използваме binary(8)
вместо 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
И това работи,освен че стойностите са грешни .
Знакът за време RowVersion
ние връщаме не е стойността на времевата марка, както е съществувала след завършване на АКТУАЛИЗИРАНЕТО:
- Върнат времеви печат :
0x0000000001B71692
- действително времеви печат :
0x0000000001B71693
Това е така, защото стойностите OUTPUT
в нашата таблица сане стойностите, каквито бяха в края на оператора UPDATE:
- Започва оператор UPDATE
- променя ред
- марката за време е актуализирана (напр. 2 → 3)
- OUTPUT извлича ново времеви печат (т.е. 3)
- спусъкът се изпълнява
- променя отново ред
- клеймото за време е актуализирано (напр. 3 → 4)
- променя отново ред
- променя ред
- Изявлението UPDATE е завършено
- OUTPUT връща 3 (грешна стойност)
Това означава:
- Не получаваме клеймото за време, както съществува в края на оператора UPDATE (4 )
- Вместо това получаваме клеймото за време, както беше в неопределената среда на оператора UPDATE (3 )
- Не получаваме правилната времева марка
Същото важи и завсякото тригер, който променя всяко стойност в реда. OUTPUT
няма да ИЗВЕДЕ стойността към края на АКТУАЛИЗИРАНЕТО.
Това означава, че не можете да имате доверие на OUTPUT да върне правилни стойности.
Тази болезнена реалност е документирана в BOL:
Колоните, върнати от OUTPUT, отразяват данните, каквито са, след като операторът INSERT, UPDATE или DELETE е завършил, но преди да бъдат изпълнени тригерите.
Как го реши Entity Framework?
.NET Entity Framework използва версия на редове за оптимистичен паралелност. EF зависи от познаването на стойността на timestamp
както съществува, след като издадат АКТУАЛИЗИРАНЕ.
Тъй като не можете да използвате OUTPUT
за всякакви важни данни Entity Framework на Microsoft използва същото решение, което правя аз:
Заобиколно решение №3 – Окончателно – Не използвайте клауза OUTPUT
За да извлечете след стойности, проблеми с 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
Не използвайте OUTPUT
.
Да, той страда от състезание, но това е най-доброто, което SQL Server може да направи.
Какво ще кажете за INSERT
Правете това, което прави 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
Отново те използват SELECT
изявление за четене на реда, вместо да се доверява на клаузата OUTPUT.