LINQ

Summary

Introduction

LINQ, or Language Integrated Query, is a set of language and framework features for writing structured type-safe queries over local object collections and remote data source. LINQ enables you to query any collection implementing IEnumerable<T>, whether an array, list, or XML DOM, as well as remote data sources, such as tables in SQL Server. The basic units of data in LINQ are sequences and elements. A sequence is any object that implements IEnumerable<T> and an element is each item in the sequence.

int[] numbers = { 1, 2, 3, 4 };    // numbers is a sequence, and 1, 2, 3, 4 are elements

Note the following points about a query operator:

A query is an expression that transforms sequences using query operators. Queries that operate over local sequences are called local queries or LINQ-to-objects queries. Most query operators accept a lambda expression as an argument. In the following code, the lambda expression is (string input) => input.Length >= 4.

public void BasicQuery()
{
    // Define input sequence
    string[] names = { "Tom""Dick""Harry" };
 
    // Use the 'Where'standard query operator on the input sequence to extract names whoe length >= 4
    // Because standard query operators are implemented as static methods on objects that implement
    // IEunumerable, we call call it directly on the input sequence. In the lambda expression, the
    // input argument 'input' represents each name in the array
    // The following syntax is known as the 'fluent syntax'
    IEnumerable<string> filteredNames1 = names.Where((string input) => input.Length >= 4);
 
    foreach (string name in filteredNames1)
        Trace.WriteLine(name);
 
    // The following syntax is known as the 'query expression' syntax
    IEnumerable<string> filteredNames2 = from name in names where (name.Length >= 4) select name;
 
    foreach (string name in filteredNames2)
        Trace.WriteLine(name);
}

The previous example shows a query built using extension method and lambda expression. This strategy is highly composable as it allows the chaining of multiple query operators.

Fluent Syntax

Fluent syntax is the most flexible and fundamental. The following examples illustrate:

Chaining Query Operators

When query operators are chained, the output sequence of one operator is the input sequence of the next:

public void FluentQuery_Chaining()
{
    // Create input sequence
    string[] names = {"Tom""Dick""Harry""May""Jay"};
 
    // To build complex queries, just append additional query operators (OrderBy, Select, etc.)
    // Note that the variable 'name' is privately scoped to each lambda expression. 'name' used
    // in Where query operator does not relate to 'name' used in OrderBy query operator.
    // Recall:
    //  	Where() takes an input sequence and emits a filtered sequence
    //  	OrderBy() takes an input sequence and emits a sorted sequence
    //  	Select() takes an input sequence and emits a transformed or projected sequence
    // When query operators are appended (i.e., chained), the output sequence of one query operator
    // becomes the input sequence for the next query operator. Therefore, data flows from left to 
    // right through the chain of operators; data is first filtered, then sorted, then projected
    var final = names
                .Where((string name) => name.Contains("a"))
                .OrderBy((string name) => name.Length)
                .Select((string name) => name.ToUpper());
 
    // Output:
    //  MAY
    //  JAY
    //  HARRY
    PrintCollection(final);
}
 
private void PrintCollection<T>(IEnumerable<T> collection)
{
    foreach (T t in collection)
        Trace.WriteLine(t.ToString());
}

The end results resembles a production line of lazy conveyor belts. The conveyor belts roll elements only upon demand. Constructing a query constructs a production line—with everything in place—but with nothing rolling. When the consumer requests an element by enumerating over the query, the conveyor belts start rolling. LINQ follows a demand-driven pull model, rather than a supply-driven push model.

The query in the previous example can be constructed progressively:

public void FluentQuery_ProgressiveChaining()
{
    // Create input sequence
    string[] names = { "Tom""Dick""Harry""May""Jay" };
 
    // 'Transformed' is compositionally identical to 'final' in the previous example
    var Filtered = names.Where((string name) => name.Contains("a"));
    var Sorted = Filtered.OrderBy((string name) => name.Length);
    var Transformed = Sorted.Select((string name) => name.ToUpper());
 
    // Note the output of each intermediate step. Output of 'Transformed' query
    // is the same as in the previous example
 
    // Output
    //  Harry
    //  May
    //  Jay
    PrintCollection(Filtered);
 
    // Output 
    //  May
    //  Jay
    //  Harry
    PrintCollection(Sorted);
 
    // Output
    //  MAY
    //  JAY
    //  HARRY
    PrintCollection(Transformed);
}

Composing Lambda Expressions

Note the following about Lambda expressions

For example, this is how Enumerable.Where is implemented, exception handling aside:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, boolpredicate)
{
    foreach (TSource element in source)
        if (predicate(element))
            yield return element;
}
string[] names = { "Tom""Dick""Harry""May""Jay" };
var v = names.Where( (string name) => name.Contains("a") );

The standard query operators utilize generic Func delegates. Func is a family of general-purpose generic delegates used to encapsulate a methods that take one or more parameters and returns a value. Func is defined with the following intent: The type arguments in Func appear in the same order they do in lambda expressions. Hence:

Natural Ordering

The original ordering of elements within an input sequence is important in LINQ. Some query operators rely on this behaviour such as Take, Skip and Reverse:

public void FluentQuery_NaturalOrdering()
{
    // Create an input sequence
    int[] numbers = { 1, 2, 3, 4, 5, 6 };
 
    // Take the first x elements
    var vTake = numbers.Take<int>(3);
    PrintCollection(vTake);
 
    // Skip the first x elements
    var vSkip = numbers.Skip(3);
    PrintCollection(vSkip);
 
    // Generate a reversed sequence. The original input sequence is not affected
    var vReverse = numbers.Reverse();
    PrintCollection(vReverse);
    PrintCollection(numbers);
}

Other Operators

Not all query operators return a sequence. Query operators that do not return a sequence must appear as the last operator in a query.

public void FluentQuery_DoesNotReturnSequence()
{
    // Create input sequence
    int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
 
    // Element operators
    int nFirst = numbers.First();
    int nLast = numbers.Last();
 
    // Aggregation operators
    int nCount = numbers.Count();
    int nMin = numbers.Min();
 
    // Quantifier operators
    bool bNonEmpty = numbers.Any();
    bool bHasEvenNumbers = numbers.Any((int n) => n % 2 == 0);
}

Query Expressions

Query expressions are C# syntactic shortcut for writing LINQ queries. Query expressions always start with a from clause and end with either select or group. The from clause declares a range variable (in this case, name) which you can think of as traversing the input sequence (query expressions can also introduce range variables using let, into and additional from clauses). The following shows the example in Chaining Query Operators rewritten using query expressions:

public void QueryExpression_Chaining()
{
    // Create input sequence
    string[] names = { "Tom""Dick""Harry""May""Jay" };
 
    // Same logic as FluentQuery_Chaining() method. The 'name' identifier following the from
    // keyword is called a 'range variable' and it refers to the current element in the input
    // sequence. Note that while the range variable appears in every clause (from, where, orderby,
    // and select), the variable enumerates over a different sequence with each clause. This
   // becomes clear if you look at the fluent syntax in FluentQuery_Chaining() method.
    var final = from name in names
                where name.Contains("a")    // Filter
                orderby name.Length         // Sort
                select name.ToUpper();      // Project
 
    // Output:
    //  MAY
    //  JAY
    //  HARRY
    PrintCollection(final);
}

The following table helps decide when to use query syntax vs. fluent syntax.

Scenario What to Use
  • A let clause for introducing a new variable alongside the range variable.
  • SelectMany, Join, or GroupJoin, followed by an outer range variable reference.
Query Syntax.
Simple use of Where, OrderBy, and Select. Either syntax works well.
Queries that comprise a single operator. Fluent syntax (shorter and less cluttered.)

any operator outside of the following:

Where, Select, SelectMany
OrderBy, ThenBy, OrderByDescending, ThenByDescending
GroupBy, Join, GroupJoin
Fluent syntax.

You can mix query syntax and fluent syntax. The only restriction is that each query-syntax component must be complete (i.e., start with a from clause and end with a select or group clause):

public void QueryExpressionMixedWithFluentSyntax()
{
    // Create input sequence
    string[] names = { "Tom""Dick""Harry""May""Jay" };
 
    // Count the number of names containing the letter 'a'            
    int nCount1 = (from name in names where name.Contains('a'select name).Count();    // Mixed
    int nCount2 = names.Where((name) => name.Contains('a')).Count();                    // Fluent-only
 
    // Get the first name in alphabatical order
    string firstName1 = (from name in names orderby name select name).First();
    string firstName2 = names.OrderBy((name) => name).First();
}

Deferred Execution

A very important feature of most query operators is that they execute not when constructed, but when enumerated. This is called lazy or deferred execution. Deferred execution is important because it decouples query construction from query execution. This allows you to construct a query in several steps.

public void DeferredExecution()
{
    // Create an input sequence
    var lstNumbers = new List<int>() { 1, 2, 3 };
 
    // Create a query on the input sequence;
    IEnumerable<int> lst10Times = from number in lstNumbers select number * 10;
 
    // Sneak another element in
    lstNumbers.Add(4);
 
    // lst10Times is evaluated when enumerated and when constructed. This means that
    // the extra number sneaked in after the query was constructed is included in the
    // result. 
    PrintCollection(lst10Times);
}

All standard query operators provide deferred execution, with the following exceptions which cause immediate query execution:

The following example illustrates:

public void ImmediateExecution()
{
    // Create an input sequence
    var lstNumbers = new List<int>() { 1, 2, 3 };
 
    // Create a query on the input sequence. Gets executed immediately
    int nCount = (from number in lstNumbers select number).Count();     // Count = 3
 
    // Add another element
    lstNumbers.Add(4);
 
    // Create a query on the input sequence. Gets executed immediately
    nCount = (from number in lstNumbers select number).Count();         // Count = 4
}

Deferred execution is due to the use of yield return in the implementation of query operators. For example, here's how Select query operator is implemented. Note the use of yield return:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> inputSequence, Func<TSource, TResult> selector)
{
    foreach (TSource element in inputSequence)
        yield return selector(element);
}

ReEvaluation

Deferred execution means that a query is evaluated not on construction, but on enumeration. Deferred execution also means that a query is reevaluated when you re-enumerate:

public void DeferredExecutionWithReevaluation()
{
    // Create an input sequence
    var lstNumbers = new List<int>() { 1, 2, 3 };
 
    // Create a query on the input sequence. query is evaluated on enumeration
    // PrintCollection prints 1, 2, 3
    IEnumerable<int> query = from number in lstNumbers select number;
    PrintCollection(query);
 
    // query is re-evaluated on enumeration. Since lstNumbers is now empty, PrintCollection
    // prints nothing
    lstNumbers.Clear();
    PrintCollection(query);
}

Reevaluation is sometimes disadvantageous:

You prevent reevaluation by immediately executing the query. Just call a conversion operator, such as ToArray() or ToList().

Captured Variables

A captured variable is a local variable declared at the same scope as a lambda expression. If the query’s lambda expressions reference local variables, these local variables are subject to captured variable semantics. This means that if you later change their value, the query changes as well:

public void CapturedVariables()
{
    // Create input sequence
    int[] numbers = { 1, 2, 3 };
 
    // nFactor is a captured variable!
    int nFactor = 10;
 
    // Create a query that uses a captured variables
    var query = from number in numbers select number * nFactor;
 
    // Change the captured variables (query not evaluated yet)
    nFactor = 100;
 
    // Now evaluate the query by enumerating it. Prints 100, 200, 300.
    PrintCollection(query);
}

This is a common trap when building a query with a foreach loop:

public void CapturedVariables_Trap()
{
    // Create input sequence
    string msg = "Statement with lots of vowels";
 
    // Remove all vowels from the input sequence by using multiple filters.
    // Approach works but is ineffective as the output sequence is passed
    // to the next filter multiple times
    IEnumerable<char> query1 = msg.Where( c => c != 'a' );
    query1 = query1.Where( c => c != 'e' );
    query1 = query1.Where(c => c != 'i');
    query1 = query1.Where(c => c != 'o');
    query1 = query1.Where(c => c != 'u');
    WriteCollection(query1);        // Output: Sttmnt wth lts f vwls
 
    // Remove all vowels from the input sequence by using a foreach loop
    // Approach does not work because the compiler scopes the iteration
    // variable in the foreach loop as if it was declared outside the loop
    // This means that the same variables (the iteration variable) is updated
    // repeatedly so each lambda expressions captures the same variable producing
    // the same effect when we updated nFactor in the previous example.
    // So when we enumerate the query, all lambda expressions reference that
    // single variable's current value which is 'u'. 
    IEnumerable<char> query2 = "Statement with lots of vowels";
    foreach (char vowel in "aeiou")
        query2 = query2.Where(c => c != vowel);
    WriteCollection(query2);        // Output: Statement with lots of vowels
 
    // This problem is solved by ASSIGNING THE LOOP VARIABLE TO ANOTHER VARIABLE
    // DECLARED INSIDE THE STATEMENT BLOCK
    IEnumerable<char> query3 = "Statement with lots of vowels";
    foreach (char vowel in "aeiou")
    {
        // Force a fresh variables to be used in each iteration
        char temp = vowel;
        query3 = query3.Where(c => c != temp);
    }
    WriteCollection(query3);        // Output: Sttmnt wth lts f vwls
}
 
private void WriteCollection<T>(IEnumerable<T> collection)
{
    Trace.WriteLine("");
    foreach (T t in collection)
        Trace.Write(t.ToString());
}

Subqueries

A subquery is a query contained within another query's lambda expression. The following shows an example of a subquery:

public void Subquery()
{
    // Input sequence is an array of arrays
    int[][] data = {
                    new int[] {3,4,5},
                    new int[] {1,2},                           
                    new int[] {5,6,7},
                    new int[] {1,0,2,3,4}
                    };
            
 
    // Query to order sequences by their average
    var v = data.OrderByDescending(input => input.Average());            
}

Subqueries are allowed because you can use any valid C# expression on the right-hand side of a lambda. Note that the subquery executes when required to feed the outer query. In our example, subquery input.Average() executes for every input.

Composition Strategies

There are three strategies for building complex LINQ queries. All are chaining strategies and produce identical results:

  1. Progressive query construction.
  2. Using the into keyword.
  3. Wrapping queries.

Progressive Query Construction

We showed earlier that a fluent query can be built progressively:

public void FluentQuery_ProgressiveChaining()
{
    // Create input sequence
    string[] names = { "Tom""Dick""Harry""May""Jay" };
 
    // 'Transformed' is compositionally identical to 'final' in the previous example
    var Filtered 	  = names.Where((string name) => name.Contains("a"));
    var Sorted 	  = Filtered.OrderBy((string name) => name.Length);
    var Transformed = Sorted.Select((string name) => name.ToUpper());
}

There are a few benefits to writing queries progressively:

  1. It can make queries easier to write.
  2. You can add query operators conditionally. For example:

    if (bSorted) query = query.OrderBy( ... )
public void ProgressiveQueryBuilding()
{
    // Create input sequence
    string[] names = { "John""Paul""Ringo""George" };
 
    // Remove vowels from all names
    var v = from name in names select name.Replace("a""");
    v = from name in v select name.Replace("o""");
    v = from name in v select name.Replace("u""");
    v = from name in v select name.Replace("i""");
    v = from name in v select name.Replace("e""");
    v.OrderBy(n => n);
    PrintCollection(v);
 
    // Remove vowels from all names
    var v2 = from name in names 
                select name.Replace("a""").
                Replace("o""").
                Replace("u""").
                Replace("i""").
                Replace("e""");
    v2.OrderBy(n => n);
    PrintCollection(v2);
}

Output from both queries:

Jhn
Pl
Rng
Grg

Using the into Keyword

The into keyword is interpreted by query expressions in two ways: To indicate a query continuation or a GroupJoin. This section discusses using into for query continuation. The into keyword lets you continue a query after a projection and is a short cut for progressively querying. The only place you can use into is after select or group clauses. into restarts a query, allowing you to introduce fresh where, orderby and select clauses:

public void UsingInto()
{
    // Input sequence is an array of sequences
    int[][] data = new int[][] 
                        { 
                            new int[] {3,4,5},
                            new int[] {5,5,5}, 
                            new int[] {3,2,5,4}
                        };
 
    // Get sequences whose average is <= 4
    double dThreshold = 4.0;
    var v = data.
                Select(input => input.Average()).
                Where(avg => avg <= dThreshold);                        
 
    // Cannot translate above query directly using query expression because the select
    // clause must come afterthe where clause. Note that this query ends up selecting
    // a sequence rather than an average
    var v2 = from input in data where input.Average() < dThreshold select input;
 
    // Using into gives same result as v
    var v3 = from input in data select input.Average()
                into average
                where average >= dThreshold select average;
}

Note that all query variables are out of scope following an into keyword. The following does not compile:

var query = from input in data select input.Average()
            into average
            where average < input.First();      // only average is visible. 'input' is not in scope

Wrapping Queries

For example, consider the following query:

IEnumerable<string> query = from n in names
			 select n.Replace("a""").Replace("e""").Replace("i""").Replace("o""").Replace("u""");
query = from n in query where n.Length > 2 orderby n select n;

Reformulated in wrapped form, it’s the following:

var query = from n1 in
		(from n2 in names
		select n2.Replace("a""").Replace("e""").Replace("i""")
		.Replace("o""").Replace("u""")
		)
	   where n1.Length > 2
	   orderby n1
	   select n1;

Projection Strategies

All previous select clauses have projected scalar element types. With C# object initializers and anonymous types you can project into more complex types. Recall that with anonymous types,  the complier creates a type less class with fields that match the structure of our projection, unlike object initializers where you need to explicitly create a class with required fields:

public void CompositionStrategy_AnonymousTypes()
{
    // Create an input sequence
    var lstNumbers = new List<double>() { 100.0, 200.0, 500.0 };
 
    // Create a query on the input sequence;
    var Values = from number in lstNumbers select new { Price = number, VAT = number * 0.20, Total = number * 1.20 };
 
    // Enumerate query
            
    // Output results:
    //    Price = £100.00, VAT = £20.00, Total = £120.00
    //    Price = £200.00, VAT = £40.00, Total = £240.00
    //    Price = £500.00, VAT = £100.00, Total = £600.00
    foreach (var v in Values)
        Trace.WriteLine(string.Format("Price = {0:C}, VAT = {1:C}, Total = {2:C}", v.Price, v.VAT, v.Total));
}

The let Keyword

The let keyword introduces a new variable alongside the range variable. let  accomplishes two things:

Note the following about let keyword:

  1. You can have any number of let statements, before or after a where statement.
  2. A let statement can reference variables introduced in earlier let statements (subject to the boundaries imposed by an into clause).
public void CompositionStrategy_Let()
{
    // Create input sequence
    string[] names = { "Altogether""AOUIE""Sometime""UnConditional""Evolutionary" };
 
    //Extract vowles
    var query = from name in names
            let NoAs = name.ToLower().Replace("a""")
            let NoOs = name.ToLower().Replace("o""")
            let NoUs = name.ToLower().Replace("u""")
            let NoIs = name.ToLower().Replace("i""")
            let NoEs = name.ToLower().Replace("e""")
            let NoVowels = name.ToLower().Replace("a""").Replace("o""").Replace("u""").Replace("i""").Replace("e""")
            select new {Name = name, NoA = NoAs, NoU = NoUs, NoO = NoOs, NoI = NoIs, NoE = NoEs, NoAOUEI = NoVowels};
 
    // Output
    //    Original: Altogether, A: ltogether, U: altogether, O: altgether, I: altogether, E: altogthr, AUOIE: ltgthr
    //    Original: AOUIE, A: ouie, U: aoie, O: auie, I: aoue, E: aoui, AUOIE: 
    //    Original: Sometime, A: sometime, U: sometime, O: smetime, I: sometme, E: somtim, AUOIE: smtm
    //    Original: UnConditional, A: unconditionl, U: nconditional, O: uncnditinal, I: uncondtonal, E: unconditional, AUOIE: ncndtnl
    //    Original: Evolutionary, A: evolutionry, U: evoltionary, O: evlutinary, I: evolutonary, E: volutionary, AUOIE: vltnry
    foreach (var v in query)
        Trace.WriteLine(string.Format("Original: {0}, A: {1}, U: {2}, O: {3}, I: {4}, E: {5}, AUOIE: {6}", 
                        new string[] {v.Name, v.NoA, v.NoU, v.NoO, v.NoI, v.NoE, v.NoAOUEI}));
}

Interpreted Queries

LINQ provides two parallel architectures

  1. Local queries for local objects collections
  2. Interpreted queries for remote data sources

A query can include both interpreted and local operators. A typical pattern is to have the local operators on the outside and the interpreted components on the inside; in other words, the interpreted queries feed the local queries. This pattern works well with LINQ-to-database queries. AsEnumerable() method can act as a bridge between interpreted and local queries. Its purpose is to cast an IQueryable<T> sequence to IEnumerable<T>, forcing subsequent query operators to bind to Enumerable operators instead of Queryable operators. This causes the remainder of the query to execute locally:

var query = dataContext.MyTable.Where( field => field.ID > 100) // Interpreted query runs remotely on database
            .AsEnumerable()                                     // Interpreted to local bridge
            .OrderBy( field => field.ID)                        // Local query runs locally

An alternative to calling AsEnumerable is to call ToArray or ToList. The advantage of AsEnumerable is that it doesn’t force immediate query execution, nor does it create any storage structure.

LINQ Operators

All examples in this section uses the following input collection

	string[] names = { "Tom""Dick""Harry""May""Jay" };

The standard query operators fall into three categories:

Sequence à Sequence
Filtering Where, Take, TakeWhile, Skip, SkipWhile, Distinct
Projecting Select, SelectMany
Joining Join, GroupJoin
Ordering OrderBy, ThenBy, Reverse
Grouping GroupBy
Set Operators Concat, Union, Intersect, Except
Zip Operator Zip
Conversion - Import OfType, Cast
Conversion - Export ToList, ToArray, ToDictionary, ToLookup, AsEnumerable, AsQueryable
Sequence à Element or Scalar
Element First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault, DefaultIfEmpty
Aggregation Aggregate, Average, Count, LongCount, Sum, Max, Min
Quantifiers All, Any, Contains, SequenceEqual
Void à Sequence
Generation Empty, Range, Repeat

Filtering

public void Filtering()
{
    string[] names = { "Tom""Dick""Harry""Mary""Jay" };
 
    // Where returns elements from the input sequence tht staisfy a given predicate
    // Result: Harry, Mary, Jay
    var v1 = names.Where((name) => name.EndsWith("y"));
 
    // A where clause can appear multiple times and can be interspersed with let
    // Result: // HARRAY, MARRY
    var v2 = from name in names
                where name.Length > 3
                let u = name.ToUpper()
                where u.EndsWith("Y")
                select u;
 
    // Where's predicate can accept a second optional argument of type int which represents
    // the index of each element in the input sequence
    // Result: Tom, Harry, Jay
    var v3 = names.Where( (name, index) => index % 2 == 0);  // Select items whose index is even
 
    // Take method takes the first N elements, while Skip method skips the first N elements
    // Skip followed by Take allows you to return a specific range, say items 100-110 in an
    // input collection            
    IEnumerable<int> ZeroToThousand = Enumerable.Range(0, 1000);
    var v4 = from n in ZeroToThousand.Skip(101).Take(10) select n;            // 101 ... 110
    var v5 = from n in ZeroToThousand.
                    SkipWhile(n => n <= 100).TakeWhile(n => n <= 110) select n;  // 101 ... 110
}


Projecting

public void Projecting()
{
    string[] names = { "Tom""Dick""Harry""Mary""Jay" };
 
    // With Select you always get the same number of elements you started with.
    // TOM, DICK, HARRAY, MAY, JAY
    var v1 = from name in names select name.ToUpper();
 
    // Select is often used to project to anonymous types
    var v2 = from name in names select new { LowerCase = name.ToLower(), UpperCase = name.ToUpper() };
 
    // Select with no transformation is used with query syntax to satisfy the requirement
    // that the query must end in a select or gorup clause
    var v3 = from f in FontFamily.Families
             where f.IsStyleAvailable(FontStyle.Strikeout)
             select f;
 
    // Select accepts an optional int argument that acts as the index of each element
    // in the input sequence
    // Result: "0=Tom", "1=Dick" ...
    var v4 = names.Select((name, index) => index + "=" + name);
 
    // You can nest a subquery in a select clause to build an object hierarchy
    // The folloiwng returns a collection of files under each directory  in d:\Prsim4
    // Note that a subquery within a select causes double-deferred execution. In the example
    // below, the files do not get projected until an inner foreach enumerates.
    DirectoryInfo[] dirs = new DirectoryInfo(@"D:\Prism4").GetDirectories();            
    var v5 = from d in dirs
                select new 
                {
                    DirectoryName = d.Name,
                    Files = from file in d.GetFiles() select file.FullName // correlated query!
                };
}

public void Projecting_Subqueries()
{
    // Subquery projections can be used in LINQ-to-SQL to do the work of SQL-style joins.
    // This style of query is ideally suited to interpreted queries. The outer query and
    // subquery are processed as a unit, avoiding unnecessary round-tripping. With local
    // queries, however, it’s inefficient because every combination of outer and inner
    // elements must be enumerated to get the few matching combinations. A better choice
    // for local queries is Join or GroupJoin, described in the following sections.
    var query = from c in dataContext.Customers
        select new {
                    c.Name,
                    Purchases = from p in dataContext.Purchases
                                where p.Price > 1000
                                select new { p.Description, p.Price }
                    };
 
    foreach (var purchase in query)
    {
        Console.WriteLine ("Customer: " + purchase.Name);
        foreach (var detail in purchase.Purchases)
        Console.WriteLine ("  - $$$: " + purchase.Price);
    }
}
public void Project_SelectMany()
{
    // Recall that select yields one output element for each input element. SelectMany, in contrast,
    // yields 0..n output elements for each input element. The 0..n output elements come from a
    // child sequence that the lambda expression must emit. Therefore, SelectMany is used to
    // 1. Expand child sequences
    // 2. Flatten nested collections
    // 3. Join two collections into a flat output sequence
    // The key point is that the lambda expression must emit a child sequencer per input element
 
    // Input collection: A collection of parents with children
    // See Watch window 1
    var families = new[] {
        new {ParentName = "X", Names = new[] { new {Name="X1"}, new {Name = "X2"}}},
        new {ParentName = "Y", Names = new[] { new {Name="Y1"}, new {Name = "Y2"}, new {Name = "Y3"}}},
        new {ParentName = "Z", Names = new[] { new {Name="Z1"}, new {Name = "Z2"}}},
    };
 
    // Result: X1 X2 Y1 Y2 Y3 Z1 Z2
    // See Watch window 2
    var names = families.SelectMany(v => v.Names);
    foreach (var v in names)
        Trace.WriteLine(v.Name);
 
    // Result: X1 X2 Y1 Y2 Y3 Z1 Z2
    // See Watch window 2
    // Note use of a nested foreach statement
    var names2 = families.Select(v => v.Names);
    foreachvar v1 in names2)
    {
        foreach (var v2 in v1)
            Trace.WriteLine(v2.Name);
    }
 
    // SelectMany is supported in query syntax by having an additional generator -
    // i.e., an additional from clause. In the example below, child is the new range
    // variable and family is the outer range variable. Note that the outer range
    // variable remains in scope until the query ends or eaches an into clause
    // See Watch window 3
    var children = from family in families
                    from child in family.Names
                    select new { Parent = family.ParentName, Child = child.Name };
}





public void JoiningWithSelectMany()
{
    // SelectMany can be used to join two sequences to perform a CROSS JOIN
    int[] numbers = { 1, 2, 3, 4 };
    string[] letters = { "a""b" };
    var v1 = from number in numbers
                from letter in letters
                select number.ToString() + letter;
}

Joining

Join and GroupJoin combine two input sequences into a single output sequence. Join emits flat output while GroupJoin emits hierarchical output. Join and GroupJoin offer the equivalent of inner and left outer joins only. Cross joins and non-equi joins must be performed with Select/SelectMany:

public void Join()
{
    var customers = new[] {  new {Name="A", ID = 1},
                            new {Name="B", ID = 2},
                            new {Name="C", ID = 3},
                            new {Name="D", ID = 4}};
 
    var addresses = new[] { new {ID = 1, City = "London"},
                            new {ID = 2, City = "Paris"},
                            new {ID = 3, City = "New York"},
                            new {ID = 4, City = "Tokyo"}};
 
    // Join performs an inner join (customers without an address are excluded)
    // You can swap the outer (customers) and inner (addresses) sequences and
    // still get the same result. You can also add further join clauses to the
    // same query. Query syntax is usually preferable when joining; it’s less fiddly
    // than fluent syntax
    var v1 = from customer in customers
                join address in addresses on customer.ID equals address.ID
                select new { Customer = customer.Name, Address = address.City };
 
    // Same query as above written using SelectMany. Although both queries yield the
    // same results, the Join query is considerably faster because its implementation in 
    // Enumerable preloads the inner collection into a keyed lookup.
    var v2 = from customer in customers
                from address in addresses
                where customer.ID == address.ID
                select new { Customer = customer.Name, Address = address.City };
}

public void GroupJoin()
{
    // Group join does the same work as Join, but instead of emitting a flat result
    // it emits a hierarchical result, GROUPED BY EACH OUTER ELEMENT. The query syntax
    // is the same as Join, but is followed by the into keyword.
    // An into clause translates to GroupJoin only when it appears directly after a join
    // clause. After a select or group clause, it means query continuation. The two uses
    // of the into keyword are quite different,
 
    // Input sequences
    var Managers = new[] {  new {Name="Man1", ManagerID = 1},
                            new {Name="Man2", ManagerID = 2},
                            new {Name="Man3", ManagerID = 3},
                            new {Name="Man4", ManagerID = 4}};
 
    var Employees = new[] { new {ManagerID = 1, City = "Emp1"},
                            new {ManagerID = 1, City = "Emp2"},
                            new {ManagerID = 2, City = "Emp3"},
                            new {ManagerID = 2, City = "Emp4"},
                            new {ManagerID = 2, City = "Emp5"},
                            new {ManagerID = 3, City = "Emp6"},
                            new {ManagerID = 3, City = "Emp7"},
                            new {ManagerID = 4, City = "Emp8"}};
 
    // By default group join does a left outer join
    var v1 = from manager in Managers
                join employee in Employees on manager.ManagerID equals employee.ManagerID
                into ManagerAndHisEmployees
                select new { Manager = manager.Name, Employees = ManagerAndHisEmployees };
 
    // The following is equivlant to the above query
    var v2 = from manager in Managers
                select new
                {
                    Manager = manager.Name,
                    Employees = from employee in Employees where employee.ManagerID == manager.ManagerID select employee
                };
}