如何在受唯一索引约束的记录之间交换值?

时间:2014-01-06 14:48:54

标签: c# sql sql-server linq-to-sql unique-constraint

我正在尝试在我的数据库中建模一个简单的树结构。我有一个TreeNode表,其中包含以下列:

Id (int), Name (string), ParentId (int, nullable), ChildPosition (int)

ParentId是父TreeNode的FK,而ChildPosition是TreeNode相对于其兄弟姐妹的位置:

- Parent
-- Child 1 (ChildPosition = 0)
-- Child 2 (ChildPosition = 1)
-- Child 3 (ChildPosition = 2)

我想在Parent + ChildPosition列上放置一个复合唯一约束/索引,因为我不希望任何两个TreeNode在一个下面具有相同的ChildPosition特别Parent。简单,对吧?

但我有一个问题。在我的用户界面中,用户可以将'n'子项拖放到不同的位置,从而有效地在运行中更改其排序(ChildPosition)。这可以影响多个TreeNode。例如,如果我将Child 3拖到Child 1之前,则应更新所有三个节点的ChildPosition

- Parent
-- Child 3 (ChildPosition = 0)
-- Child 1 (ChildPosition = 1)
-- Child 2 (ChildPosition = 2)

然而,我的独特约束似乎并不喜欢这样。它会生成以下错误:

  

无法在对象'dbo.TreeNode'中插入具有唯一的重复键行   index'IX_TreeNode'。

我认为这是因为我试图在一个事务中同时交换多个记录的ChildPosition,并且唯一约束无法识别它。那么我该如何解决这个问题?

我正在通过Linq to Sql进行此操作,如果这是相关的。

编辑:我还应该提一下,我在ChildPosition列上有一个检查限制以防止出现负数,如果可能的话我想保留它。 :)

1 个答案:

答案 0 :(得分:0)

只要你一次只需要移动一个节点,这样的事情应该有效:

declare @ParentID int
declare @FromPosition int
declare @ToPosition int
select @ParentID = 1, @FromPosition = 2, @ToPosition = 0

update TreeNode set ChildPosition =
    CASE WHEN ChildPosition = @FromPosition THEN @ToPosition
         WHEN @FromPosition > @ToPosition THEN ChildPosition + 1
         ELSE ChildPosition -1
    END
where
    ParentID = @ParentID and
    (
        ChildPosition between @FromPosition and @ToPosition or
        ChildPosition between @ToPosition and @FromPosition
    )

这是有效的,因为单个UPDATE语句可以影响多行,并且UNIQUE约束仅在该语句的最终结果上进行检查。

这一点看起来有点时髦:

    (
        ChildPosition between @FromPosition and @ToPosition or
        ChildPosition between @ToPosition and @FromPosition
    )

但那是因为between总是会返回false,如果你在第一个位置给它两个值中较高的值,而备用公式(以避免明显的冗余)往往会更加丑陋。


您可能想要考虑的另一个选择是使用float来表示位置而不是int大多数当时,您可以通过取其位置的平均值(4.5)将孩子放在任何另外两个孩子之间(例如在第4和第5位),而您不必做任何其他重新编号。偶尔,这不起作用(一旦你达到浮点代表的极限),你只需要做一个快速的重新编号练习,为它再次工作腾出空间。