2

我在我的 asp.net mvc Web 应用程序中使用实体框架,但我无法理解它将如何处理访问相同数据的多个事务。例如,我有以下操作方法可以删除一个集合,然后遍历一个集合并删除记录:-

[HttpPost]
public ActionResult AssignPermisionLevel2(ICollection<SecurityroleTypePermision> list, int id)
{
    repository.DeleteSecurityroleTypePermisions(id);
    foreach (var c in list)
    {
        repository.InsertOrUpdateSecurityroleTypePermisions(c,User.Identity.Name);
    }
    repository.Save();
    return RedirectToAction("AssignPermisionLevel", new { id = id });
}

这将调用以下存储库方法:-

public void DeleteSecurityroleTypePermisions(int securityroleID)
{    
    var r = tms.SecurityroleTypePermisions.Where(a => a.SecurityRoleID == securityroleID);
    foreach (var c in r) {
        tms.SecurityroleTypePermisions.Remove(c);
    }

}

&

public void InsertOrUpdateSecurityroleTypePermisions(SecurityroleTypePermision role, string username)
{    
     var auditinfo = IntiateAdminAudit(tms.AuditActions.SingleOrDefault(a => a.Name.ToUpper() == "ASSIGN PERMISION").ID, tms.SecurityTaskTypes.SingleOrDefault(a => a.Name.ToUpper() == "SECURITY ROLE").ID, username, tms.SecurityRoles.SingleOrDefault(a=>a.SecurityRoleID == role.SecurityRoleID).Name, tms.PermisionLevels.SingleOrDefault(a=>a.ID== role.PermisionLevelID).Name +  " --> " + tms.TechnologyTypes.SingleOrDefault(a=>a.AssetTypeID == role.AssetTypeID).Name);
     tms.SecurityroleTypePermisions.Add(role);
     InsertOrUpdateAdminAudit(auditinfo);
}

那么假设两个用户同时访问相同的操作方法,那么他们的事务会相互冲突吗?,或者所有事务操作(删除和添加)都将执行,然后另一个事务将开始?

更新 在我的 Controller 类中,我将按如下方式启动存储库:-

[Authorize]
    public class SecurityRoleController : Controller
    {

        Repository repository = new Repository();

我的第二个问题是。您提到EF会将实体标记为删除或插入,然后sql将独立执行数据库。但是如果一个 sql 语句删除了一些实体,而第二个事务中的另一个 sql 语句删除了其他实体,这种冲突会发生在数据库级别吗?或者一旦第一个事务的第一个sql语句开始执行,它会阻止其他事务被执行?你能建议吗?

4

1 回答 1

1

这完全取决于您如何实现 DbContext。如果您的上下文在控制器中实例化,则每个事务都将包含在该上下文中,即

public class SomeController : Controller
{
    var repository = new DbContext();

    [HttpPost]
    public ActionResult AssignPermisionLevel2(ICollection<SecurityroleTypePermision> list, int id)
    {
        repository.DeleteSecurityroleTypePermisions(id);
        foreach (var c in list)
        {
            repository.InsertOrUpdateSecurityroleTypePermisions(c,User.Identity.Name);
        }
        repository.Save();
        return RedirectToAction("AssignPermisionLevel", new { id = id });
    }       
}

每个请求都将创建自己的存储库实例,并且两者不会在应用程序级别发生冲突。当SaveChanges在 DbContext 上调用时,它在单个事务中完成,并且为每个请求创建存储库对象。

不幸的是,Entity Framework 不会像您期望的那样删除,而是会删除单个元素而不是整个表。当您在第一步中删除实体并在第二步中添加它们时,实际发生的情况如下:

  1. 加载实体 X、Y 和 Z
  2. 将 X、Y 和 Z 标记为删除
  3. 插入新行 A、B 和 C
  4. 运行删除 X、Y 和 Z 并插入 A、B 和 C 的 SQL

现在,如果两个请求同时进入,则可能发生的情况是对象 X、Y 和 Z 都在步骤 1 中由两个请求上下文加载。它们都被标记为删除,并且两组 A、B 和 C 被设置为插入。当第一个事务执行时它会很好,但是当第二个事务提交时它将无法找到 X、Y 和 Z,因为它们不再存在。

您可以使用lockover the critical 部分,以便在实体被另一个请求删除之前不会加载它们。锁必须是静态的,例如:

public class SecurityRoleController : Controller
{
Repository repository = new Repository();
public static object REQUEST_LOCK = new object();

[HttpPost]
public ActionResult AssignPermisionLevel2(ICollection<SecurityroleTypePermision> list, int id)
{
    lock(REQUEST_LOCK)
    {
            repository.DeleteSecurityroleTypePermisions(id);
            foreach (var c in list)
            {
                repository.InsertOrUpdateSecurityroleTypePermisions(c,User.Identity.Name);
            }
            repository.Save();
        }
            return RedirectToAction("AssignPermisionLevel", new { id = id });
    }       
}

更新 2

您的问题有两个方面,SQL 处理事务的方式和实体框架执行删除的方式。如果不深入讨论线程的大量细节,您基本上必须锁定操作,以便相同的方法不能在完全相同的时间执行两次。这将防止上下文读取可能陈旧/已删除的数据。

您可以通过以下问题阅读有关 SQL/EF 竞争条件的更多信息:Preventing race condition of if-exists-update-else-insert in Entity Framework

于 2013-08-16T16:31:36.220 回答