我正在使用带有 SQL Server 2005 后端的 C# 4.0 编写的 Windows 桌面应用程序。应用程序使用 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());
}
这是更新触发器之一。它是由名为 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'