crunchy
crunchy

Reputation: 703

Order list by parent and child items

I have a list of products that have to be ordered by parent then all children of that parent, then next parent, etc.

Product One
    Child One
    Child Two
Product Two
    Child One

These products are all in one table with a parent id field, the child products have a parent id but the parent items can have a null parent (indicating that product is a top level product)

Sample table

I was thinking something like the following:

var list = GetProductList();
var newList = new List<ProductDTO>();

var parents = from p in list
    where p.Parent == null
    select p.Id;


foreach (var parent in parents)
{
    var tempList = new List<ProductDTO>();
    tempList.Add(list.FirstOrDefault(x => x.Id == parent));
    tempList.AddRange(list.Where(x => x.Parent == parent).OrderBy(x => x.Id));
    newList.AddRange(tempList);
}

Any suggestions on how I would do this a little cleaner?

Upvotes: 11

Views: 13317

Answers (8)

and.ryx
and.ryx

Reputation: 753

I'd the same problem but I resolved it in another way, without parent level limitations, using recursion principles.

By editing examples wrote by @loopedcode, there are some editing for Product class for illustrative purpose.

public class ProductDTO
 {
     public int Id { get; set; }
     public string Name { get; set; }
     public int? Parent { get; set; }
     public override string ToString() {
         return "[" + Id + " - " + Name + "]";
     }
     public int Deep { get; set; }
 }

Let's writing a recursive function to build an ordered list, from a given product list

public static List<ProductDTO> GetOrderedList(List<ProductDTO> Items, int? root, int deep) {
    var result = new List<ProductDTO>();
    foreach(var item in Items.Where(x => x.Parent == root).OrderBy(x => x.Id)) {
        item.Deep = deep;
        result.Add(item);
        result.AddRange(GetOrderedList(Items, item.Id, deep + 1));
    }
    return result;
}

Suppose that client code give us a totally unordered list, just by calling recursive function for root items it build ordered list

public static void Main()
{
    Console.WriteLine("Hello World");

     var products = new List<ProductDTO>();
     products.Add(new ProductDTO() { Id = 7, Name = "Child One", Parent = 3 });
     products.Add(new ProductDTO() { Id = 10, Name = "Child One", Parent = 4 });
     products.Add(new ProductDTO() { Id = 8, Name = "Child Three", Parent = 1 });
     products.Add(new ProductDTO() { Id = 9, Name = "Child Two", Parent = 2 });
     products.Add(new ProductDTO() { Id = 1, Name = "Product One" });
     products.Add(new ProductDTO() { Id = 2, Name = "Product Two" });
     products.Add(new ProductDTO() { Id = 6, Name = "Child Two", Parent = 1 });
     products.Add(new ProductDTO() { Id = 4, Name = "Child One", Parent = 1 });
     products.Add(new ProductDTO() { Id = 5, Name = "Child One", Parent = 2 });
     products.Add(new ProductDTO() { Id = 3, Name = "Product Three" });
    
    var ordered = GetOrderedList(products, null, 0);
    foreach(var item in ordered) {
        for(int i = 0; i < item.Deep; i++) {
            Console.Write("\t");
        }
        Console.Write(item.ToString());
        Console.Write("\n");
    }
    
}

Here's a print of output results, solution could be try here

Hello World
[1 - Product One]
    [4 - Child One]
        [10 - Child One]
    [6 - Child Two]
    [8 - Child Three]
[2 - Product Two]
    [5 - Child One]
    [9 - Child Two]
[3 - Product Three]
    [7 - Child One]

Upvotes: 0

Avtandil Kavrelishvili
Avtandil Kavrelishvili

Reputation: 1757

It's very easy and complicated way, In 'res' variable you'll see this kind of situation - parent1 > child.1.1 > child.1.2 > parent2 > child.2.1 > child.2.2 > child.2.3 > parent3:

        //items is a list of unsorted objects
       
        var res = items.OrderBy(x =>
        {
            if (x.ParentId == null)
                return x.Id;
            else
                return x.ParentId;
        }).ThenBy(t => t.Id);

Upvotes: 0

Lev
Lev

Reputation: 434

you could try something like that. Assuming parent is a nullable:

var sorted = list.OrderBy(x => x.parent ?? x.id).ThenBy(x=>x.id);

if its a string:

            var sorted = list.OrderBy(x =>
            {
                if (x.parent == "null")
                    return x.id;
                else
                    return Convert.ToInt32(x.parent);
            }).ThenBy(x => x.id);

Upvotes: 7

MAC
MAC

Reputation: 151

Maybe using linq this way:

var groupList = from c in products
                where c.Parent.HasValue
                group c by c.Parent into r
                join p in products on r.Key equals p.Id
                orderby p.Name
                select new { Parent = p, Children = r };

Upvotes: 0

binard
binard

Reputation: 1794

I don't know if it's more cleaner but if you want a unique linq instruction you can try this :

var result = GetProductList().Where(p => p.Parent == null)
                             .SelectMany(p => list.Where(c => c.Parent == p.Id)
                                                  .Concat(new[] { p })
                                                  .OrderBy(c => c.Parent.HasValue)
                                                  .ThenBy(c => c.Id)
                                                  .ToList())
                             .ToList();

Upvotes: 1

hmMT
hmMT

Reputation: 81

You could do it this way:

list.ForEach(item => 
            {
                if (item.Parent == null)
                {
                    orderedList.Add(item);
                    orderedList.AddRange(list.Where(child => child.Parent == item.Id));
                }
            });

Upvotes: 5

loopedcode
loopedcode

Reputation: 4893

Given "Parent" is nullable property (assuming nullable int here). Following should give you parent-child related ordered list:

 public class ProductDTO
 {
     public int Id { get; set; }
     public string Name { get; set; }
     public int? Parent { get; set; }
 }

 var products = new List<ProductDTO>();
 products.Add(new ProductDTO() { Id = 1, Name = "Product One" });
 products.Add(new ProductDTO() { Id = 2, Name = "Product Two" });
 products.Add(new ProductDTO() { Id = 3, Name = "Product Three" });
 products.Add(new ProductDTO() { Id = 4, Name = "Child One", Parent=1 });
 products.Add(new ProductDTO() { Id = 5, Name = "Child Two", Parent = 2 });
 products.Add(new ProductDTO() { Id = 6, Name = "Child One", Parent = 1 });

var ordered = products
                .Where(p => p.Parent == null)
                .OrderBy(p=> p.Id)
                .Select(p => products
                    .Where(c => c.Parent == p.Id)
                    .OrderBy(c => c.Id))
                .ToList();

Upvotes: 4

Oscar Bralo
Oscar Bralo

Reputation: 1907

You should add a ParentId to the Product One and Product two, and the, will be easier to solve it. If Parent One is 1, and Parent Two is 2, only do this

var result = parents.OrderBy(x => x.Parent).ThenBy(x => x.Id);

Upvotes: 0

Related Questions