2009-01-31 76 views
3

我有两个表(当然,两个相关的这个问题):LINQ到SQL - 更新以增加非主键字段 - 线程安全

投注(持有投注;专栏:身份证,当我插入Bets_Messages我想在投注更新(增量确切地说)相应领域的新BetMessage标识,BetId)

:列; MessagesPosted) Bets_Messages(持有投注论坛的消息。

在纯T-SQL,这将是:

INSERT INTO Bets_Messages (BetId, <bla bla>) VALUES (23, "asfasdfasdf"); 
UPDATE Bets SET MessagesPosted = MessagesPosted + 1 WHERE Id = 23; 

上面的代码将工作奇妙,它是线程安全的;如果两个线程会对它进行数据库调用(并且对于同一个BET),MessagesPosted列将会很好地增加,因为第一个UPDATE至少会在其上放置一个ROWLOCK,实际上会对UPDATE进行序列化。

但是使用LINQ to SQL这涉及到一个比较困难的方法:

public void PostMessage(MyProject.Entities.BetMessage betMessageEntity) 
    { 
     DatabaseDataContext ctx = GetFreshContext(); // GetFreshContext is a private method that practically initializes a new DataContext 
     Bets_Message msg = new Bets_Message(betMessageEntity); 
     ctx.Bets_Messages.InsertOnSubmit(msg); 
     Bet bet = (from b in ctx.Bets where b.Id == (long)betMessageEntity.BetId select b).Single(); 
     bet.MessagesPosted++; 
     ctx.SubmitChanges(); 
    } 

看起来不错,是吧?那么这里就是它会产生:

exec sp_executesql N'INSERT INTO [dbo].[Bets_Messages]([ParentMessageId], [BetsId], [UserId], [Subject], [DisplayXml], [Time], [ReplyDepth], [Text]) 
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7) 

SELECT CONVERT(BigInt,SCOPE_IDENTITY()) AS [value]',N'@p0 bigint,@p1 bigint,@p2 uniqueidentifier,@p3 nvarchar(6),@p4 nvarchar(114),@p5 datetime2(7),@p6 tinyint,@p7 nvarchar(8)',@p0=NULL,@p1=1,@p2='A0D253AF-6261-49AE-8C11-BA6117EF35C7',@p3=N'aaawww',@p4=N'<m ai="a0d253af-6261-49ae-8c11-ba6117ef35c7" a="AndreiR" s="aaawww" t="2009-01-31T18:04:31.282+02:00">wwwwwaaa</m>',@p5='2009-01-31 18:04:31.2820000',@p6=0,@p7=N'wwwwwaaa' 

(用于BetMessage插入),并为UPDATE:

exec sp_executesql N'UPDATE [dbo].[Bets] 
SET [MessagesPosted] = @p17 
WHERE ([Id] = @p0) AND ([UserId] = @p1) AND ([Bets_CategoriesId] = @p2) AND ([Bets_TypesId] = @p3) AND ([TotalSum] = @p4) AND ([TotalBetters] = @p5) AND ([CreateDate] = @p6) AND ([DeadlineDate] = @p7) AND ([ClosedDate] IS NULL) AND ([Bets_StatusesId] = @p8) AND ([LastBetAdded] IS NULL) AND ([Title] = @p9) AND ([ShortDescription] = @p10) AND ([Description] = @p11) AND ([DescriptionPlainText] = @p12) AND ([Version] = @p13) AND ([ReviewedBy] = @p14) AND ([UrlFragment] = @p15) AND ([MessagesPosted] = @p16) AND ([ClosedBy] IS NULL) AND ([OutcomeNumber] IS NULL)',N'@p0 bigint,@p1 uniqueidentifier,@p2 smallint,@p3 tinyint,@p4 money,@p5 int,@p6 datetime2(7),@p7 datetime2(7),@p8 tinyint,@p9 nvarchar(7),@p10 nvarchar(30),@p11 nvarchar(33),@p12 nvarchar(22),@p13 smallint,@p14 uniqueidentifier,@p15 varchar(7),@p16 int,@p17 int',@p0=1,@p1='A0D253AF-6261-49AE-8C11-BA6117EF35C7',@p2=2,@p3=1,@p4=$0.0000,@p5=0,@p6='2008-12-03 00:00:00',@p7='2008-12-31 00:00:00',@p8=2,@p9=N'Pariu 1',@p10=N'Descriere pariu 1 - text chior',@p11=N'Descriere pe larg 1 - html permis',@p12=N'descriere text chior 1',@p13=1,@p14='A0D253AF-6261-49AE-8C11-BA6117EF35C7',@p15='pariu-1',@p16=18,@p17=19 

与该更新产生的T-SQL的问题是,虽然似乎确定为线程-safety它可能会在执行行更新的第二个线程中引发错误,而不是等待它完成。会吗?

这就是我为什么这么想的原因。

一号线做这个的:

插入相应betMessage, 更新赌注行从0递增MessagePosted 1

第二线程将做到这一点:

插入其相应betMessage , 更新投注行以将MessagePosted从0增加到1(当它读取它时为0)。但是现在它是1,并且WHERE子句将使它不更新,因为wHERE子句将评估为false。受影响的行将被发送到LINQ客户端,并反过来会引发异常。

因此,我将不得不编写我的f *#$ ing重试尝试代码,而不是依赖SQL Server中的ROW LOCKs。

是否有一些体面的方法,使用LINQ to SQL和不是存储过程,特设查询等?

感谢您的阅读这篇长岗耐心..

回答

1

编辑:其实重读您的帖子后,我认为,产生的SubmitChanges自动交易将采取已经做的护理确保所有的陈述完成或没有。我一直在想,既然你使用的是自动生成的ID,那么你会在LINQ中做两个阶段的更新,但似乎SubmitChanges为你处理它。

我会留下以下代码作为参考,但我不认为这是必需的。底部的链接解释了完成交易的不同方式。

你需要的是一个交易范围来包装整个组的插入/更新内找到。

using System.Transactions; 

public void PostMessage(MyProject.Entities.BetMessage betMessageEntity) 
{ 
    using (TransactionScope scope = new TransactionScope()) { 
     DatabaseDataContext ctx = GetFreshContext(); 
     Bets_Message msg = new Bets_Message(betMessageEntity); 
     ctx.Bets_Messages.InsertOnSubmit(msg); 
     ctx.SubmitChanges(); // this is what I thought you did 
     Bet bet = (from b in ctx.Bets 
        where b.Id == (long)betMessageEntity.BetId select b) 
       .Single(); 
     bet.MessagesPosted++; 
     ctx.SubmitChanges(); 
     scope.Complete(); 
    } 
} 

我不认为这会导致自动升级为分布式事务,因为所有的命令重用数据上下文中的连接相同。如果事实证明它确实如此,您可以在数据上下文的连接上创建一个事务并将其分配给数据上下文的Transaction属性。但是,如果你这样做,你需要自己管理和处理它。

有关LINQ2SQL和交易的更多信息MSDN

+0

事实上,TransactionScope可能比默认事务更好,因为TransactionScope默认为使用键范围锁等的“可序列化”隔离级别。 – 2009-01-31 18:21:12