Different ways of using SelectMany()

Select many allows you to select a property from your query source that is an IEnumerable<T> collection, but instead of returning a collection of collections (IEnumerable<IEnumerable<T>>) it will flatten the collections into a single collection.

Here's an example that you can run to demonstrate the differences between Select and SelectMany:

//set up some data for our example
var tuple1 = new { Name = "Tuple1", Values = new int [] { 1, 2, 3 } };
var tuple2 = new { Name = "Tuple2", Values = new int [] { 4, 5, 6 } };
var tuple3 = new { Name = "Tuple3", Values = new int [] { 7, 8, 9 } };

//put the tuples into a collection
var tuples = new [] { tuple1, tuple2, tuple3 };

//"tupleValues" is an IEnumerable<IEnumerable<int>> that contains { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }
var tupleValues = tuples.Select(t => t.Values);

//"tupleSelectManyValues" is an IEnumerable<int> that contains { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
var tupleSelectManyValues = tuples.SelectMany(t => t.Values);

By using SelectMany you make it easier to query values within child collections.


SelectMany basically flattens and processes hierarchical data, and has two main forms

(for the purposes of examples, see this initial code)

class TestObj
{
    public string Name { get; set; }
    public List<string> Items { get; set; }
}

var hierarchicalCollection = new List<TestObj>();

hierarchicalCollection.Add(new TestObj() 
    {Items = new List<string>()
        {"testObj1-Item1", "testObj1-Item2"}, Name="t1"});
hierarchicalCollection.Add(new TestObj() 
    {Items = new List<string>()
        {"testObj2-Item1", "testObj2-Item2"}, Name="t2"});

option 1) creates a collection from a collection of collections (essentially flattening hierarchical data)

IEnumerable<string> flattenedCollection = 
    hierarchicalCollection.SelectMany(t => t.Items);

The result is:

"testObj1-Item1"
"testObj1-Item2"
"testObj2-Item1"
"testObj2-Item2"

option 2) creates a collection from a collection of collections, and then processes each item of the new collection via a reference to the original parent

IEnumerable<string> flattenedModifiedCollection = 
    hierarchicalCollection.SelectMany
        (t => t.Items, (t, i) => t.Name + " : " + i);

the result is:

"t1 : testObj1-Item1"
"t1 : testObj1-Item2"
"t2 : testObj2-Item1"
"t2 : testObj2-Item2"

each of the above useages has a variant, where the index of the item being processed is available to the transformation functions.


There are several overloads to SelectMany. One of them allows you to keep track of any relationship between parent and children while traversing the hierarchy.

Example: suppose you have the following structure: League -> Teams -> Player

You can easily return a flat collection of players. However you may loose any reference to the team a player is part of.

Fortunately there is an overload for such purpose:

var teamsAndTheirLeagues = 
         from helper in leagues.SelectMany
               ( l => l.Teams
                 , ( league, team ) => new { league, team } )
                      where helper.team.Players.Count > 2 
                           && helper.league.Teams.Count < 10
                           select new 
                                  { LeagueID = helper.league.ID
                                    , Team = helper.team 
                                   };

The previous example is taken from Dan's IK blog:

http://blogs.interknowlogy.com/2008/10/10/use-linqs-selectmany-method-to-flatten-collections/

I strongly recommend you take a look at it.


I use this extension all the time for diving into hierarchies.

Another cool way to do this when the Extensions get a bit messy is to use the formal LINQ way, like:

var vehicles = from cust in context.Customers
               from fleet in cust.Fleets
               from v in fleet.Vehicles
               select v;

This would be the equivalent of:

var vehicles = context.Customers.SelectMany(c => c.Fleets).SelectMany(f => f.Vehicles);

This can get a bit long winded when adding in where clauses and joins etc. Hope this helps!

Tags:

C#

Linq