Peringatan Visibilitas :Jangan jawaban yang lain. Ini akan memberikan nilai yang salah. Baca terus mengapa itu salah.
Mengingat kludge yang dibutuhkan untuk membuat UPDATE
dengan OUTPUT
bekerja di SQL Server 2008 R2, saya mengubah kueri saya dari:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
ke:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
Pada dasarnya saya berhenti menggunakan OUTPUT
. Ini tidak seburuk Entity Framework itu sendiri menggunakan peretasan yang sama!
Semoga 2012 2014 2016 2018 2019 Tahun 2020 akan memiliki implementasi yang lebih baik.
Pembaruan:menggunakan OUTPUT berbahaya
Masalah yang kami mulai dengan mencoba menggunakan OUTPUT
klausa untuk mengambil "setelah" nilai dalam tabel:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
Itu kemudian mencapai batasan yang sudah diketahui ("tidak akan diperbaiki" bug) di SQL Server:
Tabel target 'BatchReports' dari pernyataan DML tidak dapat mengaktifkan pemicu apa pun jika pernyataan tersebut berisi klausa OUTPUT tanpa klausa INTO
Upaya Solusi #1
Jadi kami mencoba sesuatu di mana kami akan menggunakan TABLE
perantara variabel untuk menampung OUTPUT
hasil:
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
Kecuali itu gagal karena Anda tidak diizinkan memasukkan timestamp
ke dalam tabel (bahkan variabel tabel sementara).
Upaya Solusi #2
Kami diam-diam mengetahui bahwa timestamp
sebenarnya adalah bilangan bulat tidak bertanda 64-bit (alias 8 byte). Kita dapat mengubah definisi tabel sementara untuk menggunakan binary(8)
daripada 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
Dan itu berhasil, kecuali nilainya salah .
Stempel waktu RowVersion
yang kami kembalikan bukanlah nilai stempel waktu seperti yang ada setelah UPDATE selesai:
- stempel waktu yang dikembalikan :
0x0000000001B71692
- stempel waktu sebenarnya :
0x0000000001B71693
Itu karena nilai OUTPUT
ke dalam tabel kami tidak nilai-nilai seperti yang ada di akhir pernyataan UPDATE:
- PERBARUI pernyataan dimulai
- memodifikasi baris
- stempel waktu diperbarui (mis. 2 → 3)
- OUTPUT mengambil stempel waktu baru (yaitu 3)
- pemicu berjalan
- memodifikasi baris lagi
- stempel waktu diperbarui (mis. 3 → 4)
- memodifikasi baris lagi
- memodifikasi baris
- PERBARUI pernyataan selesai
- OUTPUT mengembalikan 3 (nilai yang salah)
Artinya:
- Kami tidak mendapatkan stempel waktu seperti yang ada di akhir pernyataan UPDATE (4 )
- Sebagai gantinya, kami mendapatkan stempel waktu seperti yang ada di tengah tak tentu dari pernyataan UPDATE (3 )
- Kami tidak mendapatkan stempel waktu yang benar
Hal yang sama berlaku untuk apa pun pemicu yang mengubah apa saja nilai dalam baris. OUTPUT
tidak akan OUTPUT nilai pada akhir UPDATE.
Ini berarti Anda tidak dapat mempercayai OUTPUT untuk mengembalikan nilai yang benar.
Realitas menyakitkan ini didokumentasikan dalam BOL:
Kolom yang dikembalikan dari OUTPUT mencerminkan data apa adanya setelah pernyataan INSERT, UPDATE, atau DELETE selesai tetapi sebelum pemicu dieksekusi.
Bagaimana Entity Framework menyelesaikannya?
.NET Entity Framework menggunakan versi baris untuk Konkurensi Optimis. EF bergantung pada mengetahui nilai timestamp
sebagaimana adanya setelah mereka mengeluarkan UPDATE.
Karena Anda tidak dapat menggunakan OUTPUT
untuk data penting apa pun, Kerangka Entitas Microsoft menggunakan solusi yang sama seperti yang saya lakukan:
Solusi #3 - Final - Jangan gunakan klausa OUTPUT
Untuk mengambil setelah nilai, masalah Kerangka Entitas:
UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))
SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1
Jangan gunakan OUTPUT
.
Ya, ia mengalami kondisi balapan, tetapi itulah yang terbaik yang dapat dilakukan SQL Server.
Bagaimana dengan INSERT
Lakukan apa yang Entity Framework lakukan:
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
Sekali lagi, mereka menggunakan SELECT
pernyataan untuk membaca baris, daripada menaruh kepercayaan pada klausa OUTPUT.