2

我有一个包含项目的列表。他们都有一个“排序”列。排序列是 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);
 }
4

3 回答 3

4

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

让我们设置一些数据。

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 = 1, newSort = 3):

1 foo2
2 foo3 
3 foo1 
4 foo4 

向上移动项目的结果 ( oldSort = 4, newSort = 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 });
}

不同之处在于这些类型现在是明确兼容的。

于 2012-11-23T13:31:27.153 回答
2

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

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

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

public static class ListExtensions
{
    public static void MoveItem<T>(this IList<T> list, int fromIndex, int toIndex,
                                   Func<T, int> getSortKey, Action<T, int> 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);

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


这是我用于测试的类。只需在两个测试方法的末尾设置一个断点,以便在本地窗口中检查结果。通过右键单击Test类并选择“调用静态方法”在类视图中开始测试。

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<SomeItem> list = InitializeList();
        list.MoveItem(3, 1, x => x.Sort, (x, i) => x.Sort = i);
    }

    public static void MoveDown()
    {
        List<SomeItem> list = InitializeList();
        list.MoveItem(1, 3, x => x.Sort, (x, i) => x.Sort = i);
    }

    private static List<SomeItem> InitializeList()
    {
        return new List<SomeItem> {
            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" }
        };
    }

}

更新

关于调整排序键的说明:如果排序键是有序且唯一的,则上述解决方案效果很好。如果情况并非总是如此,更可靠的解决方案是在将列表存储回数据库之前调整排序键,只需将排序键设置为等于列表索引即可。这将简化该MoveItem方法。

public static void MoveItem<T>(this IList<T> 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<T>(this IList<T> list, Action<T, int> setSortKey)
{
    for (int i = 0; i < list.Count; i++) {
        setSortKey(list[i], i);
    }
}
于 2012-11-23T14:40:09.607 回答
2

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

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)),而且它简洁易读。

于 2012-11-23T15:18:31.047 回答