首页 > 解决方案 > 确保在实体框架请求期间阻止数据库行

问题描述

我创建了与我的数据库通信的服务。GetAvailableUserId服务的方法不能同时运行,因为我不想为两个不同的调用返回相同的用户 ID。到目前为止,我已经做到了:

public class UserService : IUserService
{
    public int GetAvailableUserId()
    {
        using (var context = new UsersEntities())
        {
            using (var transaction = context.Database.BeginTransaction())
            {
                var availableUser = context.User
                    .Where(x => x.Available)
                    .FirstOrDefault();

                if (availableUser == null)
                {
                    throw new Exception("No available users.");
                }

                availableUser.Available = false;
                context.SaveChanges();
                transaction.Commit();

                return availableUser.Id;
            }
        }
    }
}

我想测试服务是否会按预期工作,所以我创建了简单的控制台应用程序来模拟同步请求:

Parallel.For(1, 100, (i, state) => {
    var service = new UserServiceReference.UserServiceClient();
    var id = service.GetAvailableUserId();
});

不幸的是,它没有通过那个简单的测试。我可以看到,它为不同的for迭代返回了相同的 id。

那里有什么问题?

标签: c#entity-framework

解决方案


实现此目的的替代方法是捕获 DbUpdateConcurrencyException 以检查在您尝试保存时检索后行中的值是否已更改。

因此,例如,如果同一记录被检索两次。第一个在数据库中更新可用值将导致另一个在尝试保存时抛出并发异常,因为该值自检索以来已更改。

Microsoft 处理并发冲突

在实体中的可用属性上方添加 ConcurrencyCheck 属性。

  [ConcurrencyCheck]
    public bool Available{ get; set; }

然后:

 public int GetAvailableUserId()
    {
        using (var context = new UsersEntities())
        {
          try
            {
                var availableUser = context.User
                    .Where(x => x.Available)
                    .FirstOrDefault();

                if (availableUser == null)
                {
                    throw new Exception("No available users.");
                }

                availableUser.Available = false;
                context.SaveChanges();

                return availableUser.Id;
             }
             catch (DbUpdateConcurrencyException)
             {
                //If same row was already retrieved and updated to false, do not save, instead call the method again to get the next true row. 
                return GetAvailableUserId();
             }
        }
    }

推荐阅读