Lambda Expressions and Linq-to-Object

In this tutorial, we will learn how to write LINQ statements to query objects in List type collections. We will also learn about Lambda Expressions. For this purpose, we will implement a simple Course Management application.

The Web Controls We Need

The interface of the application will have only two controls: RadioButtonList (rdn_Departments) and ListBox (lst_Courses). It should look like the following image:

1

Classes

In our application we will have two classes: Department and Course. Please see the following code for the declaration of the classes:

[Serializable]
public class Department
{
    public int Id { get; set; }

    public string DeptName { get; set; }

    public int StudentCount { get; set; }
}

[Serializable]
public class Course
{
    public int Id { get; set; }

    public string CourseCode { get; set; }

    public int CreditHour { get; set; }

    public string FullTitle
    {
         get { return CourseCode + " (" + CreditHour + ")"; }
    }

    public int DeptId { get; set; }
}

Creating Some Data

Please note that we built a relationship between Course and Department classes. That is, Course class has a DeptId property indicating the department that the course belongs to. Let’s switch to CodeBehind file. We need to define a method that creates some Department and Course objects and insert them to a list, and stores the lists to ViewState. We will pretend that ViewState is our database in this small application.

public void GenerateData()
{
    if (ViewState["departments"] == null || ViewState["courses"] == null)
    {
        Department department1 = new Department { Id = 1, DeptName = "CSCI", StudentCount = 20 };
        Department department2 = new Department { Id = 2, DeptName = "MIS", StudentCount = 30 };
        Department department3 = new Department { Id = 3, DeptName = "EDIT", StudentCount = 40 };

        List<Department> departments = new List<Department> { department1, department2, department3 };
        ViewState["departments"] = departments;

        Course course1 = new Course { Id = 1, CourseCode = "EDIT4160", CreditHour = 4, DeptId = 3 };
        Course course2 = new Course { Id = 2, CourseCode = "EDIT2160", CreditHour = 2, DeptId = 3 };
        Course course3 = new Course { Id = 3, CourseCode = "EDIT5160", CreditHour = 5, DeptId = 3 };
        Course course4 = new Course { Id = 4, CourseCode = "EDIT3160", CreditHour = 3, DeptId = 3 };
        Course course5 = new Course { Id = 5, CourseCode = "CSCI3000", CreditHour = 3, DeptId = 1 };
        Course course6 = new Course { Id = 6, CourseCode = "CSCI4000", CreditHour = 4, DeptId = 1 };
        Course course7 = new Course { Id = 7, CourseCode = "CSCI5000", CreditHour = 5, DeptId = 1 };
        Course course8 = new Course { Id = 8, CourseCode = "MIS6000", CreditHour = 6, DeptId = 2 };
        Course course9 = new Course { Id = 9, CourseCode = "MIS2000", CreditHour = 2, DeptId = 2 };
        Course course10 = new Course { Id = 10, CourseCode = "MIS1000", CreditHour = 1, DeptId = 2 };

        List<Course> courses = new List<Course>();
        {
             course1, course2, course3,
             course4, course5, course6,
             course7, course8, course9, course10,
        };
        ViewState["courses"] = courses;
    }
}

Binding Data to List Controls

Let’s define a method that binds the existing data (departments, and courses) to list controls.

public void BindData()
{
    rdn_Departments.DataSource = ViewState["departments"];
    rdn_Departments.DataTextField = "DeptName";
    rdn_Departments.DataValueField = "Id";
    rdn_Departments.DataBind();

    lst_Courses.DataSource = ViewState["courses"];
    lst_Courses.DataTextField = "FullTitle";
    lst_Courses.DataValueField = "Id";
    lst_Courses.DataBind();
}

Now, we need to call these two methods in Page_Load event:


protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!IsPostBack) { GenerateData(); BindData(); } 
}

If you run the application, you should be able to see both list controls are populated with the data we just created:

2

Now, let’s add more functionality: When a department is selected, only the courses that belong to the selected department should be listed. To do that we will double-click on the RadioButtonList control to create the event handler that will be executed when the selected value of the RadioButtonList changes:


protected void rdn_Departments_SelectedIndexChanged(object sender, EventArgs e)
{
    
}

Where Clauses in LINQ and Predicates

Now, let’s use Where clause of LINQ. The where clause is used to specify which items from the data source will be returned. It does this by using a Predicate. Predicates are functions that return true or false. They provide a convenient way of testing the element from the data source with a condition statement. For example, in our case, where clause will iterate through the list items and use a Predicate to a check if the item meets the specified condition, if it does, the item will be returned, otherwise it will be filtered out.

In the following code, we created a predicate called myCoursePredicate, and then used it in the where clause to filter out all the courses except those with matching DeptId.

protected void rdn_Departments_SelectedIndexChanged(object sender, EventArgs e)
{
      List<Course> allCourses = (List<Course>) ViewState["courses"];

      List<Course> coursesfiltered = allCourses.Where(myCoursePredicate).ToList();

      lst_Courses.DataSource = coursesfiltered;
      lst_Courses.DataBind();
}

private bool myCoursePredicate(Course course)
{
      int deptId = Convert.ToInt32(rdn_Departments.SelectedValue);
      return (course.DeptId == deptId);
}

Anonymous Functions

Instead of defining the Predicate function separately, we can define an anonymous function using the delegate keyword as the predicate for the where clause:


protected void rdn_Departments_SelectedIndexChanged(object sender, EventArgs e)
{
      List<Course> allCourses = (List<Course>) ViewState["courses"];

      int deptId = Convert.ToInt32(rdn_Departments.SelectedValue);

      List<Course> coursesfiltered = allCourses
                                     .Where(
                                         delegate(Course course)
                                         {return course.DeptId == deptId}
                                      )
                                     .ToList();

      lst_Courses.DataSource = coursesfiltered;
      lst_Courses.DataBind();
}

Lambda Expressions

This will function the same. However, it reduces the complexity since it help achieve the same functionality with less code. Anonymous functions do not have a name and just created and used for an instant need. Therefore, they are not accessible in any other place in your code. We could even simplify our code with the help of Lambda Expressions. After introduced with C# 3.0, Lambda Expressions supersede Anonymous functions because of their simplicity.


protected void rdn_Departments_SelectedIndexChanged(object sender, EventArgs e)
{
      //other code not shown here
      List<Course> coursesfiltered = allCourses
                                     .Where(course => course.DeptId == deptId)
                                     .ToList();

      //the rest of the code not shown here
}

In the Lambda Expression, we use a notation (=>) pronounced as “Goes To”. So, on the left hand-side of   =>   we have our input parameter. The lambda expression can infer that the input parameter (whatever its name is) is a type of Course class because where clause is applied to a list of Course objects. The right hand-side of   =>   is the function that evaluates the input parameter based on a condition that returns true or false.

We could write a more complex where clause that contains multiple conditions. Let’s find the courses with 3 or more credit-hour of the selected Department:


List<Course> coursesfiltered = allCourses
                               .Where(course => course.DeptId.Equals(deptId) 
                                                && course.CreditHour >= 3)
                               .ToList();

We used && characters to define another condition. In the second condition we can still access course object since we are on the right-hand side of the  =>  notation. We do not need to and SHOULD NOT re-write   course =>   after &&. Also, we used Equals method instead of  ==  to check if the deptId matches.

Or, we could find the courses with course code starting with, let’s say M :


List<Course> coursesfiltered = allCourses
                               .Where(course => course.DeptId == deptId
                                      && course.CourseCode.StartsWith('M'))
                               .ToList();

Did you notice, we repeated the course.DeptId == deptId statement each time in our query. We can get rid of this repetition in two ways. First we can create two lists each of which has a simple where clause:


List<Course> coursesOfDepartment = allCourses
                                   .Where(course => course.DeptId == deptId)
                                   .ToList();

List<Course> coursesFiltered = coursesOfDepartment
                               .Where(course.CourseCode.StartsWith('M'))
                               .ToList();

What we have done in the code above, we created a new List of courses based on the selected department and store it in the memory of the server. Then, we created another list in the memory by using the second Where clause which returns courses with M the first letter in their course codes. Apparently, this approach did not use the system resources wisely.

IEnumerable: Deferred Execution

The second approach would be not converting the result  set to List type and instead keeping the result set in IEnumerable<> type, which is the default return type of Where clause.


IEnumerable<Course> courses1 = allCourses
                               .Where(course => course.DeptId == deptId);

IEnumerable<Course> coursesM = courses1
                               .Where(course.CourseCode.StartsWith('M'));

IEnumarable<Course> coursesCH = courses1
                               .Where(course.CreditHour >= 3);

Compared to ToList method which evaluates the query immediately and puts result set into a list (using system memory), this second approach returns an IEnumerable<> that contains all the information needed to run the query later on. With IEnumerable<> the whole collection does not need to be loaded into the memory. When we use IEnumerable, we allow compiler to defer the execution of the query until later. However, when we use ToList(), we force the compiler to execute the query and get the results at that moment.

Therefore, whenever we need to “stack” LINQ expressions, we better use IEnumerable as seen in the example above. This can be very useful in certain circumstances, for instance if you are querying a large database table, we would not want to copy the entire table to the server memory before processing its specific rows.

Let’s do more practice with different Linq expressions.

First and FirstOrDefault

First() returns the first item in a collection. The following will return the first item in the List object allDepartments:


Department myDept = allDepartments.First();

We can use a condition with First(). With the following linq expression, we will find the first department in the list of departments with more than 20 students:


Department myDept = allDepartments
                    .First(dept => dept.StudentCount > 20);

You should use First() when you are sure that the sequence will have at least one element, otherwise it will throw an exception when there are no results.  However, you should better use FirstOrDefault() if you think that the resulting sequence might be empty, in other words, when you need to check whether there was an element or not. FirstOrDefault() will not throw a null exception. Instead, it will return either null (reference types) or the default value of the value type (for example ‘0’ for an int.)

The following linq expression checks if there is a department with StudentCount more than 50.

Department myDept = allDepartments.First(dept => dept.StudentCount > 50);

if(myDept != null){

     //Do stuff

}else{

     //show message: no records found

}

OrderBy, OrderByDescending, ThenBy, and ThenByDescending

OrderBy()/OrderByDescending() is used to sort a collection in an ascending/descending order. Let’s sort the departments alphabetically based on their names:


IEnumerable <Department> myDepts = allDepartments
                                   .OrderBy(dep t=> dept.DeptName);

If we want to sort the departments first by DeptName and then by StudentCount, then we need to use ThenBy()/ThenByDescending().


IEnumarable<Department> myDepts = allDepartments.OrderBy(dept => dept.DeptName).ThenBy(dept => dept.StudentCount);

IENumerable<Department> myDepts2 = allDepartments
                                   .OrderBy(dept => dept.DeptName)
                                   .ThenByDesceding(dept => dept.StudentCount);

If you used OrderBy statement again instead of ThenBy, then you will call OrderBy multiple times. Each OrderBy will reorder the sequence, overwriting the previous OrderBy results, so the final OrderBy call will be the dominant one.

Count, Sum, Min, and Max

The following Linq statement returns the number of departments with student count more than 20:


int count = allDepartments.Count(dept => dept.StudentCount > 20);

The following statement calculates the sum of the student count in all departments. Min and Max also works with same logic but returns the Min/Max value in the sequence.


int count = allDepartments.Sum(dept => dept.StudentCount);
int maxStdCount = allDepartments.Max(dept => dept.StudentCount);
int minStdCount = allDepartments.Min(dept => dept.StudentCount);

Select

We use Select() to return the list of the specific properties in the sequence. For example we can return the list of the course names and store it in a string IEnumarble by using Select:


IEnumerable<string> coursetitles = allCourses.Select(c => c.CourseCode);

Partitioning: Take, Skip, TakeWhile, and SkipWhile

We can do some partitioning on the data by using linq. We can use Take to read a certain number of elements from the collection. For example, the following code returns the first 3 courses:

IEnumarable<Course> courses = allCourses.Take(3);

In the following example we will use Skip() to retrieve the courses except the first 3 one:


IEnumerable<Course> courses = allCourses.Skip(3);

What if we want to retrieve the 4th, 5th, and 6th courses in our list? Then we can combine Take and Skip together like this:


IEnumberable<Course> courses = allCourses.Skip(3).Take(3);

We can use TakeWhile to return the elements from a sequence from the beginning until a condition holds. The following example returns the courses until the one with credit-hour 1:


IEnumerable<Course> courses = allCourses

                              .TakeWhile(course => course.CreditHour > 1);

We can implement the reverse functionality by using SkipWhile. The following example skips all the elements until the one with credit-hour 1:


IEnumerable<Course> courses = allCourses

                              .SkipWhile(course => course.CreditHour > 1);

This tutorial does not cover entire Linq functionality. Here is a very useful resource that illustrates all linq expressions and methods with 101 Examples: https://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b

2 thoughts on “Lambda Expressions and Linq-to-Object

  1. Pingback: Binding List Controls with OOP Approach | Asp.Net (C#) and Entity Framework Code-First

Leave a comment