简体   繁体   English

C#向上/向下移动项目

[英]C# moving an item up/down

I got a list that contains items. 我得到一个包含物品的清单。 They all got a 'Sort' column. 他们都有一个'排序'列。 The sort column is of type int, and it's unique. sort列的类型为int,它是唯一的。

Scenario: 场景:

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

If the user moves an item up (for example sort 3) in the list (for example to position 1, which would give the sort value 1), the items that are under the one that just got moved up, have to be shifted down in the list, and the sort number should be applied accordingly. 如果用户在列表中向上移动项目(例如排序3)(例如到位置1,这将给出排序值1),那么刚刚向上移动的项目必须向下移动在列表中,应相应地应用排序编号。 In this case all shifted items sort - 1. 在这种情况下,所有转移的项目进行排序-1。

So the end state of the scenario looks like this: 因此,方案的最终状态如下所示:

sort 1 was sort 3; 第1类是第3类; sort 3 was sort 2; 第3类是第2类; sort 3 is now sort 1; 排序3现在是排序1;

How can i do this with LINQ ? 我怎么能用LINQ做到这一点? It's not just 3 items. 这不仅仅是3项。 It can be a lot more. 它可以更多。

[Edit] [编辑]

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

    return View(dataContext.items);
 }

That might not be the easiest code to understand but I've tested it and it seems to work as intended. 这可能不是最容易理解的代码,但我已经测试了它,它似乎按预期工作。

Let's setup some data. 让我们设置一些数据。

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;

First, query is split into three parts depending on positions of old and new indexes, so we can handle each case separately. 首先,根据旧索引和新索引的位置将查询分为三个部分,因此我们可以分别处理每个案例。

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;

Results for moving an item down ( oldSort = 1 , newSort = 3 ): 向下移动项目的结果( oldSort = 1newSort = 3 ):

1 foo2
2 foo3 
3 foo1 
4 foo4 

Results for moving an item up ( oldSort = 4 , newSort = 2 ): 向上移动项目的结果( oldSort = 4newSort = 2 ):

1 foo1 
2 foo4 
3 foo2 
4 foo3 

UPDATE : The query works by splitting a sequence into three parts 更新 :查询的工作原理是将序列分为三个部分

  • Item with the old index becomes an item with the new index; 具有旧索引的项目将成为具有新索引的项目;
  • Items between the old and new indexes are shifted either up or down; 旧索引和新索引之间的项目向上或向下移动;
  • The rest keeps their indexes. 其余的保留他们的索引。

The result is the union of the parts. 结果是零件的并集。

UPDATE 2 : The query works for any number of items and the absence of loops is intentional. 更新2 :查询适用于任意数量的项目,并且缺少循环是故意的。

UPDATE 3 : Here's one way to make the query work with LINQ-to-Entities. 更新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 });
}

The difference is that the types are now explicitly compatible. 不同之处在于类型现在明确兼容。

I know that you asked for a LINQ solution, but LINQ seems complicated to use in this situation, especially if you want to adjust the Sort column as well. 我知道您要求LINQ解决方案,但在这种情况下LINQ似乎很复杂,特别是如果您想要调整Sort列。 I suggest a plain old approach using for loops and indexes. 我建议使用for循环和索引的简单旧方法。 It performs the sort operation in-place and does not created a new list. 它就地执行排序操作,并且不会创建新列表。

In order to make it reusable I create it as extension method for the IList interface, which makes it compatible to arrays too. 为了使其可重用,我将其创建为IList接口的扩展方法,这使得它也与数组兼容。

In order to make it generic, you need some way to access the Sort column. 为了使其通用,您需要一些方法来访问Sort列。 Exposing this column through an interface would restrict the solution to classes implementing this interface. 通过接口公开此列会将解决方案限制为实现此接口的类。 Therefore I opted for accessors that you have to pass as delegates. 因此,我选择了必须作为代表通过的访问器。 They also work if the Sort column has another name like Order for instance. 如果“ 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;
    }
}

You can use the method like this 你可以使用这样的方法

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

Note that you have to pass the list indexes and not the sort values. 请注意,您必须传递列表索引而不是排序值。


Here are the classes I used for the tests. 这是我用于测试的类。 Just set a breakpoint at the end of the two test methods in order to inspect the result in the locals window. 只需在两个测试方法的末尾设置一个断点,以便在locals窗口中检查结果。 Start the test in the Class View by right clicking on the Test class and choosing "Invoke Static Method". 右键单击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<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" }
        };
    }

}

UPDATE UPDATE

A note on adjusting the sort key: The solution above works well if the sort keys are in-order and unique. 关于调整排序键的注意事项:如果排序键是有序且唯一的,则上述解决方案很有效。 If this is not always the case, a more robust solution would be to adjust the sort keys before storing the list back to the DB by simply setting the sort key equal to the list index. 如果情况并非总是如此,则更可靠的解决方案是在将列表存储回数据库之前,通过简单地将排序关键字设置为等于列表索引来调整排序关键字。 This would simplify the MoveItem method. 这将简化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);
    }
}

The conditional operator is useful here: 条件运算符在这里很有用:

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
                   }); 

This is using Serge's setup : 这是使用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;

Its performance is decent (O(n) in all scenarios), plus it's concise and readable. 它的性能很好(在所有场景中都是O(n)),而且它简洁易读。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM