Reputation: 703
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)
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
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
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
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
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
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
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
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
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