【问题标题】:How to cascade delete in SQL on a table that has an FK pointed to the PK of that same table?如何在 FK 指向同一张表的 PK 的表上在 SQL 中级联删除?
【发布时间】:2013-10-08 17:13:30
【问题描述】:

这是我的表 - ParentId 是返回到 Id 的外键。如您所知,这是一个层次结构:

这是我的关系(请注意,在 INSERT 和更新规范下,删除和更新规则是灰色的 - 这就是导致我的问题的原因):

这是我的数据:

最后,仅出于视觉目的,这是我在 UI 上的输出:

问题:我想要做的是删除“澳大利亚”并让 SQL 自动级联并删除所有由 ParentId/Id 链接在一起的“子区域”。我知道我可以自定义代码来做到这一点,但我想为了时间避免这样做。为什么删除和更新规则显示为灰色?如何对具有与同一个表中的主键绑定的外键的表进行级联删除?

【问题讨论】:

    标签: c# sql tsql hierarchy cascade


    【解决方案1】:

    恐怕这在 SQL Server 中是不可能的。如果您尝试像这样创建表:

    create table Region (
        Id int primary key,
        ParentId int references Region(Id) on delete cascade,
        Name nvarchar(50)
    )
    

    您将收到错误消息:Introducing FOREIGN KEY constraint 'FK__Region__ParentId' on table 'Region' may cause cycles or multiple cascade paths。而且,实际上,可以使用您的模式创建循环,如下所示:

    Id    Name     ParentId
     1    USA             2
     2    Germany         1
    

    所以在你的情况下,我认为你必须创建 on delete 触发器。

    【讨论】:

      【解决方案2】:

      我最终创建了一个专门用于邻接列表的自定义模块。 SQL 中的邻接列表是指您有一个表通过 ParentId 列维护“n”深度层次结构,该列是返回到同一表中 Id 列(主键)的外键。

      我创建了一个名为“Children”的辅助类。此类的目的是返回给定父级的所有遍历的子级 ID 的列表。因此,如果您传入 6 级以上的父级的 Id,下面的代码将遍历所有 6 级并返回所有子、孙、曾孙等的 Id 的完整列表。

      我之所以要获取所有 Id 的子项列表而不是要删除的对象列表,是因为如果我要获取要删除的对象列表,我将不得不在不同的 DbContext 下获取这些对象,所以当我尝试实际删除它们,我会收到一条错误消息,指出对象已分离(或类似的东西),因为它是在不同的上下文中实例化的。获取 Id 列表可以防止这种情况发生,我们可以使用相同的上下文来执行删除。

      因此,从您的代码中调用此方法GetChildrenIds(List<int> immediateChildrenIds) 并传入与所选节点的直接子节点相对应的ints 列表。例如,要获取要传递给此方法的直接子级列表,请使用以下内容(这是基于使用 WebAPI 的 ASP.NET MVC 模式):

      // keep in mind that `id` is the `id` of the clicked on node.
      
      // DELETE api/region/5
      public HttpResponseMessage Delete(int id)
      {
           Region region = db.Regions.Find(id);
      
           List<int> tempRegionIds = new List<int>();
           List<int> immediateChildrenIds = (from i in db.Regions where i.ParentId == id select i.Id).ToList();
           List<int> regionsToDeleteIds = new List<int>();
      
           // the below is needed because we need to add the Id of the clicked on node to make sure it gets deleted as well
           regionsToDeleteIds.Add(region.Id);
      
           // we can't make this a static method because that would require static member
           // variables, and static member variables persist throughout each recursion
           HelperClasses.HandleChildren.Children GetChildren = new HelperClasses.HandleChildren.Children();
      
           // see below this code block for the GetChildrenIds(...) method
           tempRegionIds = GetChildren.GetChildrenIds(immediateChildrenIds);
      
           // here, we're just adding to regionsToDeleteIds the children of the traversed parent
           foreach (int tempRegionId in tempRegionIds)
           {
               regionsToDeleteIds.Add(tempRegionId);
           }
      
           // reverse the order so it goes youngest to oldest (child to grandparent)
           // is it necessary?  I don't know honestly.  I just wanted to make sure that
           // the lowest level child got deleted first (the one that didn't have any children)
           regionsToDeleteIds.Reverse(0, regionsToDeleteIds.Count);
      
           if (regionsToDeleteIds == null)
           {
               return Request.CreateResponse(HttpStatusCode.NotFound);
           }
      
           foreach (int regionId in regionsToDeleteIds)
           {
               // here we're finding the object based on the passed in Id and deleting it
               Region deleteRegion = db.Regions.Find(regionId);
               db.Regions.Remove(deleteRegion);
           }
      
           ...
      

      以下代码是返回子 ID 的完整列表的类。我把这段代码放在一个单独的帮助类文件中。 DbContext _db 是我所说的,当我说您不想在此上下文下检索对象列表时,否则 Delete 在控制器中实际调用时将不起作用。因此,如您所见,我得到了一个 Id 列表,并进行了实际的 DbContext 调用以获取我的控制器中的对象,而不是这个帮助程序类。

      public class Children
          {
              private Entities _db = new Entities(HelperClasses.DBHelper.GetConnectionString());
              private List<int> _childrenIds = new List<int>();
              private List<int> _childRegionIds = new List<int>();
      
              public List<int> GetChildrenIds(List<int> immediateChildrenIds)
              {
                  // traverse the immediate children
                  foreach (var i in immediateChildrenIds)
                  {
                      _childRegionIds.Add(i);
                      _childrenIds = (from child in _db.Regions where child.ParentId == i select child.Id).ToList();
                      if (_childrenIds.Any())
                          GetChildrenIds(_childrenIds);
                      else
                          continue;
                  }
      
                  return _childRegionIds;
              }
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-01-07
        • 2013-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-17
        • 1970-01-01
        • 2010-09-16
        相关资源
        最近更新 更多