Projection in LINQ C# with the SelectMany operator
May 16, 2014 Leave a 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.