Aviso de visibilidade :Não a outra resposta. Vai dar valores incorretos. Leia por que está errado.
Dado o kludge necessário para fazer
UPDATE
com OUTPUT
trabalho no SQL Server 2008 R2, alterei minha consulta de:UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
para:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
Basicamente eu parei de usar
OUTPUT
. Isso não é tão ruim quanto o Entity Framework em si usa esse mesmo hack! Espero que
Atualização:usar OUTPUT é prejudicial
O problema com o qual começamos foi tentar usar o
OUTPUT
cláusula para recuperar o "depois" valores em uma tabela:UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
Isso atinge a limitação bem conhecida ("não vai corrigir" bug) no SQL Server:
A tabela de destino 'BatchReports' da instrução DML não pode ter nenhum acionador habilitado se a instrução contiver uma cláusula OUTPUT sem cláusula INTO
Tentativa de solução nº 1
Então, tentamos algo em que usaremos uma
TABLE
intermediária variável para manter o OUTPUT
resultados: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
Exceto que falha porque você não tem permissão para inserir um
timestamp
na tabela (mesmo uma variável de tabela temporária). Tentativa de solução nº 2
Sabemos secretamente que um
timestamp
é na verdade um inteiro sem sinal de 64 bits (também conhecido como 8 bytes). Podemos alterar nossa definição de tabela temporária para usar binary(8)
em vez de 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
E isso funciona, exceto que o valor está errado .
O carimbo de data/hora
RowVersion
que retornamos não é o valor do timestamp como existia após a conclusão do UPDATE:- carimbo de data e hora retornado :
0x0000000001B71692
- carimbo de data e hora real :
0x0000000001B71693
Isso porque os valores
OUTPUT
em nossa tabela não os valores como estavam no final da instrução UPDATE:- Instrução UPDATE iniciando
- modifica a linha
- o carimbo de data/hora foi atualizado (por exemplo, 2 → 3)
- OUTPUT recupera o novo carimbo de data/hora (ou seja, 3)
- execuções de gatilho
- modifica a linha novamente
- o carimbo de data/hora foi atualizado (por exemplo, 3 → 4)
- modifica a linha novamente
- modifica a linha
- Instrução UPDATE concluída
- SAÍDA retorna 3 (o valor errado)
Isso significa:
- Não recebemos o carimbo de data/hora, pois existe no final da instrução UPDATE (4 )
- Em vez disso, obtemos o carimbo de data/hora como estava no meio indeterminado da instrução UPDATE (3 )
- Não recebemos o carimbo de data/hora correto
O mesmo vale para qualquer gatilho que modifica qualquer valor na linha. A
OUTPUT
não irá OUTPUT o valor no final do UPDATE. Isso significa que você não pode confiar em OUTPUT para retornar valores corretos.
Esta dolorosa realidade está documentada no BOL:
As colunas retornadas de OUTPUT refletem os dados como estão após a conclusão da instrução INSERT, UPDATE ou DELETE, mas antes que os gatilhos sejam executados.
Como o Entity Framework resolveu isso?
O .NET Entity Framework usa rowversion para simultaneidade otimista. O EF depende de saber o valor do
timestamp
como existe depois que eles emitem um UPDATE. Como você não pode usar
OUTPUT
para quaisquer dados importantes, o Entity Framework da Microsoft usa a mesma solução alternativa que eu:Solução nº 3 - Final - Não use a cláusula OUTPUT
Para recuperar o depois valores, problemas do 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
Não use
OUTPUT
. Sim, sofre de uma condição de corrida, mas isso é o melhor que o SQL Server pode fazer.
E os INSERTOS
Faça o que o Entity Framework faz:
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
Novamente, eles usam um
SELECT
instrução para ler a linha, em vez de colocar qualquer confiança na cláusula OUTPUT.