SQL Server 2005 バックエンドを使用して、C# 4.0 で記述された Windows デスクトップ アプリケーションに取り組んでいます。アプリケーションは Timestamp データ型フィールドを使用して、データの同時実行性を処理します。監査を処理するためにデータテーブルにいくつかのトリガーを配置するまで、すべてが正常に機能していました。テスト スクリプトを実行すると、誤ったデータ同時実行エラーが発生するようになりました。同時実行を管理するために使用している Timestamp フィールドをトリガーが更新しているかのようです。
そうですか?もしそうなら、それについて私にできることはありますか?
さらに詳しい情報が必要な場合は、同時実行チェックのしくみについて簡単に説明します。レコードがロードされると、Timestamp データ型の値が読み取られ、他のすべてのデータとともにクラスに格納されます。
ユーザーがデータを保存しようとすると、クラスはトランザクションを開始し、データベースからレコードを読み取り、Timestamp フィールドを比較します。
それらが一致する場合、同じトランザクションで保存が行われ、"UPDATE ... ; SELECT @@DBTS" のような T-SQL ステートメントを使用して新しいタイムスタンプが取得されます。
タイムスタンプが一致しない場合、データ同時実行例外がスローされます。
監査トリガーを追加する前は計画どおりに機能していましたが、レコードが更新されてから再度更新されると、常にデータ同時実行例外がスローされます。私の推測では、更新後に新しいタイムスタンプ値を取得していますが、トリガーにより、その後再び変更されます。
更新を実行するコードは次のとおりです。
// Begin Transaction
SqlConnection conn = new SqlConnection(DataGateway.ConnStr);
SqlTransaction trans;
conn.Open();
trans = conn.BeginTransaction();
// Read current record
DataTable dt = base.Select(conn, trans);
// Timestamps match?
DataRow row = dt.Rows[0];
if (RowversionsEqual(Rowversion, (byte[])row["Rowversion"])) // Rowversion is a class property that holds the Timestamp obtained when data is initially read, Rowversions equal is a function that compares two Timestamp values
{
// Timestamps match, update record
SqlCommand cmd = new SqlCommand("UPDATE WrdImp SET Imp = @Imp, Note = @Note, EditDate = @EditTimestamp, EditBy = @EditBy WHERE BID = @BID AND WID = @WID; SELECT @@DBTS", conn, trans);
// Code to insert parameter values
Rowversion = (byte[])cmd.ExecuteScalar();
trans.Commit();
}
else
{
// Another user has made an interim change, notify user
trans.Rollback();
conn.Close();
throw new ImpDataConcurrencyException(dt.Rows[0]["EditBy"].ToString(), (DateTime)dt.Rows[0]["EditDate"],MsgComponent.Title, dt.Rows[0]["Imp"].ToString(), dt.Rows[0]["Note"].ToString());
}
更新トリガーの 1 つを次に示します。これは、APEX SQL Audit というサードパーティ製品によって自動生成されました。
ALTER TRIGGER [dbo].[tr_u_AUDIT_WrdImp]
ON [dbo].[WrdImp]
FOR UPDATE
NOT FOR REPLICATION
As
BEGIN
DECLARE
@IDENTITY_SAVE varchar(50),
@AUDIT_LOG_TRANSACTION_ID Int,
@PRIM_KEY nvarchar(4000),
@Inserted bit,
--@TABLE_NAME nvarchar(4000),
@ROWS_COUNT int
SET NOCOUNT ON
--Set @TABLE_NAME = '[dbo].[WrdImp]'
Select @ROWS_COUNT=count(*) from inserted
SET @IDENTITY_SAVE = CAST(IsNull(@@IDENTITY,1) AS varchar(50))
INSERT
INTO [PLIMS].dbo.AUDIT_LOG_TRANSACTIONS
(
TABLE_NAME,
TABLE_SCHEMA,
AUDIT_ACTION_ID,
HOST_NAME,
APP_NAME,
MODIFIED_BY,
MODIFIED_DATE,
AFFECTED_ROWS,
[DATABASE]
)
values(
'WrdImp',
'dbo',
1, -- ACTION ID For UPDATE
CASE
WHEN LEN(HOST_NAME()) < 1 THEN ' '
ELSE HOST_NAME()
END,
CASE
WHEN LEN(APP_NAME()) < 1 THEN ' '
ELSE APP_NAME()
END,
SUSER_SNAME(),
GETDATE(),
@ROWS_COUNT,
'PLIMS'
)
Set @AUDIT_LOG_TRANSACTION_ID = SCOPE_IDENTITY()
SET @Inserted = 0
If UPDATE([Imp])
BEGIN
INSERT
INTO [PLIMS].dbo.AUDIT_LOG_DATA
(
AUDIT_LOG_TRANSACTION_ID,
PRIMARY_KEY_DATA,
COL_NAME,
OLD_VALUE_LONG,
NEW_VALUE_LONG,
DATA_TYPE
, KEY1, KEY2
)
SELECT
@AUDIT_LOG_TRANSACTION_ID,
convert(nvarchar(1500), IsNull('[WID]='+CONVERT(nvarchar(4000), IsNull(OLD.[WID], NEW.[WID]), 0), '[WID] Is Null')+' AND '+IsNull('[BID]='+CONVERT(nvarchar(4000), IsNull(OLD.[BID], NEW.[BID]), 0), '[BID] Is Null')),
'Imp',
CONVERT(nvarchar(4000), OLD.[Imp], 0),
CONVERT(nvarchar(4000), NEW.[Imp], 0),
'A'
, IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[WID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[WID], 0))), IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[BID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[BID], 0)))
FROM deleted OLD Inner Join inserted NEW On
(CONVERT(nvarchar(4000), NEW.[WID], 0)=CONVERT(nvarchar(4000), OLD.[WID], 0) or (NEW.[WID] Is Null and OLD.[WID] Is Null)) AND (CONVERT(nvarchar(4000), NEW.[BID], 0)=CONVERT(nvarchar(4000), OLD.[BID], 0) or (NEW.[BID] Is Null and OLD.[BID] Is Null))
where (
(
NEW.[Imp] <>
OLD.[Imp]
) Or
(
NEW.[Imp] Is Null And
OLD.[Imp] Is Not Null
) Or
(
NEW.[Imp] Is Not Null And
OLD.[Imp] Is Null
)
)
SET @Inserted = CASE WHEN @@ROWCOUNT > 0 Then 1 Else @Inserted End
END
If UPDATE([Note])
BEGIN
INSERT
INTO [PLIMS].dbo.AUDIT_LOG_DATA
(
AUDIT_LOG_TRANSACTION_ID,
PRIMARY_KEY_DATA,
COL_NAME,
OLD_VALUE_LONG,
NEW_VALUE_LONG,
DATA_TYPE
, KEY1, KEY2
)
SELECT
@AUDIT_LOG_TRANSACTION_ID,
convert(nvarchar(1500), IsNull('[WID]='+CONVERT(nvarchar(4000), IsNull(OLD.[WID], NEW.[WID]), 0), '[WID] Is Null')+' AND '+IsNull('[BID]='+CONVERT(nvarchar(4000), IsNull(OLD.[BID], NEW.[BID]), 0), '[BID] Is Null')),
'Note',
CONVERT(nvarchar(4000), OLD.[Note], 0),
CONVERT(nvarchar(4000), NEW.[Note], 0),
'A'
, IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[WID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[WID], 0))), IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[BID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[BID], 0)))
FROM deleted OLD Inner Join inserted NEW On
(CONVERT(nvarchar(4000), NEW.[WID], 0)=CONVERT(nvarchar(4000), OLD.[WID], 0) or (NEW.[WID] Is Null and OLD.[WID] Is Null)) AND (CONVERT(nvarchar(4000), NEW.[BID], 0)=CONVERT(nvarchar(4000), OLD.[BID], 0) or (NEW.[BID] Is Null and OLD.[BID] Is Null))
where (
(
NEW.[Note] <>
OLD.[Note]
) Or
(
NEW.[Note] Is Null And
OLD.[Note] Is Not Null
) Or
(
NEW.[Note] Is Not Null And
OLD.[Note] Is Null
)
)
SET @Inserted = CASE WHEN @@ROWCOUNT > 0 Then 1 Else @Inserted End
END
If UPDATE([EditDate])
BEGIN
INSERT
INTO [PLIMS].dbo.AUDIT_LOG_DATA
(
AUDIT_LOG_TRANSACTION_ID,
PRIMARY_KEY_DATA,
COL_NAME,
OLD_VALUE_LONG,
NEW_VALUE_LONG,
DATA_TYPE
, KEY1, KEY2
)
SELECT
@AUDIT_LOG_TRANSACTION_ID,
convert(nvarchar(1500), IsNull('[WID]='+CONVERT(nvarchar(4000), IsNull(OLD.[WID], NEW.[WID]), 0), '[WID] Is Null')+' AND '+IsNull('[BID]='+CONVERT(nvarchar(4000), IsNull(OLD.[BID], NEW.[BID]), 0), '[BID] Is Null')),
'EditDate',
CONVERT(nvarchar(4000), OLD.[EditDate], 121),
CONVERT(nvarchar(4000), NEW.[EditDate], 121),
'A'
, IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[WID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[WID], 0))), IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[BID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[BID], 0)))
FROM deleted OLD Inner Join inserted NEW On
(CONVERT(nvarchar(4000), NEW.[WID], 0)=CONVERT(nvarchar(4000), OLD.[WID], 0) or (NEW.[WID] Is Null and OLD.[WID] Is Null)) AND (CONVERT(nvarchar(4000), NEW.[BID], 0)=CONVERT(nvarchar(4000), OLD.[BID], 0) or (NEW.[BID] Is Null and OLD.[BID] Is Null))
where (
(
NEW.[EditDate] <>
OLD.[EditDate]
) Or
(
NEW.[EditDate] Is Null And
OLD.[EditDate] Is Not Null
) Or
(
NEW.[EditDate] Is Not Null And
OLD.[EditDate] Is Null
)
)
SET @Inserted = CASE WHEN @@ROWCOUNT > 0 Then 1 Else @Inserted End
END
If UPDATE([EditBy])
BEGIN
INSERT
INTO [PLIMS].dbo.AUDIT_LOG_DATA
(
AUDIT_LOG_TRANSACTION_ID,
PRIMARY_KEY_DATA,
COL_NAME,
OLD_VALUE_LONG,
NEW_VALUE_LONG,
DATA_TYPE
, KEY1, KEY2
)
SELECT
@AUDIT_LOG_TRANSACTION_ID,
convert(nvarchar(1500), IsNull('[WID]='+CONVERT(nvarchar(4000), IsNull(OLD.[WID], NEW.[WID]), 0), '[WID] Is Null')+' AND '+IsNull('[BID]='+CONVERT(nvarchar(4000), IsNull(OLD.[BID], NEW.[BID]), 0), '[BID] Is Null')),
'EditBy',
CONVERT(nvarchar(4000), OLD.[EditBy], 0),
CONVERT(nvarchar(4000), NEW.[EditBy], 0),
'A'
, IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[WID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[WID], 0))), IsNULL( CONVERT(nvarchar(500), CONVERT(nvarchar(4000), OLD.[BID], 0)), CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[BID], 0)))
FROM deleted OLD Inner Join inserted NEW On
(CONVERT(nvarchar(4000), NEW.[WID], 0)=CONVERT(nvarchar(4000), OLD.[WID], 0) or (NEW.[WID] Is Null and OLD.[WID] Is Null)) AND (CONVERT(nvarchar(4000), NEW.[BID], 0)=CONVERT(nvarchar(4000), OLD.[BID], 0) or (NEW.[BID] Is Null and OLD.[BID] Is Null))
where (
(
NEW.[EditBy] <>
OLD.[EditBy]
) Or
(
NEW.[EditBy] Is Null And
OLD.[EditBy] Is Not Null
) Or
(
NEW.[EditBy] Is Not Null And
OLD.[EditBy] Is Null
)
)
SET @Inserted = CASE WHEN @@ROWCOUNT > 0 Then 1 Else @Inserted End
END
-- Watch
-- Lookup
IF @Inserted = 0
BEGIN
DELETE FROM [PLIMS].dbo.AUDIT_LOG_TRANSACTIONS WHERE AUDIT_LOG_TRANSACTION_ID = @AUDIT_LOG_TRANSACTION_ID
END
-- Restore @@IDENTITY Value
DECLARE @maxprec AS varchar(2)
SET @maxprec=CAST(@@MAX_PRECISION as varchar(2))
EXEC('SELECT IDENTITY(decimal('+@maxprec+',0),'+@IDENTITY_SAVE+',1) id INTO #tmp')
End
GO
EXEC sp_settriggerorder @triggername=N'[dbo].[tr_u_AUDIT_WrdImp]', @order=N'Last', @stmttype=N'UPDATE'