可視性に関する警告:他の回答をしないでください。誤った値が返されます。なぜそれが間違っているのかを読んでください。
SQL Server 2008 R2 での作業に必要な手間を考慮して、クエリを次のように変更しましUPDATE
た。OUTPUT
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のよく知られた制限 ( 「修正しない」バグ) にぶつかります。
DML ステートメントのターゲット テーブル 'BatchReports' は、ステートメントに INTO 句のない OUTPUT 句が含まれている場合、有効なトリガーを持つことはできません
回避策の試み #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
ただし、テーブルに a を挿入することは許可されていないため(一時テーブル変数であっても)、失敗します。
回避策の試み #2
timestamp
aが実際には 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
は、UPDATE が完了した後に存在していたタイムスタンプの値ではありません。
- 返されたタイムスタンプ:
0x0000000001B71692
- 実際のタイムスタンプ:
0x0000000001B71693
これはOUTPUT
、テーブルへの値が UPDATE ステートメントの最後にあった値ではないためです。
- UPDATE ステートメントの開始
- 行を変更します
- OUTPUT は新しいタイムスタンプ(つまり 3)を取得します
- トリガーラン
- UPDATE ステートメントの完了
- OUTPUT は3を返します (間違った値)
これの意味は:
- UPDATE ステートメントの最後に存在するため、タイムスタンプを取得しません ( 4 )
- 代わりに、UPDATE ステートメントの不確定な途中にあったタイムスタンプを取得します ( 3 )
- 正しいタイムスタンプが得られない
行の任意の値を変更する任意のトリガーについても同じことが言えます。は、UPDATE の終了時点で値を出力しません。OUTPUT
これは、OUTPUT が正しい値を返すことを信頼できないことを意味します。
この痛ましい現実は、BOL に記載されています。
OUTPUT から返される列には、INSERT、UPDATE、または DELETE ステートメントが完了した後、トリガーが実行される前のデータがそのまま反映されます。
Entity Framework はどのように解決しましたか?
.NET Entity Framework は、オプティミスティック コンカレンシーに rowversion を使用します。timestamp
EF は、UPDATE を発行した後に存在するの値を知ることに依存します。
重要なデータには使用できないためOUTPUT
、Microsoft の Entity Framework は私と同じ回避策を使用します。
回避策 #3 - 最終 - OUTPUT 句を使用しない
after値を取得するために、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 句に信頼を置くのではなく、ステートメントを使用して行を読み取ります。