視界警告 :他の答えはしないでください。間違った値になります。なぜそれが間違っているのかを読んでください。
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
の使用をやめました 。これは、EntityFramework自体ほど悪くはありません。 これとまったく同じハックを使用します!
うまくいけば2012 2014 2016 2018 2019 2020年にはより良い実装があります。
更新:OUTPUTの使用は有害です
私たちが始めた問題は、OUTPUT
を使おうとしたことでした。 "after"を取得する句 テーブル内の値:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
その後、それはよく知られている制限に達します(「修正されません」 バグ)SQL Serverの場合:
ステートメントにINTO句のないOUTPUT句が含まれている場合、DMLステートメントのターゲットテーブル'BatchReports'でトリガーを有効にすることはできません
回避策の試み#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
UPDATEが完了した後に存在していたタイムスタンプの値ではないことを返します:
- 返されたタイムスタンプ :
0x0000000001B71692
- 実際のタイムスタンプ :
0x0000000001B71693
これは、値がOUTPUT
であるためです。 私たちのテーブルにはない UPDATEステートメントの最後の値:
- UPDATEステートメントの開始
- 行を変更します
- タイムスタンプが更新されます(例:2→3)
- OUTPUTは新しいタイムスタンプを取得します(つまり3)
- トリガー実行
- 行を再度変更します
- タイムスタンプが更新されます(例:3→4)
- 行を再度変更します
- 行を変更します
- UPDATEステートメントが完了しました
- OUTPUTは3を返します (間違った値)
これは次のことを意味します:
- UPDATEステートメントの最後にあるタイムスタンプ( 4 )は取得されません。 )
- 代わりに、UPDATEステートメントの不確定な途中にあったタイムスタンプを取得します( 3 )
- 正しいタイムスタンプが取得されません
同じことがすべてにも当てはまります 任意のを変更するトリガー 行の値。 OUTPUT
UPDATEの終了時点で値を出力しません。
これは、OUTPUTが正しい値を返すことを信頼できないことを意味します。
この痛ましい現実はBOLに文書化されています:
OUTPUTから返される列は、INSERT、UPDATE、またはDELETEステートメントが完了した後、トリガーが実行される前のデータをそのまま反映します。
Entity Frameworkはどのように解決しましたか?
.NET Entity Frameworkは、楽観的同時実行性のために行バージョンを使用します。 EFは、timestamp
の値を知ることに依存します UPDATEを発行した後に存在するため。
OUTPUT
は使用できないため 重要なデータについては、MicrosoftのEntityFrameworkは私が行うのと同じ回避策を使用します。
回避策#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
は使用しないでください 。
はい、競合状態に悩まされていますが、それがSQLServerで可能な最善の方法です。
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句を信頼するのではなく、行を読み取るステートメント。