Max*_*sky 2 c# sql-server asp.net-mvc race-condition linq-to-sql
我正在构建一个使用LINQ to SQL的ASP.NET MVC 2站点.在我的网站访问数据库的其中一个地方,我认为竞争条件是可能的.
以下是相关数据库表的一些列,命名为Revisions:
Posts表的PK在我的网站上,用户可以提交帖子并稍后编辑帖子.原始海报以外的用户可以编辑帖子 - 因此可以同时在单个帖子上进行多次编辑.
提交帖子时,Posts会创建表格中的记录,以及Revisions表格中的记录,其中PostID设置为Posts记录的ID ,RevisionText设置为Post文本,EditNumber 设置为1.
当编辑一个帖子,只Revisions记录被创建,与EditNumber被设定为比最新的编辑数量1高.
因此,EditNumber列指的是Post编辑的次数.
我在实现这些功能时遇到的挑战是增加EditNumber列.由于该列不能是IDENTITY,我必须手动操作它的值.
这是我的LINQ查询,用于确定新版本应具有的EditNumber:
using(var db = new DBDataContext())
{
var rev = new Revision();
rev.EditNumber = db.Revisions.Where(r => r.PostID == postID).Max(r => r.EditNumber) + 1;
// ... (fill other properties)
db.Revisions.InsertOnSubmit(rev);
db.SubmitChanges();
}
Run Code Online (Sandbox Code Playgroud)
计算最大值并增加它可能会导致竞争条件.
有没有更好的方法来实现该功能?
直接在数据库中更新并返回新版本:
update Revisions
set EditNumber += 1
output INSERTED.EditNumber
where PostID = @postId;
Run Code Online (Sandbox Code Playgroud)
不幸的是,这在LINQ中是不可能的.事实上,在客户端根本不可能,无论使用何种技术,都不会做出悲观锁定,这有太多的缺点值得考虑.
更新:
以下是我将如何插入新版本(包括第一次修订):
create procedure usp_insertPostRevision
@postId int,
@text nvarchar(max),
@revisionId bigint output
as
begin
set nocount on;
declare @nextEditNumber (EditNumber int not null);
declare @rc int = 0;
begin transaction;
begin try
update Posts
set LastRevision += 1
output INSERTED.LastRevision
into @nextEditNumber (EditNumber)
where PostId = @postId;
set @rc = @@rowcount;
if (@rc <> 1)
raiserror (N'Expected exactly one post with Id:%i. Found:%i',
16, 1 , @postId, @rc);
insert into Revisions
(PostId, Text, EditNumber)
select @postID, @text, EditNumber
from @nextEditNumber;
set @revisionId = scope_identity();
commit;
end try
begin catch
... // Error handling omitted
end catch
end
Run Code Online (Sandbox Code Playgroud)
我省略了错误处理,请参阅模板过程的异常处理和嵌套事务,而不是正确处理错误和嵌套事务.
您会注意到Posts表有一个LastRevision字段,用作后修订的增量.这比每次添加修订时计算MAX要好得多,因为它避免了(范围)扫描修订.它还充当并发保护:一次只能有一个事务更新它,并且只有该事务才会继续插入新的修订.并发事务将阻塞并等待第一个提交,然后下一个未阻塞的事务将正确地将修订号更新为+1.
| 归档时间: |
|
| 查看次数: |
1083 次 |
| 最近记录: |