我正在尝试在我的数据库中建模一个简单的树结构。我有一个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
列上有一个检查限制以防止出现负数,如果可能的话我想保留它。 :)
答案 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位),而您不必做任何其他重新编号。偶尔,这不起作用(一旦你达到浮点代表的极限),你只需要做一个快速的重新编号练习,为它再次工作腾出空间。