首页 > 解决方案 > 如何使用实体框架更新一个事务中的选定记录?

问题描述

我正在使用 SQL Server。我的情况是这样的:

  1. 我有一张桌子Messages。该表有一列MessageStatusId
  2. 我有几个服务可以访问这个数据库
  3. 每个服务定期选择带有状态的New消息并以某种方式处理这些消息
  4. 服务选择消息,例如:
    await DbContext.Set<Message>()
                   .Where(m => m.MessageStatusId == MessageStatusEnum.New)
                  .ToListAsync();
    
  5. 处理后,服务更新消息状态

我不希望服务选择同一组记录。所以我需要将消息状态更新为InProcess. 如何使用实体框架在一次事务中实现这一点?

标签: sql-servermultithreadingentity-frameworkconcurrencytransactions

解决方案


您可以实施悲观锁定以确保一次只有一个工作人员访问表,但是保持表访问尽可能短以避免客户端之间的死锁非常重要。我建议每个请求获取有限数量的行:

var options = new TransactionOptions
{
   IsolationLevel = System.Transactions.IsolationLevel.Serializable,
   Timeout = new TimeSpan(0, 0, 0, 10)
};

var messageIds = new List<int>();

using(var scope = new TransactionScope(TransactionScopeOption.RequiresNew, options))
{
    using (var context = new MessageDbContext())
    {
        var messages = context.Messages
            .Where(x => x.MessageStatusId == MessageStatusEnum.New)
            .OrderBy(x => x.MessageDate)
            .Take(100)
            .ToList();

       foreach(var message in messages)
           message.MessageStatusId = MessageStatusEnum.InProgress;

       context.SaveChanges();
       messageIds = messages.Select(x => x.MessageId).ToList();
    }
    scope.Commit();
}

我的建议是:

  • 将此 Message 实体分离到一个有界 DbContext 中,该 DbContext 仅服务于消息检查/状态更新,没有别的。此消息实体可能只是用于此操作的 MessageId 和 MessageStatusId 以及其他用于检查消息状态/w 悲观锁定的实体。
  • 使用同步调用,比异步更快。
  • 获取行,将它们标记为进行中并获取它们的 ID,然后提交状态更改。您可以在之后使用乐观锁定阅读它们,以便在闲暇时处理并避免死锁。
  • 如果碰巧有大量积压,请限制查询的行数以避免意外。(10、100,任何合理的)

推荐阅读