LINQ to SQL - 更新以增加非主键字段 - 线程安全

时间:2009-01-31 16:40:33

标签: .net linq linq-to-sql concurrency locking

我有两张桌子(好吧,两张与此问题相关):

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

当我在Bets_Messages中插入一个新的BetMessage时,我想更新(准确地说是增量)Bets中的相应字段。

在纯T-SQL中:

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

上面的代码可以很好用,它是线程安全的;如果两个线程会对它进行DB调用(并且对于相同的Bet ofcourse),则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

为UPDATE生成的T-SQL的问题在于,尽管线程安全似乎没问题,但它可能会在第二个线程中执行错误,而不是等待它完成。会吗?

这就是为什么我这么想。

第一个线程执行此操作:

插入相应的betMessage, 更新下注行以将MessagePosted从0增加到1

第二个线程将执行此操作:

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

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

是否有一些不错的方法使用LINQ to SQL和 NOT 存储过程,即席查询等等?

感谢您耐心阅读这篇长篇文章..

1 个答案:

答案 0 :(得分: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处的交易的更多信息。