In this article, we’ll explore flattening nested collections in C#. We’ll examine different approaches for handling from simple to complex data structures, offering practical guidance to effectively flatten nested collections within our C# applications.
Let’s get started.
Importance of Flattening Nested Collections
Nested collections can introduce unnecessary complexity, making it challenging to work with the data. Flattening these collections can simplify the structure, making it easier to understand and manipulate.
Also, organizing related records into a single, cohesive structure can lead to more efficient data storage and processing.
Moreover, by restructuring nested collections into a more linear format, we represent the data in a tabular format, which might be necessary for input in a database or integration with external systems.
Use of Select() Method to Flatten Specific Property
We will use as an example the department structure along with employees as nested collections. Let’s inspect the Department
record:
public record Department(string Name, IEnumerable<Employee> Employees);
We include only two properties, a string
for its Name
and the nested collection of Employee
objects. The constructor defined within the record syntax we use is a primary constructor. In C#, a primary constructor is a concise way to define a constructor that initializes the properties of a record
. Similarly, we design the Employee
record:
public record Employee(string Name, string Email);
For simplicity, here we include only the employee’s name and an email. Next, we initialize a Department
entity:
var department = new Department( "Human Resources", [ new("Alice", "[email protected]"), new("Bob", "[email protected]" ) ] );
The Department
record is the parent entity, while the Employee
record is the child. One Department
consists of one or more Employee
entities. Next, we will create the entity we are expecting to receive as a result:
public record DepartmentFlattened(string DepartmentName, string EmployeeName, string EmployeeEmail);
This object includes all properties of every other object at the same level. Now, let’s build a simple method for flattening the parent Department
object:
public static IEnumerable<DepartmentFlattened> FlattenWithSelect(Department department) { return department?.Employees?.Select(employee => new DepartmentFlattened( department.Name, employee.Name, employee.Email ) ); }
Our method FlattenWithSelect()
takes as input a Department
object and returns a collection of DepartmentFlattened
object. We use the LINQ syntax and the Select()
method to iterate over the Employee
object collection within the provided Department
.
For each Employee
, we construct a new DepartmentFlattened
object that includes every property value such as the DepartmentName
, EmployeeName
, and EmployeeEmail
. Consequently, we transform the entity into a flattened format by projecting these selected properties into a sequence of the objects we desire.
Finally, we can flatten our nested department
variable with the FlattenWithSelect()
method:
{ DepartmentName = Human Resources, EmployeeName = Alice, EmployeeEmail = [email protected] } { DepartmentName = Human Resources, EmployeeName = Bob, EmployeeEmail = [email protected] }
We can verify that the outcome includes every combination of the objects. We have a list of two Employee
objects from one Department
. So, the flattened objects are two in total, containing all the available properties.
Let’s see a different syntax now:
public static IEnumerable<DepartmentFlattened> FlattenWithQueryExpression(Department department) { return from employee in department?.Employees ?? [] select new DepartmentFlattened(department.Name, employee.Name, employee.Email); }
This time, we use query expression, a SQL-like syntax for composing queries over collections. In brief, it allows us to express operations in a more readable way.
As we expect, the method FlattenWithQueryExpression()
method performs the same.
Flatten Multiple Nested Collections With SelectMany()
Now, we will make the scenario a little bit more complex. We will add one more nested collection property to the Department
object, called Project
:
public record Project(string Title, int Budget);
The Project
record consists of a string
property name Title
and an int
property name Budget
, for demonstration purposes. Also, we extend the result object:
public record DepartmentFlattenedMultipleCollections( string DepartmentName, string EmployeeName, string EmployeeEmail, string ProjectTitle, int ProjectBudget );
Here, we add the string
property ProjectTitle
and int
property ProjectBudget
in order to include every combination. Now, we can build the new Department
object:
var department = new Department( "Engineering", [ new("Charlie", "[email protected]"), new("David", "[email protected]") ], [ new("Product Development", 50000), new("Research", 20000) ] );
We add two Project
object and two Employee
object instances in the Department
entity. Next, we can define a new method to flatten the Department
object:
public static IEnumerable<DepartmentFlattenedMultipleCollections>? FlattenWithSelectMany(Department department) { return department?.Employees?.SelectMany( employee => department.Projects.Select(project => new DepartmentFlattenedMultipleCollections( department.Name, employee.Name, employee.Email, project.Title, project.Budget ) ) ); }
In our FlattenWithSelectMany()
method, we aim to simplify the complex structure of a Department
object by using LINQ’s SelectMany()
method. For every Employee
in the Employees
collection, we iterate over all Projects
in the Projects
collection. Consequently, this allows us to construct a new object for each combination of Employee
and Project
. Within each object, we include all the properties that are present.
When we apply to a collection of collections, the SelectMany()
method projects each element of the outer collection to an inner collection. It is similar to a cross-join operation in SQL.
Let’s see the outcome:
{ DepartmentName = Engineering, EmployeeName = Charlie, EmployeeEmail = [email protected], ProjectTitle = Product Development, Budget = 50000 } { DepartmentName = Engineering, EmployeeName = Charlie, EmployeeEmail = [email protected], ProjectTitle = Research, Budget = 20000 } { DepartmentName = Engineering, EmployeeName = David, EmployeeEmail = [email protected], ProjectTitle = Product Development, Budget = 50000 } { DepartmentName = Engineering, EmployeeName = David, EmployeeEmail = [email protected], ProjectTitle = Research, Budget = 20000 }
Now, we can see that we have all possible combinations between collection properties in the result.
How to Flatten Two-Level Nested Collection
Now we will proceed with flattening an object that has a nested collection inside another one. Let’s enhance the Employee
object with the Certification
property:
public record Employee( string Name, string Email, IEnumerable<Certification>? Certifications = null );
The Employee
object includes a list of Certification
objects. Likewise, our next modification is about the result object we want as the outcome:
public record DepartmentFlattenedComplex( string DepartmentName, string EmployeeName, string EmployeeEmail, string CertificationTitle, DateOnly CertificationIssueDate );
In contrast to the previous similar object, we now have the string
property CertificationTitle
and the DateOnly
property CertificationIssueDate
. Respectively, we implement the method that handles this case:
public static IEnumerable<DepartmentFlattenedComplex>? FlattenComplexWithSelectMany(Department department) { return department?.Employees?.SelectMany( employee => employee.Certifications?.Select(certification => new DepartmentFlattenedComplex( department.Name, employee.Name, employee.Email, certification.Title, certification.IssueDate ) ) ); }
Initially, we navigate through the Employee
object collection within the provided Department
. For each Employee
, we utilize the SelectMany()
method to go through the Certification
collection of that Employee
. Lastly, within the inner Select()
method, we project each Certification
into the final object containing all properties to create a flat structure.
Now let’s examine the result:
{ DepartmentName = Engineering, EmployeeName = Charlie, EmployeeEmail = [email protected], Title = MCSD, IssueDate = 4/15/2020 } { DepartmentName = Engineering, EmployeeName = Charlie, EmployeeEmail = [email protected], Title = PMP, IssueDate = 3/1/2022 } { DepartmentName = Engineering, EmployeeName = David, EmployeeEmail = [email protected], Title = Solutions Architect, IssueDate = 7/5/2017 } { DepartmentName = Engineering, EmployeeName = David, EmployeeEmail = [email protected], Title = CCNA, IssueDate = 10/1/2019 }
We now project every Employee
and Certification
at the level of the parent Department
object.
To summarize, we can effectively flatten any nested collection hierarchy by utilizing multiple SelectMany()
operations. With each SelectMany()
call, we iterate over a nested collection within the parent collection, transforming it into a flat structure. Thus, by chaining multiple SelectMany()
calls, each targeting a different nested collection level, we progressively flatten the entire hierarchy into a single sequence of desired elements.
It is worth mentioning that apart from LINQ usage, we can also accomplish object flattening using the AutoMapper (or similar) libraries. We can set up rules that instruct the AutoMapper
mechanism how to convert from a complex, nested object and place it into a simpler, flat object.
Conclusion
In this article, we showcase solutions for flattening nested collections in objects. We examined from simple to complex cases, also demonstrating different syntax approaches.