9.3 Querying an Array of Employee Objects Using LINQ

LINQ is not limited to querying arrays of simple types such as ints. It can be used with arrays of any data type, including strings and user-defined classes. It cannot be used when a query does not have a defined meaning—for example, you cannot use orderby on values that are not comparable. Comparable types in .NET are those that implement the IComparable interface, which is discussed in Section 20.4. All built-in types, such as string, int and double implement IComparable. Figure 9.3 presents an Employee class. Figure 9.4 uses LINQ to query an array of Employee objects.

Fig. 9.3 Employee class with FirstName, LastName and MonthlySalary properties.

Alternate View

  1   // Fig. 9.3: Employee.cs
  2   // Employee class with FirstName, LastName and MonthlySalary properties.
  3   class Employee 
  4   {
  5      public string FirstName { get; } // read-only auto-implemented property 
  6      public string LastName { get; } // read-only auto-implemented property 
  7      private decimal monthlySalary; // monthly salary of employee 
  8
  9      // constructor initializes first name, last name and monthly salary
 10      public Employee(string firstName, string lastName,
 11         decimal monthlySalary) 
 12      {
 13         FirstName = firstName;
 14         LastName = lastName;
 15         MonthlySalary = monthlySalary;
 16      }
 17
 18      // property that gets and sets the employee's monthly salary
 19      public decimal MonthlySalary
 20      {
 21         get
 22         {
 23            return monthlySalary; 
 24         }
 25         set
 26         {
 27            if (value >= 0M) // validate that salary is nonnegative 
 28            {
 29                monthlySalary = value; 
 30            }
 31       }
 32   }
 33
 34   // return a string containing the employee's information
 35   public override   string ToString() =>
 36      $"{FirstName,-10} {LastName,-10} {MonthlySalary,10:C}";
 37  }
 

Fig. 9.4 LINQ to Objects querying an array of Employee objects.

Alternate View

  1   // Fig. 9.4: LINQWithArrayOfObjects.cs
  2   // LINQ to Objects querying an array of Employee objects.
  3   using System;
  4   using System.Linq;
  5
  6   class LINQWithArrayOfObjects
  7  {
  8      static void Main()
  9      {
 10         // initialize array of employees 
 11         var employees = new[] {
 12            new Employee("Jason", "Red", 5000M),
 13            new Employee("Ashley", "Green", 7600M),
 14            new Employee("Matthew", "Indigo", 3587.5M),
 15            new Employee("James", "Indigo", 4700.77M),
 16            new Employee("Luke", "Indigo", 6200M),
 17            new Employee("Jason", "Blue", 3200M),
 18            new Employee("Wendy", "Brown", 4236.4M)};
 19
 20         // display all employees 
 21         Console.WriteLine("Original array:"); 
 22         foreach (var element in employees)
 23         {
 24            Console.WriteLine(element);
 25         }
 26
 27         // filter a range of salaries using && in a LINQ query 
 28         var between4K6K =
 29            from e in employees                                           
 30            where (e.MonthlySalary >= 4000M) && (e.MonthlySalary <= 6000M)
 31            select e;                                                     
 32
 33         // display employees making between 4000 and 6000 per month 
 34         Console.WriteLine("
Employees earning in the range" + 
 35            $"{4000:C}-{6000:C} per month:");
 36         foreach (var element in between4K6K)
 37         {
 38            Console.WriteLine(element);
 39         }
 40
 41         // order the employees by last name, then first name with LINQ 
 42         var nameSorted =
 43            from e in employees            
 44            orderby e.LastName, e.FirstName
 45            select e;                      
 46
 47         // header 
 48         Console.WriteLine("
First employee when sorted by name:"); 
 49
 50         // attempt to display the first result of the above LINQ query 
 51         if (nameSorted.Any()) 
 52         {
 53            Console.WriteLine(nameSorted.First());
 54         }
 55         else 
 56         {
 57            Console.WriteLine("not found");
 58         }
 59
 60         // use LINQ to select employee last names 
 61         var lastNames =
 62            from e in employees
 63            select e.LastName; 
 64
 65         // use method Distinct to select unique last names 
 66         Console.WriteLine("
Unique employee last names:");
 67         foreach (var element in lastNames.Distinct())
 68         {
 69            Console.WriteLine(element);
 70         }
 71
 72         // use LINQ to select first and last names 
 73         var names =
 74            from e in employees                  
 75            select new {e.FirstName, e.LastName};
 76
 77         // display full names 
 78         Console.WriteLine("
Names only:"); 
 79         foreach (var element in names)
 80         {
 81            Console.WriteLine(element);
 82         }
 83
 84         Console.WriteLine();
 85      }
 86   }

Original array:
Jason      Red          $5,000.00
Ashley     Green        $7,600.00
Matthew    Indigo       $3,587.50
James      Indigo       $4,700.77
Luke       Indigo       $6,200.00
Jason      Blue         $3,200.00
Wendy      Brown        $4,236.40
Employees earning in the range $4,000.00-$6,000.00 per month:
Jason      Red          $5,000.00
James      Indigo       $4,700.77
Wendy      Brown        $4,236.40
First employee when sorted by name:
Jason      Blue         $3,200.00
Unique employee last names:
Red
Green
Indigo
Blue
Brown
Names only:
{ FirstName = Jason, LastName = Red }
{ FirstName = Ashley, LastName = Green }
{ FirstName = Matthew, LastName = Indigo }
{ FirstName = James, LastName = Indigo }
{ FirstName = Luke, LastName = Indigo }
{ FirstName = Jason, LastName = Blue }
{ FirstName = Wendy, LastName = Brown }

9.3.1 Accessing the Properties of a LINQ Query’s Range Variable

The where clause in line 30 (Fig. 9.4) accesses the range variable’s properties. The compiler infers that the range variable is of type Employee, because employees was defined as an array of Employee objects (lines 11–18). Any bool expression can be used in a where clause. Line 30 uses the && (conditional AND) operator to combine conditions. Here, only employees that have a salary between $4,000 and $6,000 per month, inclusive, are included in the query result, which is displayed in lines 36–39.

9.3.2 Sorting a LINQ Query’s Results by Multiple Properties

Line 44 uses orderby to sort the results according to multiple properties—specified in a comma-separated list. Here, the employees are sorted alphabetically by last name. Employees that have the same last name are sorted by first name.

9.3.3 Any, First and Count Extension Methods

Line 51 introduces the Any method, which returns true if the query to which it’s applied has at least one element. The query result’s First method (line 53) returns the first element in the result. You should check that the query result is not empty (line 51) before calling First, which throws an InvalidOperationException if the collection is empty.

We’ve not specified the class that defines methods First and Any. Your intuition probably tells you they’re methods of interface IEnumerable<T>, but they aren’t. They’re actually extension methods that enhance a class’s capabilities without modifying the class’s definition. The LINQ extension methods can be used as if they were methods of IEnumerable<T>. Section 10.14 shows how to create extension methods.

LINQ defines many more extension methods, such as Count, which returns the number of elements in the results. Rather than using Any, we could have checked that Count was nonzero, but it’s more efficient to determine whether there’s at least one element than to count all the elements. The LINQ query syntax is actually transformed by the compiler into extension method calls, with the results of one method call used in the next. It’s this design that allows queries to be run on the results of previous queries, as it simply involves passing the result of a method call to another method. For a complete list of IEnumerable<T> extension methods, visit


https://msdn.microsoft.com/library/9eekhta0

9.3.4 Selecting a Property of an Object

Line 63 uses the select clause to select the range variable’s LastName property rather than the range variable itself. This causes the results of the query to consist of only the last names (as strings), instead of complete Employee objects. Lines 67–70 display the unique last names. The Distinct extension method (line 67) removes duplicate elements, causing all elements in the resulting collection to be unique.

9.3.5 Creating New Types in the select Clause of a LINQ Query

The last LINQ query in the example (lines 74–75) selects the properties FirstName and LastName. The syntax


new {e.FirstName, e.LastName}

creates a new object of an anonymous type (a type with no name), which the compiler generates for you, based on the properties listed in the curly braces ({}). In this case, each new object of the anonymous type is initialized with the FirstName and LastName values from the corresponding Employee object. These selected properties can then be accessed when iterating over the results. Implicitly typed local variables allow you to use anonymous types because you do not have to explicitly state the type when declaring such variables.

When the compiler creates an anonymous type, it automatically generates a ToString method that returns a string representation of the object. You can see this in the program’s output—it consists of the property names and their values, enclosed in braces. Anonymous types are discussed in more detail in Chapter 22.

Projections

The query in lines 74–75 is an example of a projection, which transforms an object into a new form. In this case, the transformation creates new objects containing only the FirstName and LastName properties, but projections also can manipulate the data. For example, a projection that includes the MonthlySalary could give all employees a 10% raise by multiplying their MonthlySalary properties by 1.1 with the expression


e.MonthlySalary * 1.1M

Changing the Names of Properties in Anonymous Types

You can specify a new name for a selected property in an anonymous type. For example, if line 75 is written as


new {First = e.FirstName, Last = e.LastName}

the anonymous type would have properties named First and Last, rather than FirstName and LastName. If you don’t specify a new name, the property’s original name is used.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset