.NET LINQ Projection
Introduction
Projection is one of the most powerful features in LINQ (Language Integrated Query). When we talk about projection in LINQ, we're referring to the process of transforming data from one form to another. It's similar to how a movie projector takes a film and projects it onto a screen in a different format.
In LINQ, projection allows you to:
- Extract specific properties from objects
- Transform data into entirely new types
- Reshape your data structures to fit your needs
- Create anonymous types on the fly
Projection operations in LINQ primarily use the Select
and SelectMany
methods. These methods allow you to define exactly how you want your output data to be structured, regardless of the input structure.
The Select Method
The Select
method is the basic projection operator in LINQ. It transforms each element from the source sequence into a new form.
Basic Syntax
var result = collection.Select(item => expression);
Where:
collection
is your source dataitem
represents each element in the collectionexpression
defines how to transform each element
Simple Select Examples
Let's start with a basic example using a list of numbers:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// Project each number into its squared value
var squaredNumbers = numbers.Select(n => n * n);
// Display results
Console.WriteLine("Original numbers:");
foreach (var num in numbers)
Console.WriteLine(num);
Console.WriteLine("\nSquared numbers:");
foreach (var num in squaredNumbers)
Console.WriteLine(num);
Output:
Original numbers:
1
2
3
4
5
Squared numbers:
1
4
9
16
25
Projecting Object Properties
Consider a more realistic scenario with a class:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
// Create a list of products
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Price = 1200.00m, Category = "Electronics" },
new Product { Id = 2, Name = "Desk Chair", Price = 250.50m, Category = "Furniture" },
new Product { Id = 3, Name = "Coffee Maker", Price = 89.99m, Category = "Kitchen" },
new Product { Id = 4, Name = "Monitor", Price = 350.75m, Category = "Electronics" }
};
// Project to get only the names
var productNames = products.Select(p => p.Name);
Console.WriteLine("Product Names:");
foreach (var name in productNames)
Console.WriteLine(name);
Output:
Product Names:
Laptop
Desk Chair
Coffee Maker
Monitor
Creating Anonymous Types
LINQ projection allows us to create new anonymous types on the fly:
// Project to a new anonymous type with selected properties
var productInfo = products.Select(p => new {
ProductName = p.Name,
ProductPrice = p.Price
});
Console.WriteLine("\nProduct Information:");
foreach (var item in productInfo)
Console.WriteLine($"{item.ProductName}: ${item.ProductPrice}");
Output:
Product Information:
Laptop: $1200.00
Desk Chair: $250.50
Coffee Maker: $89.99
Monitor: $350.75
Using Index in Select
Sometimes you might need to know the position of each element in the collection:
// Using the index in the projection
var numberedProducts = products.Select((p, index) =>
new { Number = index + 1, Name = p.Name });
Console.WriteLine("\nNumbered Products:");
foreach (var item in numberedProducts)
Console.WriteLine($"{item.Number}. {item.Name}");
Output:
Numbered Products:
1. Laptop
2. Desk Chair
3. Coffee Maker
4. Monitor
The SelectMany Method
While Select
produces one output element for each input element, SelectMany
allows you to flatten nested collections into a single collection. It's extremely useful when working with hierarchical or nested data.
Basic Syntax
var result = collection.SelectMany(item => item.NestedCollection);
SelectMany Examples
Let's look at a scenario with nested collections:
public class Student
{
public string Name { get; set; }
public List<string> Courses { get; set; }
}
// Create a list of students
List<Student> students = new List<Student>
{
new Student {
Name = "Alice",
Courses = new List<string> { "Math", "Physics", "Chemistry" }
},
new Student {
Name = "Bob",
Courses = new List<string> { "Biology", "English", "Art" }
},
new Student {
Name = "Charlie",
Courses = new List<string> { "Computer Science", "Statistics" }
}
};
// Using Select (creates nested lists)
var nestedCourses = students.Select(s => s.Courses);
// This produces IEnumerable<List<string>> - a collection of lists
// Using SelectMany (flattens to a single list)
var allCourses = students.SelectMany(s => s.Courses);
// This produces IEnumerable<string> - a flattened collection of strings
Console.WriteLine("All courses (flattened with SelectMany):");
foreach (var course in allCourses)
Console.WriteLine(course);
Output:
All courses (flattened with SelectMany):
Math
Physics
Chemistry
Biology
English
Art
Computer Science
Statistics
Combining Elements with SelectMany
We can also use SelectMany
to combine elements from the outer and inner collections:
// Combining student names with their courses
var studentCourses = students.SelectMany(
s => s.Courses,
(student, course) => new { StudentName = student.Name, CourseName = course }
);
Console.WriteLine("\nStudent Course Enrollments:");
foreach (var sc in studentCourses)
Console.WriteLine($"{sc.StudentName} is taking {sc.CourseName}");
Output:
Student Course Enrollments:
Alice is taking Math
Alice is taking Physics
Alice is taking Chemistry
Bob is taking Biology
Bob is taking English
Bob is taking Art
Charlie is taking Computer Science
Charlie is taking Statistics
Real-World Applications
Let's explore how projection is used in real-world scenarios.
Example 1: Data Transfer Objects (DTOs)
In web applications, we often need to convert database entities to DTOs (Data Transfer Objects) before sending them to the client:
public class CustomerEntity
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public DateTime DateOfBirth { get; set; }
public bool IsSubscribedToNewsletter { get; set; }
public string InternalNotes { get; set; }
}
public class CustomerDto
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
}
// Simulate database query
List<CustomerEntity> customers = GetCustomersFromDatabase();
// Project to DTOs for API response
var customerDtos = customers.Select(c => new CustomerDto
{
Id = c.Id,
FullName = $"{c.FirstName} {c.LastName}",
Email = c.Email
});
Example 2: Data Analysis and Reporting
When generating reports, you often need to calculate aggregates or reshape data:
public class SalesRecord
{
public DateTime Date { get; set; }
public string Product { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
// Simulate sales data
List<SalesRecord> sales = GetSalesData();
// Generate daily sales report
var dailySalesReport = sales
.GroupBy(s => s.Date.Date)
.Select(g => new {
Date = g.Key,
TotalSales = g.Sum(s => s.Quantity * s.UnitPrice),
ProductsSold = g.Count(),
TopProduct = g.OrderByDescending(s => s.Quantity).First().Product
});
// Display report
foreach (var day in dailySalesReport)
{
Console.WriteLine($"Date: {day.Date.ToShortDateString()}");
Console.WriteLine($"Total Sales: ${day.TotalSales}");
Console.WriteLine($"Products Sold: {day.ProductsSold}");
Console.WriteLine($"Top Selling Product: {day.TopProduct}");
Console.WriteLine();
}
Example 3: Working with JSON Data
When parsing JSON data, you often need to transform and extract specific information:
using System.Text.Json;
string jsonResponse = @"{
""results"": [
{
""id"": 1,
""name"": ""John"",
""details"": {
""age"": 32,
""location"": ""New York""
}
},
{
""id"": 2,
""name"": ""Sarah"",
""details"": {
""age"": 28,
""location"": ""Boston""
}
}
]
}";
using System.Text.Json.Nodes;
var jsonObj = JsonNode.Parse(jsonResponse);
var results = jsonObj["results"].AsArray();
// Project JSON data to strongly typed objects
var users = results.Select(r => new {
Id = (int)r["id"],
Name = (string)r["name"],
Age = (int)r["details"]["age"],
Location = (string)r["details"]["location"]
});
foreach (var user in users)
{
Console.WriteLine($"User: {user.Name}, Age: {user.Age}, Location: {user.Location}");
}
Output:
User: John, Age: 32, Location: New York
User: Sarah, Age: 28, Location: Boston
Performance Considerations
When using projection, keep in mind:
-
Deferred Execution: LINQ projection operations use deferred execution, meaning they aren't executed until you iterate through the results.
-
Memory Usage: Creating new objects for each element can consume memory. For large datasets, consider using streaming approaches.
-
Select vs. SelectMany: Choose the right operator for your needs.
SelectMany
flattens collections but may be less efficient for simple projections. -
Avoid Multiple Enumerations: Store projected results in a collection (like a
List<T>
) if you need to iterate through them multiple times.
// This will enumerate twice
var projection = numbers.Select(n => ExpensiveCalculation(n));
foreach (var p in projection) { /* first use */ }
foreach (var p in projection) { /* second use - recalculates everything! */ }
// Better approach
var storedProjection = numbers.Select(n => ExpensiveCalculation(n)).ToList();
foreach (var p in storedProjection) { /* first use */ }
foreach (var p in storedProjection) { /* second use - uses cached results */ }
Summary
LINQ projection operations are essential tools in the .NET developer's toolkit. They allow you to:
- Transform data from one form to another using
Select
- Flatten nested collections with
SelectMany
- Create new types on the fly, including anonymous types
- Extract only the data you need, reducing memory usage
By mastering projection, you can write cleaner, more efficient code that shapes data exactly as needed for your application.
Additional Resources
Exercises
-
Create a list of integers and use LINQ projection to generate the square root of each number.
-
Given a list of words, project each word to a new object containing the word and its length.
-
Given a list of
Person
objects withName
andAddress
properties (whereAddress
is another object withCity
andCountry
properties), use projection to create a list of strings in the format "Name lives in City, Country". -
Create a nested list structure (like a list of lists) and use
SelectMany
to flatten it into a single list. -
Use projection to transform a list of dates into a list of strings showing the day of week (e.g., "Monday", "Tuesday").
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)