Sort a list and all its nested objects using LINQ
Solution 1
You need to do all three levels of sorting inside the objects that you return, like this (I'll show only the "Retail"
, the "Institutional"
needs to be sorted in the same way):
{
"Retail", organisations
.Where(x => x.Type == "Retail")
.OrderBy(x => x.Code).ThenBy(x => x.Name)
.Select(x => new Organisation {
x.Code
, x.Type
, x.Name
, Departments = x.Departmentsd.OrderBy(d => d.Code).ThenBy(d => d.Name)
.Select(d => new Department {
d.Code
, d.Name
, Employees = d.Employees.OrderBy(e => e.Code).ThenBy(e => e.Name).ToList()
})
}).ToList()
}
Since you need to select this multiple times, you may want to wrap this code in a method, and use it from several spots, like this:
private Organisation SortedOrganisation(Organisation x) {
return new Organisation {
x.Code
, x.Type
, x.Name
, Departments = x.Departmentsd.OrderBy(d => d.Code).ThenBy(d => d.Name)
.Select(d => new Department {
d.Code
, d.Name
, Employees = d.Employees.OrderBy(e => e.Code).ThenBy(e => e.Name).ToList()
})
};
}
...
var legalEntitiesCollectionByType = new Dictionary<string, ICollection<Organisation>>
{
{
"Institutional", organisations
.Where(x => x.Type == "Institutional")
.OrderBy(x => x.Code).ThenBy(x => x.Name)
.Select(SortedOrganisation)
.ToList()
},
{
"Retail", organisations
.Where(x => x.Type == "Retail")
.OrderBy(x => x.Code).ThenBy(x => x.Name)
.Select(SortedOrganisation)
.ToList()
}
};
Solution 2
I know this is an old question, but there's a simpler way of achieving the same result:
organisations = organisations.OrderBy(org =>
{
org.Departments = org.Departments
.OrderBy(dept =>
{
dept.Employees = dept.Employees
.OrderBy(employee => employee.Code)
.ThenBy(employee=>employee.Name);
return dept.Code;
})
.ThenBy(dept=>dept.Name);
return org.Code;
})
.ThenBy(org=>org.Name);
Solution 3
If Employees
ever need to be sorted by code and name then you can make that property a SortedList<>
.
public class Department
{
...
public SortedList<Tuple<int, string>, Employee> Employees { get; set; }
}
Prior to .NET 4 you could use KeyValuePair
instead of Tuple
.
When creating Employees
object you'd need to provide IComparer
object for sorted list's key.
Employees = new SortedList<Tuple<int, string>, Employee>(new EmployeeKeyComparer());
where EmployeeKeyComparer
could be defined as
public class EmployeeKeyComparer : IComparer<Tuple<int, string>>
{
public int Compare(Tuple<int, string> x, Tuple<int, string> y)
{
if (x.First == y.First)
return StringComparer.Ordinal.Compare(x.Second, y.Second);
else
return x.First.CompareTo(y.First);
}
}
Solution 4
You can sort before :
organisations.ToList().ForEach(o => o.Departments = o.Departments.OrderBy(d => d.Code).ToList());
organisations.SelectMany(o => o.Departments).ToList().ForEach(d => d.Employees = d.Employees.OrderBy(e => e.Name).ToList());
And then use the list already sorted
var legalEntitiesCollectionByType = new Dictionary<string, ICollection<Organisation>>
{
{
"Institutional", organisations
.Where(x => x.Type == "Institutional")
.ToList()
},
{
"Retail", organisations
.Where(x => x.Type == "Retail")
.ToList()
}
};
NB : the sort is not in place, you can achieve this using a comparer
organisations.ToList().ForEach(o => o.Departments.Sort(CreateCustomComparison));
organisations.SelectMany(o => o.Departments).ToList().ForEach(d => d.Employees.Sort(CreateCustomComparison));
haroonxml
Updated on July 17, 2022Comments
-
haroonxml almost 2 years
There is an organisation with several departments and each department has a few employees.
I have created the following object model:
public class Organisation { public int Code { get; set; } public string Type { get; set; } public string Name { get; set; } public List<Department> Departments { get; set; } } public class Department { public int Code { get; set; } public string Name { get; set; } public List<Employee> Employees { get; set; } } public class Employee { public int Code { get; set; } public string Name { get; set; } }
Now, I have a list of these organisations and using LINQ, I would like to sort/order the output as follows:
1) Organisations: Ordered by Code and Name
2) Departments: Ordered by Code and Name
3) Employees: Ordered by Code and Name
Below is some test data that I have populated:
var britishTelecomLtd = new Organisation { Code = 8, Name = "British Telecom Ltd", Type = "Institutional", Departments = new List<Department> { new Department { Code = 6, Name = "Finance", Employees = new List<Employee> { new Employee { Code = 5, Name = "Peter" }, new Employee { Code = 2, Name = "James" }, new Employee { Code = 6, Name = "Andrew" } } }, new Department { Code = 5, Name = "Accounts", Employees = new List<Employee> { new Employee { Code = 15, Name = "Jane" }, new Employee { Code = 22, Name = "John" }, new Employee { Code = 16, Name = "Mark" } } } } }; var virginMediaLtd = new Organisation { Code = 5, Name = "Virgin Media Ltd", Type = "Institutional", Departments = new List<Department> { new Department { Code = 6, Name = "Sales", Employees = new List<Employee> { new Employee { Code = 5, Name = "Peter" }, new Employee { Code = 2, Name = "James" }, new Employee { Code = 6, Name = "Andrew" } } }, new Department { Code = 5, Name = "Support", Employees = new List<Employee> { new Employee { Code = 15, Name = "Jane" }, new Employee { Code = 22, Name = "John" }, new Employee { Code = 16, Name = "Mark" } } } } }; var pcWorldLtd = new Organisation { Code = 18, Name = "PC World Ltd", Type = "Retail", Departments = new List<Department> { new Department { Code = 6, Name = "Marketing", Employees = new List<Employee> { new Employee { Code = 15, Name = "Jane" }, new Employee { Code = 22, Name = "John" }, new Employee { Code = 16, Name = "Mark" } } }, new Department { Code = 5, Name = "Customer Services", Employees = new List<Employee> { new Employee { Code = 5, Name = "Kelly" }, new Employee { Code = 2, Name = "Jenny" }, new Employee { Code = 6, Name = "Tricia" } } } } }; var blueCatLtd = new Organisation { Code = 3, Name = "Blue Cat Music Ltd", Type = "Retail", Departments = new List<Department> { new Department { Code = 6, Name = "Sales", Employees = new List<Employee> { new Employee { Code = 5, Name = "Peter" }, new Employee { Code = 2, Name = "James" }, new Employee { Code = 6, Name = "Andrew" } } }, new Department { Code = 5, Name = "Warehouse", Employees = new List<Employee> { new Employee { Code = 5, Name = "Andy" }, new Employee { Code = 2, Name = "Robert" }, new Employee { Code = 6, Name = "Dave" } } } } }; var organisations = new List<Organisation> { britishTelecomLtd, virginMediaLtd, pcWorldLtd, blueCatLtd };
Here I am adding the data to a dictionary:
var legalEntitiesCollectionByType = new Dictionary<string, ICollection<Organisation>> { { "Institutional", organisations .Where(x => x.Type == "Institutional") .OrderBy(x => x.Code).ThenBy(x => x.Name) .ToList() }, { "Retail", organisations .Where(x => x.Type == "Retail") .OrderBy(x => x.Code).ThenBy(x => x.Name) .ToList() } };
doing it this way the sorting only happens at the Organisation level and not on the department or the employee level.
My question is, how can I achieve sorting on all 3 levels within the object hierarchy while populating the dictionary above?
Cheers
-
haroonxml over 10 yearsYour answer makes sense and I am working on your suggested solution but will get back to you shortly. Cheers
-
haroonxml over 10 yearsthanks for your suggestion but if i am using linq orderby i shouldnt have the need to implement an IComparer.
-
haroonxml over 10 yearswhy use comparer when you have all that functionality implemented within linq ? . Linq's orderby does the job pretty well.
-
Dialecticus over 10 years@HaroonEmmanuel, if the list is always required to be sorted then it makes sense to sort it only once, rather than sort it with linq every time it's used.
-
Toto over 10 yearsBecause 'orderby' does not order 'in place', it generates a new IEnumerable and I need to assign it. 'Sort' method, on the contrary, does the job 'in place'.
-
haroonxml over 10 yearsThese guys are same something else stackoverflow.com/questions/3378603/linq-orderby-vs-icomparer
-
mhlz about 9 yearsPlease provide more information in your answer. Why does this code work better/different than the accepted answer?
-
Tom Baxter over 7 yearsThis is the best answer, in my opinion. There is a typo -- Departmets should be Departments and each of the "...ThenBy(...) needs to end with a .ToList();
-
Hellraiser over 7 yearsThanks for pointing out the typo. As for the .ToList() I don't think it's really necessary. I'll check and let you know.