Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

October 30, 2010

SelectMany: Finding in a Child List

Filed under: C#,Lambda Expressions,VB.NET @ 11:44 pm

In this prior post, I demonstrated how to find a specific item in a generic list of items. But what if the item you want to find is in a child list?

For example, a list of customers where each customer is a company that has a company name and a list of contact persons. You may want to find a customer by matching the names of the contacts.

The sample Customer and Contact classes along with the code to build the list of customers with their contacts can be found in this prior blog post.

You can use the Select extension method on IEnumerable to find within a child list.

In C#:

var foundContacts = custList.
         Select(cust=> cust.ContactList.
                Where(cn=> cn.LastName.StartsWith("B")));

foreach (var item in foundContacts)
{
     foreach (var childItem in item)
         Console.WriteLine(childItem.LastName + ", " +
                           childItem.FirstName);
}

In VB:

Dim foundContacts = custList.
        Select(Function(cust) cust.ContactList.
               Where(Function(cn) cn.LastName.StartsWith("B")))

For Each item In foundContacts
    For Each childItem In item
        Console.WriteLine(childItem.LastName & ", " &
                          childItem.FirstName)
    Next
Next

The first line of code uses a Lambda expression to find the contacts with a last name that starts with "B".

[To begin with an overview of lambda expressions, start here.]

This Select statement returns an IEnumerable<IEnumerable<Contact>>, which is a little different to loop through as shown in the looping code that outputs the results of the search.

There are several issues with this approach. First is that it does not provide any access to information about the customer. So you cannot easily determine which customer is associated with the found contact name.  Second, accessing the IEnumerable<IEnumerable<Contact>> requires double loops.

Instead of using a standard Select extension method, use the SelectMany extension method to get around both of these issues.

The following shows the SelectMany extension method using only one parameter.

In C#:

var foundContacts = custList.
        SelectMany(cust=> cust.ContactList.
            Where(cn=> cn.LastName.StartsWith("B")));

foreach (var item in foundContacts)
    Console.WriteLine(item.LastName + ", " + item.FirstName);

In VB:

Dim foundContacts = custList.
        SelectMany(Function(cust) cust.ContactList.
            Where(Function(cn) cn.LastName.StartsWith("B")))

For Each item In foundContacts
    Console.WriteLine(item.LastName & ", " & item.FirstName)
Next

The above code replaces the Select statement with SelectMany. It then returns an IEnumerable<Contact>, which is much easier to access using a single loop. However, this still does not provide access to the customer.

This code demonstrates the SelectMany extension method using two parameters.

In C#:

var foundCustomers = custList.
        SelectMany(cust=> cust.ContactList.
            Where(cn=> cn.LastName.StartsWith("B")),
                    (cust, cn)=> cust);

foreach (var item in foundCustomers)
    Console.WriteLine(item.CompanyName);

In VB:

Dim foundCustomers = custList.
        SelectMany(Function(cust) cust.ContactList.
            Where(Function(cn) cn.LastName.StartsWith("B")),
                    Function(cust, cn) cust)

For Each item In foundCustomers
    Console.WriteLine(item.CompanyName)
Next

The first parameter is the same Lambda expression from the prior two examples. The second parameter is another Lambda expressions that simply returns cust. This means the the result of this search is an IEnumerable<Customer>.

Use SelectMany any time you want to search a child list and return either the list of matching children or the list of matching parents.

Enjoy!

2 Comments

  1.   Olivier — January 29, 2011 @ 5:42 pm    Reply

    The solution using SelectMany is a bit confusing : in the query you’re filtering contact list (LastName.StartsWith(“B”) but the result is list of customers with their full list of contacts.. Of course, only customer matching the filter are selected, but as the filter is acting on the contact list a lot of people will think that the latter will only contain the list of “valid” contact (those matching the filter), and this is not the case.
    The reason is simple, as your SelectMany query is returning Customer instances, and as an instance of Customer has a ContactList, of course Linq can’t change the reality of this list. Linq does only read operation, never write and it never modify instances… So the result is ok on this technical point of view, but I think (and as you’re not giving a sample) this can be a lot confusing for many people…

  2.   DeborahK — January 29, 2011 @ 7:32 pm    Reply

    Hi Olivier –

    Thank you for your comments.

    As you noted, the “correct” solution here completely depends on the question you are trying to answer.

    “Give me a list of all customers where any contact has a last name that starts with ‘B’.” Or a better example, “where any contact has not made a purchase in the past 6 months”. Then the second SelectMany solution shown above makes sense.

    If the task is instead, “Give me the list of all contacts with a last name that starts with ‘B’ along with their company name, then the first SelectMany solution shown above will give you the list of contacts and you then need to look up the customer associated with each contact.

RSS feed for comments on this post. TrackBack URI

Leave a comment

*

© 2014 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs