我正在使用Entity Framework作为与数据库进行通信并在ASP.NET CORE应用程序上获取/写入数据库信息的方式。 (用作非常基本的API,用作单独应用程序的服务器。)
有时候,客户会要求加入一个给定的大厅。我刚刚确认,如果同时输入4个请求,则所有请求都将在大厅注册,但是玩家人数没有更新,如果更新,则超过上限/上限。>
我使用实体框架错误吗?是否有其他工具可用于此类操作,还是我应该使它使用单个线程(如果有人可以提醒我怎么做),或者使用lock block语句封装我的所有动作/端点?
无论我如何构造代码,都容易受到这些相同时间的http请求的影响,并在我的存储库/上下文中并行移动。
如果我可以创建某种队列,那将是很棒的,我相信这就是将所有内容封装在锁中的作用。
编辑:
正如vasily.sib回答的那样,我可以使用并发令牌解决此问题。请检查他的评论,以获得有关如何使用它们的惊人信息!
答案 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
,您将必须处理它。有多种方法可以解决该错误。一种简单的方法是重复整个操作,直到解决为止。