实体框架核心上的多线程问题

时间:2019-04-16 06:40:36

标签: c# asp.net-core entity-framework-core

我正在使用Entity Framework作为与数据库进行通信并在ASP.NET CORE应用程序上获取/写入数据库信息的方式。 (用作非常基本的API,用作单独应用程序的服务器。)

有时候,客户会要求加入一个给定的大厅。我刚刚确认,如果同时输入4个请求,则所有请求都将在大厅注册,但是玩家人数没有更新,如果更新,则超过上限/上限。

我使用实体框架错误吗?是否有其他工具可用于此类操作,还是我应该使它使用单个线程(如果有人可以提醒我怎么做),或者使用lock block语句封装我的所有动作/端点?

无论我如何构造代码,都容易受到这些相同时间的http请求的影响,并在我的存储库/上下文中并行移动。

如果我可以创建某种队列,那将是很棒的,我相信这就是将所有内容封装在锁中的作用。

编辑:

正如vasily.sib回答的那样,我可以使用并发令牌解决此问题。请检查他的评论,以获得有关如何使用它们的惊人信息!

1 个答案:

答案 0 :(得分:0)

您的问题是这样的操作...

_context.Sessions.FirstOrDefault(s => s.Id == sessionId).PlayerCount += 1;
_context.SaveChanges();

...不是原子的。使用FirstOrDefault,您可以得到会话(您可以取消引用,然后不进行null检查,因此,First在这里会是更好的选择,因为您会收到更好的错误消息)。然后,在另一步骤中保存更改。在这些步骤之间,另一个并发线程可能已更改,并且已经为PlayerCount保存了新值。

有多种解决方法,其中大多数都需要在数据库级别进行一些更改。

解决该问题的一种方法是编写一个可以自动进行更新的存储过程。您将需要一个类似于以下内容的UPDATE语句:

UPDATE Sessions
SET PlayerCount = PlayerCount + 1
FROM Sessions
WHERE Id = @SessionId

如果您不想使用存储过程,则可以将此SQL直接发送到数据库。

此解决方案的一个问题是稍后您还要执行此步骤:

if (thisSession.Size == thisSession.PlayerCount)
{
    thisSession.Status = SessionStatus.Active;
    _context.SaveChanges(); // this can be trimmed and added at the previous scope
}

您必须将其集成到UPDATE语句中,以使操作保持原子性。如果要添加其他步骤,事情可能会变得复杂。

另一种方法是使用EF核心中内置的乐观并发。简而言之,这意味着当您保存数据时,ef将首先检查目标行与检索到它之前是否仍处于相同版本中。

为此,您的会话需要一个包含版本计数器的列。对于SQL Server,这将是类型rowversion的颜色。拥有此专栏后,EF可以发挥其乐观的并发魔力。 EF将抛出一个DbUpdateConcurrencyException,您将必须处理它。有多种方法可以解决该错误。一种简单的方法是重复整个操作,直到解决为止。