假设我要使用ASP.NET和SQL Server 2005启动一个项目。我必须设计此应用程序的并发要求。我打算在每个表中添加一个TimeStamp列。更新表时,我将检查“时间戳”列是否与所选列相同。
这种方法足够了吗?还是在任何情况下这种方法都有缺点?
请指教。
谢谢
里约
我认为,首先,您在问题中描述的方式是将 MS SQL作为数据库的ASP.NET应用程序的最佳方式。数据库中没有锁定。与永久断开连接的客户端(如Web客户端)完美匹配。
如何从一些答案中读取内容,在术语上存在误解。我们都表示使用Microsoft SQL Server 2008或更高版本来保存数据库。如果您在MS SQL Server 2008文档中打开主题“行版本(Transact-SQL)”,则会发现以下内容:
“ 时间戳记是rowversion数据类型的同义词, 并受数据类型同义词的影响。” …“ 不建议使用时间戳语法。MicrosoftSQL Server的将来版本将删除此功能。请避免在新的开发工作中使用此功能,并计划修改当前使用此功能的应用程序。”
因此,时间戳数据类型的同义词rowversion为MS SQL数据类型。它拥有64位计数器,该计数器存在于每个数据库内部,可以看作@@ DBTS。修改数据库的一个表中的一行后,计数器将增加。
在阅读您的问题时,我将“ TimeStamp”读为类型rowversion数据的列名。我个人更喜欢名称RowUpdateTimeStamp。在AzManDB中(请参阅带有数据库存储的Microsoft Authorization Manager),我可以看到这样的名称。有时还使用ChildUpdateTimeStamp来跟踪分层的RowUpdateTimeStamp结构(与触发器有关)。
我在上一个项目中实现了这种方法,并且感到非常高兴。通常,您执行以下操作:
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN s.RowUpdateTimeStamp
ELSE m.RowUpdateTimeStamp
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
Run Code Online (Sandbox Code Playgroud)
或者进行如下的数据转换
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN CAST(s.RowUpdateTimeStamp AS bigint)
ELSE CAST(m.RowUpdateTimeStamp AS bigint)
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
Run Code Online (Sandbox Code Playgroud)
保持RowUpdateTimeStamp作为BIGINT,其对应ULONG的C#的数据类型。如果您从多个表中进行OUTER JOINT或JOINT,则MAX(RowUpdateTimeStamp)所有表的构造都将变得更加复杂。由于MS SQL不支持MAX(a,b,c,d,e)之类的功能,因此相应的构造可能如下所示:
(SELECT MAX(rv)
FROM (SELECT table1.RowUpdateTimeStamp AS rv
UNION ALL SELECT table2.RowUpdateTimeStamp
UNION ALL SELECT table3.RowUpdateTimeStamp
UNION ALL SELECT table4.RowUpdateTimeStamp
UNION ALL SELECT table5.RowUpdateTimeStamp) AS maxrv) AS RowUpdateTimeStamp
Run Code Online (Sandbox Code Playgroud)
spSoftwareUpdate存储过程可能看起来像CREATE PROCEDURE dbo.spSoftwareUpdate
@Id int,
@SoftwareName varchar(100),
@originalRowUpdateTimeStamp bigint, -- used for optimistic concurrency mechanism
@NewRowUpdateTimeStamp bigint OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- ExecuteNonQuery() returns -1, but it is not an error
-- one should test @NewRowUpdateTimeStamp for DBNull
SET NOCOUNT ON;
UPDATE dbo.Software
SET Name = @SoftwareName
WHERE Id = @Id AND RowUpdateTimeStamp <= @originalRowUpdateTimeStamp
SET @NewRowUpdateTimeStamp = (SELECT RowUpdateTimeStamp
FROM dbo.Software
WHERE (@@ROWCOUNT > 0) AND (Id = @Id));
END
Run Code Online (Sandbox Code Playgroud)
dbo.spSoftwareDelete存储过程的代码看起来一样。如果不打开NOCOUNT,则可以生成在许多情况下自动生成的DBConcurrencyException。Visual Studio使您可以使用诸如TableAdapter或的高级选项中的“使用乐观并发”复选框之类的乐观并发DataAdapter。
如果您仔细看一下dbo.spSoftwareUpdate存储过程,就会发现,我RowUpdateTimeStamp <= @originalRowUpdateTimeStamp在WHERE中使用它代替RowUpdateTimeStamp = @originalRowUpdateTimeStamp。我这样做是因为,其值@originalRowUpdateTimeStamp通常具有一个MAX(RowUpdateTimeStamp)从一个表到另一个表的客户端。所以可能是这样RowUpdateTimeStamp < @originalRowUpdateTimeStamp。您应该使用严格等于=并在此处重现与SELECT语句中使用的相同的复杂JOIN语句,或者使用像我这样的<=构造并保持与以前完全相同的安全性。
顺便说一句,可以基于RowUpdateTimeStamp 为ETag构造非常好的价值,它可以与数据一起以HTTP标头形式发送到客户端。使用ETag,您可以在客户端实现智能数据缓存。
我无法在此处编写完整的代码,但是您可以在Internet上找到很多示例。我只想重复一次,在基于我看来使用乐观并发rowversion是在大多数的情况下ASP.NET的最好方式。