Practical LINQ

Web page by Kevin Harris of Homer IL

Please contact Kevin Harris of Homer IL concerning this web site

LINQ Examples II


Two types of syntax for writing LINQ:

  1. Query Syntax
  2. Method Syntax

Not all functions are available with the query syntax. The query syntax are converted to methods by the CLR before executing. LINQ methods are implemented as Extension Methods.

Like the range variable in the "foreach" statement, the range variable in a query expression must support the IEnumerable. However, unlike the "foreach" statement, the type of the range variable in the query expression does not need to be specified as it is implicitly typed. That is, the query expression knows the type which is being enumerated, so it does not need to be explicitly specified.

LINQ supports "deferred" execution. Unless a "greedy" operator is used (e.g. ToList, Last, Max) then the LINQ statement is only setting up the code which will be used. WIth deferred execution, the query expression is not executed until it is required. That is, no values are returned until the query expression is iterated, such as is done with a "foreach" statement.

Extension methods are static methods, in a static class, with the "this" keyword before the first parameter and the first parameter being the type being extended. Extension methods can be used to extend any type (even sealed classes) and can be used as if they were instance methods. Extension methods modify a class without the need for the class to be recompiled.
Extension method show up in Intellisense with an icon containing an arrow next to the box, to distiguish it from instance methods. Extension methods make your helper methods more discoverable and easier to call.

The LINQ extension methods modify the Enumerable type and are in the System.Linq namespace.

.LINQ Extension Methods

A delegate is a reference to a function. A Lambda Expression is an in-line anonymous function. For most LINQ extension methods it is more convenient to use a lambda expression that a delegate. If more than one parameter is used in the lambda expression, parenthesis are required around the parameters. If more than one code line is used in the lambda expression, begin and end braces are required along with a return statement.

{Note: In Visual Studio 2015, use Ctrl-Shift-Space, with cursor placed inside parenthesis, to see the method overloads in Intellisense.}

The following code shows the difference between an extension method and a static method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Globalization;
using System.Threading;

// Convert a static method to an extension method.
namespace PracticalLINQ
{
    class Program
    {
        static void Main(string[] args)
        {
            string bookTitle = "for whom the bell tolls";

            //bookTitle = StringExtensions.ConvertToTitleCase(bookTitle);
            //Console.WriteLine(bookTitle);
            Console.WriteLine(bookTitle.ConvertToTitleCase());

            Console.ReadLine();
        }
    }

    public static class StringExtensions
    {
        public static string ConvertToTitleCase (this string source)
        // public static string ConvertToTitleCase(string source)
        {
            CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture;
            TextInfo textInfo = cultureInfo.TextInfo;

            return textInfo.ToTitleCase(source);
        }
    }
}


Difference Between an Extension Method and a Static Method


Below are extension methods to print a sequence of customers and an individual customer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using System.Collections.Generic;
using System.Linq;

namespace PracticalLINQ
{
    class Program
    {
        static void Main(string[] args)
        {
            string customerList = "1, John Smith, 24.95; 2, Sally Smothers, 12.84; " +
                                  "3, Mike Moss, 0.0; 4, Ken Davis, 36.47";

            var theCustomers = customerList
                                .Split(';')
                                .Select(n => n.Split(','))
                                .Select(n => new Customer { custId = int.Parse(n[0].Trim()), custName = n[1].Trim(), custAmount = Double.Parse(n[2].Trim()) });

            theCustomers.PrintCustomerList("Entire List");

            theCustomers.Where(c => c.custAmount == 0).PrintCustomerList("Zero Amount Customers");

            theCustomers.FirstOrDefault().PrintCustomer("First Customer");

            Console.ReadLine();
        }
    }

    public class Customer
    {
        public int custId;
        public string custName;
        public double custAmount;
    }

    public static class MyLinqExtensions
    {
        public static IEnumerable<Customer> PrintCustomerList(this IEnumerable<Customer> customers, string desc)
        {
            Console.WriteLine(desc);
            foreach (var customer in customers)
            {                
                Console.WriteLine($"Id: {customer.custId} Name: {customer.custName}  Amount: {customer.custAmount:c}");
            }
            Console.WriteLine();
            return customers;
        }

        public static Customer PrintCustomer(this Customer customer, string desc)
        {
            Console.WriteLine(desc);
            Console.WriteLine($"Id: {customer.custId} Name: {customer.custName}  Amount: {customer.custAmount:c}");
            Console.WriteLine();
            return customer;
        }

    }
}


Print Customer Extension Methods


The code below uses LINQ set operations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System;
using System.Collections.Generic;
using System.Linq;

namespace PracticalLINQ
{
    class Program
    {
        static void Main(string[] args)
        {
            Random rand = new Random();
            var letters1 = Enumerable.Range(0, 10)
                        .Select(i => ((char)('A' + rand.Next(0, 26))).ToString())
                        .OrderBy(l => l)
                        .ToList()
                        .PrintLetters("Letters1");

            var letters2 = Enumerable.Range(0, 10)
                        .Select(i => ((char)('A' + rand.Next(0, 26))).ToString())
                        .OrderBy(l => l)
                        .ToList()
                        .PrintLetters("Letters2");

            var lettersUnion = letters1
               .Union(letters2)
               .OrderBy(l => l)
               .PrintLetters("Letters Union");

            var lettersConcat = letters1
               .Concat(letters2)
               .OrderBy(l => l)
               .PrintLetters("Letters Concat");

            var lettersIntersect = letters1
               .Intersect(letters2)
               .OrderBy(l => l)
               .PrintLetters("Letters Intersect");

            var lettersExcept = letters1
               .Except(letters2)
               .OrderBy(l => l)
               .PrintLetters("Letters Except");



            Console.ReadLine();
        }
    }

    public class Customer
    {
        public int custId;
        public string custName;
        public double custAmount;
    }

    public static class MyLinqExtensions
    {
        public static IEnumerable<String> PrintLetters(this IEnumerable<String> letters, string desc)
        {
            Console.WriteLine(desc);
            foreach (var letter in letters)
            {                
                Console.Write(letter);
            }
            Console.WriteLine("\n");
            return letters;
        }
    }
}


LINQ Set Operations



The following code uses the LINQ Join method to access values from a child table.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
using System;
using System.Collections.Generic;
using System.Linq;

namespace PracticalLINQ
{
    class Program
    {
        static void Main(string[] args)
        {
            string customerList = "1, John Smith, 24.95, 1; 2, Sally Smothers, 12.84, 2;" +
                                  "3, Mike Moss, 0.0,3; 4, Ken Davis, 36.47, 1";

            string customerType = "1, Business; 2, Individual; 3, Employee";

            var theCustomers = customerList
                                .Split(';')
                                .Select(n => n.Split(','))
                                .Select(n => new Customer { custId = int.Parse(n[0].Trim()), custName = n[1].Trim(),
                                                            custAmount = Double.Parse(n[2].Trim()), custType = int.Parse(n[3].Trim()),
                                });

            var theCustomerTypes = customerType
                                .Split(';')
                                .Select(n => n.Split(','))
                                .Select(n => new CustomerType
                                {
                                    custTypeId = int.Parse(n[0].Trim()),
                                    custTypeName = n[1].Trim()
                                });


            theCustomers.PrintCustomerList("Entire List");

            theCustomers.Where(c => c.custAmount == 0).PrintCustomerList("Zero Amount Customers");

            theCustomers.FirstOrDefault().PrintCustomer("First Customer");

            var theCustomerQuery = theCustomers.Join(theCustomerTypes, 
                c => c.custType, 
                ct => ct.custTypeId,
                (c, ct) => new
                {
                    CustName = c.custName,
                    CustType = ct.custTypeName
                });

            foreach (var customer in theCustomerQuery)
            {
                Console.WriteLine($"Customer Name: {customer.CustName},  Customer Type: {customer.CustType}");
            }
            Console.ReadLine();
        }
    }

    public class Customer
    {
        public int custId;
        public string custName;
        public double custAmount;
        public int custType;
    }

    public class CustomerType
    {
        public int custTypeId;
        public string custTypeName;
    }

    public static class MyLinqExtensions
    {
        public static IEnumerable<Customer> PrintCustomerList(this IEnumerable<Customer> customers, string desc)
        {
            Console.WriteLine(desc);
            foreach (var customer in customers)
            {                
                Console.WriteLine($"Id: {customer.custId} Name: {customer.custName}  Amount: {customer.custAmount:c} Type: {customer.custType}");
            }
            Console.WriteLine();
            return customers;
        }

        public static Customer PrintCustomer(this Customer customer, string desc)
        {
            Console.WriteLine(desc);
            Console.WriteLine($"Id: {customer.custId} Name: {customer.custName}  Amount: {customer.custAmount:c} Type: {customer.custType}");
            Console.WriteLine();
            return customer;
        }

    }
}


Join Method



The following code show how to use the SelectMany method for working with related data.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
using System;
using System.Collections.Generic;
using System.Linq;

namespace PracticalLINQ
{
    class Program
    {
        static void Main(string[] args)
        {
            string customerList = "1, John Smith; 2, Sally Smothers";

            string invoiceList = "1, 1, 2/2/2016, 2/9/2016, true; 2, 1, 2/4/2016, 2/11/2016, false;" +
                                 "3, 2, 2/8/2016, 2/15/2016, null; 4, 2, 2/9/2016, 2/16/2016, true";

            var theCustomers = customerList
                                .Split(';')
                                .Select(n => n.Split(','))
                                .Select(n => new Customer
                                {
                                    custId = int.Parse(n[0].Trim()),
                                    custName = n[1].Trim(),
                                });

            var theInvoices = invoiceList
                                .Split(';')
                                .Select(n => n.Split(','))
                                .Select(n => new Invoice
                                {
                                    InvoiceId = int.Parse(n[0].Trim()),
                                    CustomerId = int.Parse(n[1].Trim()),
                                    InvoiceDate = Convert.ToDateTime(n[2].Trim()),
                                    DueDate = Convert.ToDateTime(n[3].Trim()),
                                    IsPaid = (n[4].Trim() == "null" ? null : (bool?) bool.Parse(n[4].Trim()))
                                });

            theCustomers.PrintCustomerList("Entire List");

            theInvoices.PrintInvoiceList("Entire List");

            // Calculate Overdue Invoices
            var overdueInvoices = theInvoices
                .Where(i => i.DueDate > new DateTime(2016, 2, 10))
                .Where(i => i.IsPaid != true);
            overdueInvoices.PrintInvoiceList("Overdue List");

            // Customers with Overdue Invoices
            var overdueCustomers = theCustomers
                .SelectMany(c => theInvoices.Where(i => i.CustomerId == c.custId)
                  .Where(i => i.DueDate > new DateTime(2016, 2, 10))
                .Where(i => i.IsPaid != true),
                (c, i) => c);
            overdueCustomers.PrintCustomerList("Overdue Customers");

            // Print List of Customers with OverDue Date
            var overdueCustomerDetails = theCustomers
                .SelectMany(c => theInvoices.Where(i => i.CustomerId == c.custId)
                  .Where(i => i.DueDate > new DateTime(2016, 2, 10))
                .Where(i => i.IsPaid != true),
                (c, i) => new { CustomerName = c.custName, OverdueDate = i.DueDate });

            foreach (var item in overdueCustomerDetails)
            {
                Console.WriteLine($"Over due customer: {item.CustomerName} with overdue date: {item.OverdueDate}");
            }

            Console.ReadLine();
        }
    }

    public class Customer
    {
        public int custId { get; set; }
        public string custName { get; set; }
    }

    public class Invoice
    {
        public int InvoiceId { get; set; }
        public int CustomerId { get; set; }
        public DateTime InvoiceDate { get; set; }
        public DateTime DueDate { get; set; }
        public bool? IsPaid { get; set; }
    }

    public static class MyLinqExtensions
    {
        public static IEnumerable<Customer> PrintCustomerList(this IEnumerable<Customer> customers, string desc)
        {
            Console.WriteLine(desc);
            foreach (var customer in customers)
            {
                Console.WriteLine($"Id: {customer.custId} Name: {customer.custName}");
            }
            Console.WriteLine();
            return customers;
        }

        public static Customer PrintCustomer(this Customer customer, string desc)
        {
            Console.WriteLine(desc);
            Console.WriteLine($"Id: {customer.custId} Name: {customer.custName}");
            Console.WriteLine();
            return customer;
        }

        public static IEnumerable<Invoice> PrintInvoiceList(this IEnumerable<Invoice> invoices, string desc)
        {
            Console.WriteLine(desc);
            foreach (var invoice in invoices)
            {
                Console.WriteLine($"Invoice Id: {invoice.InvoiceId} Customer Id: {invoice.CustomerId}" +
                     $" Invoice Date: {invoice.InvoiceDate} Due Date: {invoice.DueDate} IsPaid: {invoice.IsPaid}");
            }
            Console.WriteLine();
            return invoices;
        }

        public static Invoice PrintInvoice(this Invoice invoice, string desc)
        {
            Console.WriteLine(desc);
            Console.WriteLine($"Invoice Id: {invoice.InvoiceId} Customer Id: {invoice.CustomerId}" +
                    $" Invoice Date: {invoice.InvoiceDate} Due Date: {invoice.DueDate} IsPaid: {invoice.IsPaid}");
            Console.WriteLine();
            return invoice;
        }

    }
}


SelectMany for Working with Related Data