C#向上/向下移动项目

我有一个包含项目的列表。 他们都有一个’排序’列。 sort列的类型为int,它是唯一的。

场景:

排序1; 排序2; 排序3;

如果用户在列表中向上移动项目(例如排序3)(例如到位置1,这将给出排序值1),那么刚刚向上移动的项目必须向下移动在列表中,应相应地应用排序编号。 在这种情况下,所有转移的项目排序 – 1。

所以场景的最终状态如下所示:

排序1是排序3; 第3类是第2类; 排序3现在排序1;

我怎么能用LINQ做到这一点? 这不仅仅是3项。 它可以更多。

[编辑]

  public ActionResult Up(int id) { var item = dataContext.item.FirstOrDefault(x => x.item == id); return View(dataContext.items); } 

这可能不是最容易理解的代码,但我已经对它进行了测试,它似乎按预期工作。

我们来设置一些数据。

 var array = new [] { new { Sort = 1, Value = "foo1", }, new { Sort = 2, Value = "foo2", }, new { Sort = 3, Value = "foo3", }, new { Sort = 4, Value = "foo4", }, }; var oldSort = 1; var newSort = 3; 

首先,根据旧索引和新索引的位置将查询分为三个部分,因此我们可以分别处理每个案例。

 var q = oldSort > newSort ? array .Where(x => x.Sort >= newSort && x.Sort < oldSort) .Select(x => new { Sort = x.Sort + 1, Value = x.Value }) .Union(array.Where(x => x.Sort < newSort || x.Sort > oldSort)) .Union(array.Where(x => x.Sort == oldSort) .Select(x => new { Sort = newSort, Value = x.Value })) : oldSort < newSort ? array .Where(x => x.Sort <= newSort && x.Sort > oldSort) .Select(x => new { Sort = x.Sort - 1, Value = x.Value }) .Union(array.Where(x => x.Sort > newSort || x.Sort < oldSort)) .Union(array.Where(x => x.Sort == oldSort) .Select(x => new { Sort = newSort, Value = x.Value })) : array; 

向下移动项目的结果( oldSort = 1newSort = 3 ):

 1 foo2 2 foo3 3 foo1 4 foo4 

向上移动项目的结果( oldSort = 4newSort = 2 ):

 1 foo1 2 foo4 3 foo2 4 foo3 

更新 :查询的工作原理是将序列分为三个部分

  • 具有旧索引的项目将成为具有新索引的项目;
  • 旧索引和新索引之间的项目向上或向下移动;
  • 其余的保留他们的索引。

结果是部件的结合。

更新2 :查询适用于任意数量的项目,并且缺少循环是故意的。

更新3 :这是使查询与LINQ-to-Entities一起工作的一种方法。

 using (var context = new TestDBEntities()) { var array = context.TestTables; var q = oldSort > newSort ? array .Where(x => x.Sort >= newSort && x.Sort < oldSort) .Select(x => new { Sort = x.Sort + 1, Value = x.Value }) .Union(array.Where(x => x.Sort < newSort || x.Sort > oldSort) .Select(x => new { Sort = x.Sort, Value = x.Value })) .Union(array.Where(x => x.Sort == oldSort) .Select(x => new { Sort = newSort, Value = x.Value })) : oldSort < newSort ? array .Where(x => x.Sort <= newSort && x.Sort > oldSort) .Select(x => new { Sort = x.Sort - 1, Value = x.Value }) .Union(array.Where(x => x.Sort > newSort || x.Sort < oldSort) .Select(x => new { Sort = x.Sort, Value = x.Value })) .Union(array.Where(x => x.Sort == oldSort) .Select(x => new { Sort = newSort, Value = x.Value })) : array.Select(x => new { Sort = x.Sort, Value = x.Value }); } 

不同之处在于类型现在明确兼容。

我知道您要求LINQ解决方案,但在这种情况下LINQ似乎很复杂,特别是如果您想要调整Sort列。 我建议使用for循环和索引的简单旧方法。 它就地执行排序操作,但不创建新列表。

为了使其可重用,我将其创建为IList接口的扩展方法,这使得它也与数组兼容。

为了使其通用,您需要一些方法来访问Sort列。 通过接口公开此列会将解决方案限制为实现此接口的类。 因此,我选择了您必须作为代表传递的访问者。 如果Sort列有另一个名称,例如Order for,它们也可以工作。

 public static class ListExtensions { public static void MoveItem(this IList list, int fromIndex, int toIndex, Func getSortKey, Action setSortKey) { T temp = list[fromIndex]; int lastSortKey = getSortKey(temp); setSortKey(temp, getSortKey(list[toIndex])); if (fromIndex > toIndex) { // Move towards beginning of list (upwards). for (int i = fromIndex; i > toIndex; i--) { list[i] = list[i - 1]; int nextSortKey = getSortKey(list[i]); setSortKey(list[i], lastSortKey); lastSortKey = nextSortKey; } } else if (fromIndex < toIndex) { // Move towards end of list (downwards). for (int i = fromIndex; i < toIndex; i++) { list[i] = list[i + 1]; int nextSortKey = getSortKey(list[i]); setSortKey(list[i], lastSortKey); lastSortKey = nextSortKey; } } list[toIndex] = temp; } } 

你可以使用这样的方法

 list.MoveItem(3, 1, x => x.Sort, (x, i) => x.Sort = i); 

请注意,您必须传递列表索引而不是排序值。


这是我用于测试的类。 只需在两个测试方法的末尾设置一个断点,以便在locals窗口中检查结果。 右键单击Test类并选择“Invoke Static Method”,在Class View中开始测试。

 public class SomeItem { public int Sort { get; set; } public string Value { get; set; } public override string ToString() { return String.Format("Sort = {0}, Value = {1}", Sort, Value); } } public static class Test { public static void MoveUp() { List list = InitializeList(); list.MoveItem(3, 1, x => x.Sort, (x, i) => x.Sort = i); } public static void MoveDown() { List list = InitializeList(); list.MoveItem(1, 3, x => x.Sort, (x, i) => x.Sort = i); } private static List InitializeList() { return new List { new SomeItem{ Sort = 1, Value = "foo1" }, new SomeItem{ Sort = 2, Value = "foo2" }, new SomeItem{ Sort = 3, Value = "foo3" }, new SomeItem{ Sort = 4, Value = "foo4" }, new SomeItem{ Sort = 5, Value = "foo5" } }; } } 

UPDATE

关于调整排序键的注意事项:如果排序键是有序且唯一的,则上述解决方案很有效。 如果情况并非总是这样,那么更健壮的解决方案是在将列表存储回DB之前通过简单地将排序键设置为等于列表索引来调整排序键。 这将简化MoveItem方法。

 public static void MoveItem(this IList list, int fromIndex, int toIndex) { T temp = list[fromIndex]; if (fromIndex > toIndex) { // Move towards beginning of list (upwards). for (int i = fromIndex; i > toIndex; i--) { list[i] = list[i - 1]; } } else if (fromIndex < toIndex) { // Move towards end of list (downwards). for (int i = fromIndex; i < toIndex; i++) { list[i] = list[i + 1]; } } list[toIndex] = temp; } public static void FixSortKeys(this IList list, Action setSortKey) { for (int i = 0; i < list.Count; i++) { setSortKey(list[i], i); } } 

条件运算符在这里很有用:

 var newitems = items.Select(x => new { Value = x.Value, Sort = x.Sort == oldSort ? newSort : x.Sort < oldSort && x.Sort >= newSort ? x.Sort + 1 : x.Sort > oldSort && x.Sort < newSort ? x.Sort - 1 : x.Sort }); 

这是使用Serge的设置 :

 var items = new [] { new { Sort = 1, Value = "foo1", }, new { Sort = 2, Value = "foo2", }, new { Sort = 3, Value = "foo3", }, new { Sort = 4, Value = "foo4", }, }; var oldSort = 1; var newSort = 3; 

它的性能很不错(在所有情况下都是O(n)),而且它简洁易读。