2012-11-23 91 views
2

我得到了一個包含項目的列表。他們都有一個'排序'欄。排序列是int類型的,並且是唯一的。C#將項目向上/向下移動

場景:

排序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); 
} 
+0

你可以發佈進入列表中的對象的代碼嗎? –

+0

你的意思是這個代碼? – Yustme

+0

請在下次標記你指的是哪一種linq。 –

回答

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

1 foo2 
2 foo3 
3 foo1 
4 foo4 

結果移動的項目向上(oldSort = 4newSort = 2):

1 foo1 
2 foo4 
3 foo2 
4 foo3 

UPDATE:該查詢通過將一個序列分成三部分來工作

  • 具有舊索引的項目成爲具有新索引的項目;
  • 舊指標和新指標之間的項目向上或向下移動;
  • 其餘保留其索引。

結果是部件的聯合。

UPDATE 2:該查詢適用於任意數量的項目,且缺少循環是故意的。

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

不同之處在於這些類型現在是明確兼容的。

+0

這僅適用於3件物品嗎?但如果有100個呢?它應該更具活力。我想我需要一個循環來完成這項工作。 – Yustme

+1

@Yustme查詢不取決於項目的數量。而使用LINQ的目的是避免循環。無論如何,請測試並告訴我們。 –

+0

儘管這確實可行(我沒有測試它),並且OP要求提供LINQ解決方案,但我認爲LINQ不應該用於此目的。 LINQ應該簡化一些事情,而不是讓它們變得更加複雜。 –

2

我知道你問過一個LINQ解決方案,但是在這種情況下LINQ看起來很複雜,特別是如果你想調整Sort列。我建議使用循環和索引的普通舊方法。它在原地執行排序操作並且不創建新列表。

爲了使它可重用,我將它創建爲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" } 
     }; 
    } 

} 

UPDATE

關於調整排序關鍵字的注意事項:上述解決方案的工作很好,如果排序鍵是有序的和獨特的。如果情況並非總是如此,則通過簡單地將排序鍵設置爲等於列表索引,更強健的解決方案將是在將列表存儲回DB之前調整排序鍵。這將簡化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); 
    } 
} 
+0

LINQ方法針對問題的關鍵優勢在於查詢將被正確地轉換爲SQL並且將在一個語句中在SQL Server上運行。您的建議必然意味着應用程序必須將整個表格放入內存中。說實話,我不確定代碼是否簡單得多。這種方法有什麼好處? –

+0

非常常見的情況是列出某些列表控件中的項目並交互地上下移動項目。這是我想到的用例。 –

+0

@ OlivierJacot-Descombes,我知道如何去做簡單的老方法。但我想學習如何使用LINQ。不過謝謝你的努力。我把它提高了。 – Yustme

1

條件運算符這裏是有用的:

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's setup

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)在所有情況下),再加上它的簡潔並可讀。

+0

看起來不錯,「新」會返回什麼?一個匿名類型? – Yustme

+0

@Yustme:肯定的。 –