2013-07-31 48 views
18

Tôi hiện đang cố gắng tìm ra cách tốt để sắp xếp các phần tử của mình với LINQ và C#, nhưng tôi không thể làm như vậy.LINQ sắp xếp một danh sách phẳng dựa trên childorder

Đối với vấn đề chúng ta hãy giả sử bạn có bảng sau

---TempTable 
ID (int) 
ParentID (int) 
Name (varchar) 
SortOrder (int) 

ID và ParentID có liên quan đến nhau và đưa cho tôi một cấu trúc dữ liệu tự hierachical. Các phần tử gốc có một giá trị rỗng trong trường ID. SortOrder chỉ là một phần của toàn bộ bảng và dựa trên ParentID, vì vậy các phần tử chia sẻ cùng một ParentID có 1, 2, 3 trong đó.

Cho phép tiếp tục đảm nhận các dữ liệu sau:

ID = 1 
ParentID = null 
Name = Test 1 
SortOrder = 1 

ID = 2 
ParentID = 1 
Name = Test 2 
SortOrder = 1 

ID = 3 
ParentID = 1 
Name = Test 3 
SortOrder = 2 

ID = 4 
ParentID = 2 
Name = Test 4 
SortOrder = 1 

danh sách phẳng mong muốn của tôi nên có trình tự sau:

Test 1 //root element with sort order 1 = very top 
Test 2 //child element of root with sort order 1 
Test 4 //child element of test 2 with sort order 1 
Test 3 //child element of root with sort order 2 

Ngoài ra tôi muốn để có được những đối tượng riêng của mình mà không chỉ nhận được một phần của thông tin đã sử dụng lựa chọn mới ...

Đây là một trong những lần thử không thành công của tôi:

from x in EntityModel.TempTables //DbSet<TempTable> by EntityFramework - which already holds all elements 
    orderby x.SortOrder 
    from y in x.TempTableChildren //Navigation Property by EntityFramework 
    orderby y.SortOrder 
    select y 

Cảm ơn trước sự giúp đỡ của bạn.

Edit:

Trình tự với ParentID có thể hữu ích, với TestData trao kể từ ID, ParentIDs nằm trong trật tự hoàn hảo nhưng điều này isnt trường hợp trong một ứng dụng thực sự sống kể từ khi dữ liệu của nó điều khiển, ai đó có thể xóa một mục tạo một hình mới và đặt nó theo một thứ tự nhất định theo cha mẹ và bạn sẽ có một cái gì đó như:

ID = 193475037 
ParentID = 2 
Name = Test 192375937 
SortOrder = 25 

Bây giờ trong việc áp dụng nó sẽ có thể di chuyển một này và ParentID và SortOrder sẽ thay đổi ngẫu nhiên cho một cái gì đó như:

ID = 193475037 
ParentID = 456798424 
Name = Test 192375937 
SortOrder = 4 

Để furhter giải thích các vấn đề ở đây là một số mã - làm thế nào tôi sẽ làm điều đó mà không cần 1 beautifull LINQ Query nhưng với 2 và một số yield return:

public class LinqTestDemo 
{ 
    Random rand = new Random(); 
    List<TempTable> list = new List<TempTable>(); 

    public List<TempTable> GetFlatData() 
    { 
     list = GetTestData(); 

     var rootElement = (from x in list 
          where x.ParentID == null 
          orderby x.SortOrder 
          select x).ToList(); 

     var flatList = OrderChilds(rootElement).ToList(); 

     foreach (var tempTable in flatList) 
     { 
      Console.WriteLine(string.Format("ID = {0} - ParentID = {1} - Name = {2} - SortOrder = {3}", tempTable.ID, tempTable.ParentID, tempTable.Name, tempTable.SortOrder)); 
     } 

     return flatList; 
    } 

    private IEnumerable<TempTable> OrderChilds(List<TempTable> enumerable) 
    { 
     foreach (var tempTable in enumerable) 
     { 
      yield return tempTable; 

      TempTable table = tempTable; 
      var childs = OrderChilds((from x in list 
             where x.ParentID == table.ID 
             orderby x.SortOrder 
             select x).ToList()); 

      foreach (var child in childs) 
      { 
       yield return child; 
      } 
     } 
    } 

    public List<TempTable> GetTestData() 
    { 
     var returnValue = new List<TempTable>(); 
     for (int i = 0; i < 50; i++) 
     { 
      var tempTable = new TempTable(); 
      tempTable.ID = i; 
      if (i == 0) 
       tempTable.ParentID = null; 
      else 
       tempTable.ParentID = rand.Next(0, i); 

      var maxSortOrder = (from x in returnValue 
           where x.ParentID == tempTable.ParentID 
           select (int?)x.SortOrder).Max(); 

      if (maxSortOrder.HasValue) 
       tempTable.SortOrder = maxSortOrder.Value + 1; 
      else 
       tempTable.SortOrder = 1; 

      tempTable.Name = string.Format("Test {0:00}", i); 
      returnValue.Add(tempTable); 
     } 

     return returnValue; 
    } 

    public class TempTable 
    { 
     public int ID { get; set; } 
     public int? ParentID { get; set; } 
     public string Name { get; set; } 
     public int SortOrder { get; set; } 
    } 
} 

@ Breadth-First vs Depth-First Traversal: Sau khi đọc một số tôi sẽ nói kết quả mong muốn của tôi sẽ là Depth-First Traversal, trong đó các phần tử ở cùng độ sâu nên được sắp xếp theo thuộc tính SortOrder.

+4

bảng cấu trúc của bạn định nghĩa một cấu trúc cây - và do đó, có hai cách để "đi qua" cây để tạo ra một cấu trúc phẳng . Độ sâu đầu tiên: http://www.cs.bu.edu/teaching/c/tree/breadth-first/ Chiều rộng đầu tiên: http://www.brpreiss.com/books/opus4/html/ page551.html Không rõ ràng trong ví dụ của bạn về loại truyền tải mà bạn đang đề cập đến. –

+0

Sau khi đọc một số tôi sẽ nói kết quả mong muốn của tôi sẽ là Depth-First Traversal, trong đó các phần tử ở độ sâu cùng cấp phải được sắp xếp theo thuộc tính SortOrder. –

+0

Có bao nhiêu cấp độ sâu? Nếu bạn có thể có chiều sâu không giới hạn thì không thể truy vấn đơn lẻ. Ngoài ra, cách khung thực thể hoạt động, nó không thành công trên các truy vấn có tính chất đệ quy. Các giải pháp duy nhất là cây traversal. –

Trả lời

12
public lEnumerable<TempTable> GetList(int? parentID = null){ 

    foreach (var item in Context.TempTables 
     .Where(x => x.ParentID == parentID) 
     .OrderBy(x=> x.SortOrder) 
     .ToList() { 

     yield return item; 

     foreach(var child in GetList(item.ID)) 
     { 
      yield return child; 
     } 

    } 
    } 


    var sortedList = GetList(); 

Nó tương tự như phương pháp của bạn nhưng nó là nhỏ hơn & đệ quy. Và làm việc cho nhiều cấp độ sâu. Tôi thích gọi ToList vì nó sẽ đóng resultset trước khi truy vấn truy vấn tiếp theo.

Hiện không có cách nào để thực hiện việc này trong một truy vấn đơn lẻ.

Với Độc Query như yêu cầu

Entity Framework sẽ tự động điền tất cả trẻ em.

public IEnumerable<TempTable> PrepareList(IEnumerable<TempTable> list){ 
    list = list.OrderBy(x=> x.SortOrder); 
    foreach(var item in list){ 
     yield return item; 
     foreach(var child in PrepareList(item.ChildTempTables)){ 
      yield return child; 
     } 
    } 
} 

// since EF will automatically fill each children on fetch 
// all we need is just a top level nodes 
// which we will pass to PrepareList method 
var list = Context.TempTables.ToList().Where(x=> x.ParentID == null); 
var sortedList = PrepareList(list).ToList(); 

// it is good to create list at the end if you are going to 
// iterate it many times and logic will not change. 
+0

Bạn có thể sửa đổi mã của bạn, để danh sách ban đầu (trong trường hợp của bạn là Context.TempTables) có thể là một IEnumerable đã truy vấn được chuyển và sắp xếp theo yêu cầu, như bạn có thể nhận thấy 2 câu trả lời "mới" khác (bởi Thomas B. và Roman Pekar) đều có giải pháp cho kịch bản này. Bạn sẽ làm điều này như thế nào? Thx cho thời gian của bạn. –

+0

@RandRandom Tôi đã thêm câu trả lời sẽ thực hiện cùng một công việc với mọi thứ được tải trước. –

2

Hãy thử điều này:

public class Item 
{ 
    public int ID { get; set; } 
    public int? ParentID { get; set; } 
    public string Name { get; set; } 
    public int SortOrder { get; set; } 
} 

public void DoWork() 
    { 
     Item[] data = new Item[] { 
      new Item() { ID = 2, ParentID = 1, Name = "Test 2", SortOrder = 1}, 
      new Item() { ID = 3, ParentID = 1, Name = "Test 3", SortOrder = 2}, 
      new Item() { ID = 4, ParentID = 2, Name = "Test 4", SortOrder = 1}, 
      new Item() { ID = 1, ParentID = null, Name = "Test 1", SortOrder = 1}, 
     }; 

     var result = from x in data 
        orderby x.SortOrder, x.ParentID 
        select x; 

     foreach (var row in result.ToArray()) 
     { 
      Console.WriteLine(row.Name); 
     } 
    } 

Tôi đoán nó là tất cả về trật tự chính xác

+0

Vui lòng xem chỉnh sửa của tôi, không biết liệu bạn có nhận được thông báo về chỉnh sửa không có nhận xét này không. –

2

Dưới đây là một giải pháp đơn giản:

public class TempTable 
{ 
    public int ID {get;set;} 
    public int? ParentID {get;set;} 
    public String Name {get;set;} 
    public int SortOrder {get;set;} 
} 

public List<TempTable> GetTempData() 
{ 
    var temp = new List<TempTable>(); 
    temp.Add(new TempTable { ID = 1, ParentID = null, Name = "Test 1", SortOrder = 1 }); 
    temp.Add(new TempTable { ID = 2, ParentID = 1, Name = "Test 2", SortOrder = 1 }); 
    temp.Add(new TempTable { ID = 3, ParentID = 1, Name = "Test 3", SortOrder = 3 }); 
    temp.Add(new TempTable { ID = 4, ParentID = 2, Name = "Test 4", SortOrder = 1 }); 
    temp.Add(new TempTable { ID = 5, ParentID = 1, Name = "Test 5", SortOrder = 2 }); 
    return temp; 
} 

Cách sử dụng:

var data = GetTempData(); 
var result = data.OrderBy(d => d.SortOrder).ThenBy(d => d.ParentID); 
//Do something with result 
+0

Vui lòng xem bản chỉnh sửa của tôi, không biết liệu bạn có nhận được thông báo về chỉnh sửa không. –

4

Đây là phiên bản không đệ quy. Nó sẽ không lặp đi lặp lại nhiều lần trong danh sách ban đầu. Thay vào đó, nó duy trì một từ điển cho mối quan hệ cha-con và lưu trữ vị trí hiện tại của việc duyệt cây trước khi đặt hàng trong các điều tra viên.

public static IEnumerable<TempTable> PreorderForest(IEnumerable<TempTable> list) 
{ 
    var nodesByParent = list.GroupBy(x => x.ParentID.GetValueOrDefault(-1)) 
     .ToDictionary(xs => xs.Key, 
         xs => xs.OrderBy(x => x.SortOrder).GetEnumerator()); 

    var stack = new Stack<IEnumerator<TempTable>>(); 
    stack.Push(nodesByParent[-1]); 

    while (stack.Count > 0) 
    { 
     var nodes = stack.Peek(); 
     if (nodes.MoveNext()) 
     { 
      yield return nodes.Current; 
      IEnumerator<TempTable> children; 
      if (nodesByParent.TryGetValue(nodes.Current.ID, out children)) 
       stack.Push(children); 
     } 
     else 
      stack.Pop(); 
    } 
} 
+0

Bạn có thể giải thích tại sao bạn đã làm một phiên bản không đệ quy?Isnt ngăn xếp gây ra một chi phí unnessacary? –

+1

Kích thước ngăn xếp mặc định (gọi hàm) cho các ứng dụng .NET là 4MB @ 64-bit và 1MB @ 32-bit. Stack-Datacollection được sử dụng ở đây nằm trên heap và bị giới hạn bởi giới hạn kích thước của đối tượng .NET là 2GB. Vì vậy, đối với các hệ thống phân cấp rất sâu, một phiên bản đệ quy sẽ ném một ngoại lệ xa trước khi không đệ quy. Và ngoài ra: việc triển khai đệ quy thường chậm hơn do tính phí gọi điện. (Tuy nhiên: không đệ quy phức tạp hơn và thiếu khả năng đọc) –

3

Thật sự tôi không biết nếu nó có thể được thực hiện bằng cách truy vấn LINQ thanh lịch. Dưới đây là phiên bản đệ quy của DFS, nó được xây dựng tra cứu để tăng tốc độ tìm kiếm bằng ParentID

public static IEnumerable<TempTable> SortedList(IEnumerable<TempTable> list = null, int? ParentID = null, ILookup<int?, TempTable> lookup = null) 
{ 
    if (lookup == null) 
     lookup = list.ToLookup(x => x.ParentID, x => x); 

    foreach (var p in lookup[ParentID].OrderBy(x => x.SortOrder)) 
    { 
     yield return p; 
     foreach (var c in SortedList(lookup: lookup, ParentID: p.ID)) 
      yield return c; 
    } 
} 
+0

Không chắc liệu tra cứu có cần thiết trong môi trường khung Entity Framework không, trường hợp EF đã cung cấp tìm kiếm khóa sẵn có cho các giá trị khóa chính/ngoại? Nếu đó không phải là trường hợp trong những kịch bản là nó tốt hơn để làm một tra cứu trên một "bình thường" LINQ truy vấn hoặc nó là một điều gernal nếu bạn đang liên tục quering một INT giá trị sử dụng tra cứu? –

+0

Vâng, đó là giải pháp tổng quát hơn với danh sách, không phải bảng, không thể nói về các khóa inbuild trong khung thực thể. Vì vậy, tra cứu ở đây giúp không lặp lại trên toàn bộ danh sách cho mỗi ID. –

Các vấn đề liên quan