Projection in LINQ C# with the SelectMany operator
February 9, 2016 1 Comment
The SelectMany operator creates a one-to-many output projection sequence over an input sequence. SelectMany will return 0 or more output elements for every input element. Let’s see an example.
Data source:
string[] bands = { "ACDC", "Queen", "Aerosmith", "Iron Maiden", "Megadeth", "Metallica", "Cream", "Oasis", "Abba", "Blur", "Chic", "Eurythmics", "Genesis", "INXS", "Midnight Oil", "Kent", "Madness", "Manic Street Preachers" , "Noir Desir", "The Offspring", "Pink Floyd", "Rammstein", "Red Hot Chili Peppers", "Tears for Fears" , "Deep Purple", "KISS"};
Consider the following code:
IEnumerable<char> characters = bands.SelectMany(b => b.ToArray()); foreach (char item in characters) { Console.WriteLine(item); }
We provide a string input to SelectMany and apply the ToArray extension to it which will be an array of chars. So we have a single input – a string – and a collection of many elements as output – the sequence of characters from each band name.
Here’s a small part of the output:
This is an interesting but probably not too useful application of SelectMany. We can do more interesting projections with it. Consider the following classes:
public class Singer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
public class Concert { public int SingerId { get; set; } public int ConcertCount { get; set; } public int Year { get; set; } }
We have the following data in the data collection:
public static IEnumerable<Singer> GetSingers() { return new List<Singer>() { new Singer(){Id = 1, FirstName = "Freddie", LastName = "Mercury"} , new Singer(){Id = 2, FirstName = "Elvis", LastName = "Presley"} , new Singer(){Id = 3, FirstName = "Chuck", LastName = "Berry"} , new Singer(){Id = 4, FirstName = "Ray", LastName = "Charles"} , new Singer(){Id = 5, FirstName = "David", LastName = "Bowie"} }; }
public static IEnumerable<Concert> GetConcerts() { return new List<Concert>() { new Concert(){SingerId = 1, ConcertCount = 53, Year = 1979} , new Concert(){SingerId = 1, ConcertCount = 74, Year = 1980} , new Concert(){SingerId = 1, ConcertCount = 38, Year = 1981} , new Concert(){SingerId = 2, ConcertCount = 43, Year = 1970} , new Concert(){SingerId = 2, ConcertCount = 64, Year = 1968} , new Concert(){SingerId = 3, ConcertCount = 32, Year = 1960} , new Concert(){SingerId = 3, ConcertCount = 51, Year = 1961} , new Concert(){SingerId = 3, ConcertCount = 95, Year = 1962} , new Concert(){SingerId = 4, ConcertCount = 42, Year = 1950} , new Concert(){SingerId = 4, ConcertCount = 12, Year = 1951} , new Concert(){SingerId = 5, ConcertCount = 53, Year = 1983} }; }
We can create a join with the SelectMany operator to see how many concerts each singer gave per year. Consider the following query:
IEnumerable<Singer> singers = GetSingers(); IEnumerable<Concert> concerts = GetConcerts(); var singerConcerts = singers.SelectMany(s => concerts.Where(c => c.SingerId == s.Id) .Select(c => new {Year = c.Year, ConcertCount = c.ConcertCount, Name = string.Concat(s.FirstName, " ", s.LastName) })); foreach (var item in singerConcerts) { Console.WriteLine(string.Concat(item.Name, ", ", item.Year, ", ", item.ConcertCount)); }
First every Singer object is passed into the lambda expression of SelectMany, which will be ‘s’ parameter. In the lambda expression we retrieve every Concert of each singer using the Where extension. We’re in effect joining the two data collections on the Id/SingerId properties. Finally we construct an anonymous object collection with the Select operator. Here’s the output:
You can project the results into “real” objects as opposed to anonymous ones. We have the following object:
public class SingerConcert { public string SingerName { get; set; } public int Year { get; set; } public int ConcertCount { get; set; } }
Our query can be modified as follows to build a sequence of SingerConcert objects:
IEnumerable<Singer> singers = DemoCollections.GetSingers(); IEnumerable<Concert> concerts = DemoCollections.GetConcerts(); IEnumerable<SingerConcert> singerConcerts = singers.SelectMany(s => concerts.Where(c => c.SingerId == s.Id) .Select(c => new SingerConcert() { Year = c.Year, ConcertCount = c.ConcertCount, SingerName = string.Concat(s.FirstName, " ", s.LastName) })); foreach (SingerConcert item in singerConcerts) { Console.WriteLine(string.Concat(item.SingerName, ", ", item.Year, ", ", item.ConcertCount)); }
…which provides the same output as the anonymous class example above.
You can view all LINQ-related posts on this blog here.
I love these excercises. Its really helping me practice and see how to build up my queries. In this example I struggled to understand the query itself till i found the DotNetPearls page on the join which suggested that join queries are probably easier to read and maintain in the comprehension syntax http://www.dotnetperls.com/join
so i re-wrote my copy of the query:
var query = from s in singers
join c in concerts on s.Id equals c.SingerId
select new { Year = c.Year, ConcertCount = c.ConcertCount, Name = string.Concat(s.FirstName, ” “, s.LastName) };
foreach (var result in query)
Console.WriteLine(“{0}, {1}, {2}”, result.Name, result.Year, result.ConcertCount);