.NET Support for LINQ Operations
.NET Support for LINQ Operations
Selecting Records
Introduction
By now, we know that the most fundamental operation performed on a query is to select one or more records. In LINQ, this operation is performed with the select operator. Here is an example we used in the first lesson:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
var numbers = new double[] { 12.44, 525.38, 6.28, 2448.32, 632.04 };
var number = from n in numbers select n;
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
}
}
}
This code produced:
To support the select operation, the IEnumerable<> generic interface is equipped with a method named Select. It is overloaded with two versions. The syntax of one of the versions is:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
Since this is an extension method, the first argument indicates that the method will deal with an objct of type IEnumerable<TSource>. This also means that the first argument is not passed. As a result, this method takes one argument, which is passed as a lambda expression. That's the role of the Func<TSource, TResult> selector argument. The simplest way to call this method is to pass a name to it, followed by the => operator, followed by the same name passed as argument. After doing this, you can use a foreach loop to access the result of the query. Here is an example:
namespace Numbers { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { double[] numbers = new double[] { 12.44, 525.38, 6.28, 2448.32, 632.04 }; var number = numbers.Select<double, double>(n => n); foreach (var member in number) lbxNumbers.Items.Add(member.ToString()); } } }
Although Select is a generic method, you don't have to indicate the generic parameter(s). Therefore, the above code can be written as follows:
namespace Numbers { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { double[] numbers = new double[] { 12.44, 525.38, 6.28, 2448.32, 632.04 }; var number = numbers.Select(n => n); foreach (var member in number) lbxNumbers.Items.Add(member.ToString()); } } }
Sorting the Results of a Query
Introduction
We know that the LINQ supports record sorting with an operator named orderdy. Here is an example we saw already:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
var numbers = new double[] { 12.44, 525.38, 6.28, 2448.32, 632.04 };
var regularOrder = from n
in numbers
select n;
foreach (var member in regularOrder)
lbxRegularOrder.Items.Add(member.ToString());
var sortedOrder = from n
in numbers
orderby n ascending
select n;
foreach (var member in sortedOrder)
lbxSortedOrder.Items.Add(member.ToString());
}
}
}
This would produce:
To support the ability to sort the values of a query, the Enumerable class is equipped with various methods. To let you sort records in ascending order, the Enumerable class is equipped with a method named OrderBy. The method is overloaded with two versions. One of the versions uses the following syntax:
public static IOrderedEnumerable<TSource> OrderBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector);
This method takes a lambda expression as argument. This method should be called from the result of a query. The simplest way to call this method is to pass a named argument, a => operator, and the same name passed as argument. Here is an example:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
double[] numbers = new double[] { 12, 445, 25, 380, 6, 285, 2448, 32, 6320, 4 };
IEnumerable<double> number = numbers.Select(n => n);
IEnumerable<double> ordered = number.OrderBy(n => n);
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
foreach (var v in ordered)
lbxSelections.Items.Add(v.ToString());
}
}
}
Sorting Records in Descending Order
We already know that, to let you sort a query in reverse order, the LINQ provides the descending keyword. To provide its own support for this operation, in reverse order the IEnumerable class is equipped a method named OrderByDescending. It is overloaded with two versions. One of the versions uses the following syntax:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
This version of the method takes one argument, which is a lambda expression. You can call it the same way we did for the OrderBy() method. Here is an example:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
double[] numbers = new double[] { 12, 445, 25, 6320, 428, 380, 6, 285, 2448, 932 };
IEnumerable<double> number = numbers.Select(n => n);
IEnumerable<double> ordered = number.OrderByDescending(n => n);
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
foreach (var v in ordered)
lbxSelections.Items.Add(v.ToString());
}
}
}
This would produce:
Selecting a Tuple
The second version of the Enumerable.Select() uses the following syntax:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector);
The argument passed to this version, Func<TSource, int, TResult> selector is a lambda expression that takes two arguments. The second argument is like the one we introduced in the previous section. When the query runs, as the compiler is going through the list, the second argument, int, is the index of the item that the compiler is checking. The simplest way to call this version of the method is to first pass a tuple that has two names for the argument. This is followed by the => operator, followed by another tuple with the same names as the first tuple. This can be done as follows:
private void Exercise_Load(object sender, EventArgs e)
{
var numbers = new double[] { 12, 445, 25, 380, 6, 285, 2448, 32, 6320, 4 };
var number = numbers.Select((u, v) => (u, v));
}
This method returns a list of tuples, as a combination of two values for each item. To use the result, in your foreach loop, you can access each tuple as one value by getting the results as done in queries so far. Here is an example:
namespace Numbers { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { var numbers = new double[] { 12, 445, 25, 380, 6, 285, 2448, 32, 6320, 4 }; var number = numbers.Select((u, v) => (u, v)); // Accessing each item of the resulting tuple foreach (var member in number) lbxNumbers.Items.Add(member.ToString()); } } }
This code produced:
Because the result is a combination of tuples, to get one of the items of each result, you can access it by the name used in the LINQ expression, exactly as done in a regular tuple. Here are examples:
namespace Numbers { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { var numbers = new double[] { 12, 445, 25, 380, 6, 285, 2448, 32, 6320, 4 }; var number = numbers.Select((u, v) => (u, v)); // Accessing only the first items of the tuples foreach (var member in number) lbxNumbers.Items.Add(member.u.ToString()); // Accessin only the second items of the tuples foreach (var member in number) lbxSelections.Items.Add(member.v.ToString()); // Accessin both the first and the second items individualy foreach (var member in number) lbxValues.Items.Add(member.v.ToString() + " - " + member.u.ToString()); } } }
This code produced:
Chaining the Method Calls
Combining Queries
In the above sections, we learned to create queries and to sort them. We declared an additional variable for the ordering operation. You have probably noticed that the Select() method returns an IEnumerable<> list. The OrderBy() and the OrderByDescending() methods each returns an IOrderedEnumerable<> list. The IOrderedEnumerable<> object implements the IEnumerable<> interface. This means that the Select(), the OrderBy(), and the OrderByDescending() methods all produce, directly or indirectly, an IEnumerable<> list. This gives you the ability to call one of those methods directly outside the parentheses of another. This is referred to as chaining the method calls. Here is an example:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
double[] numbers = new double[] { 12, 445, 25, 6320, 428, 380, 6, 285, 2448, 932 };
IEnumerable<double> number = numbers.Select(n => n);
IEnumerable<double> ordered = numbers.Select(n => n).OrderBy(n => n);
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
foreach (var v in ordered)
lbxSelections.Items.Add(v.ToString());
}
}
}
In the same way, you can add a method call to another, then the next call to the other, and so on.
Combining Method Calls
A typical application uses various objects and their related operations. Sometimes, you declare different variables, assign some method calls to them, and perform various types of operations. In some cases, some method calls are needed only once, and their calls may be related to other methods. To simplify some of those operations, you can call one method after another. When doing that, you must make sure that a method call returns a value or object that is the type of the method on which it is called. Consider the following example:
namespace Numbers { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { double[] numbers = new double[] { 445.27, 25.83, 6320.072, 38.6028, 285.37, 2448.69, 932.2938 }; IEnumerable<double> number = numbers.Select(n => n); foreach (var member in number) lbxNumbers.Items.Add(member.ToString()); List<double> lstNumbers = number.ToList(); lstNumbers.Add(3906.805); lstNumbers.Add(293.74); foreach (var member in lstNumbers) lbxValues.Items.Add(member.ToString()); IEnumerable<double> ordered = numbers.OrderBy(n => n); foreach (var v in ordered) lbxSelections.Items.Add(v.ToString()); lstNumbers.Add(82741.39); double[] finals = lstNumbers.ToArray(); IEnumerable<double> things = finals.OrderBy(n => n); foreach (var member in things) lbxFinals.Items.Add(member.ToString()); } } }
This would produce:
Imagine that, out of all these operations, you are interested only in the final result. In this case, you don't need all the intermediary operations. You can simply perform a chain call on all those methods. Here is an example:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
IEnumerable<double> numbers = new double[] { 25.83, 6320.072, 38.6028, 285.37, 445.27, 2448.69, 932.2938 };
List<double> lstNumbers = numbers.Select(n => n).ToList();
foreach (var member in lstNumbers)
lbxNumbers.Items.Add(member.ToString());
lstNumbers.Add(3906.805);
lstNumbers.Add(82741.39);
lstNumbers.Add(293.74);
double[] values = lstNumbers.Select(n => n).ToList().OrderBy(n => n).ToArray();
foreach (var member in values)
lbxSelections.Items.Add(member.ToString());
}
}
}
This would produce:
To make your code easy to read, especially if you are making many method calls and the line for that code becomes too long, you can write the method calls on different lines. This can be done as follows:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
IEnumerable<double> numbers = new double[] { 25.83, 6320.072, 38.6028, 285.37, 445.27, 2448.69, 932.2938 };
List<double> lstNumbers = numbers.Select(n => n).ToList();
foreach (var member in lstNumbers)
lbxNumbers.Items.Add(member.ToString());
lstNumbers.Add(3906.805);
lstNumbers.Add(82741.39);
lstNumbers.Add(293.74);
double[] values = lstNumbers.Select(n => n)
.ToList()
.OrderBy(n => n)
.ToArray();
foreach (var member in values)
lbxSelections.Items.Add(member.ToString());
}
}
}
Conditionally Selecting Records
Introduction
As we know already, the LINQ supports conditions in many ways. To start, if you are creating a query by calling the IEnumerable<>.Select() method, you can apply a condition to the TResult argument.
Creating a Regular Boolean Expression
You can use any of the logical operators we reviewed already. To do this, if you are calling the first version of the method, pass a name to it, followed by the => operator. After the => operator, create a Boolean expression that uses the same name passed as argument. Here is a simple example:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
var numbers = new double[] { 12, 445, 25, 380, 6, 285, 2448, 32, 6320, 4 };
var number = numbers.Select(n => n is >= 250);
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
}
}
}
This would produce:
In the same way, you can create any type of logical expression you want after the => operator with any types of combinations.
Where is the Condition?
Remember that the LINQ supports conditions with an operator named where. Here is an example we saw in the second lesson. Here is an example:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
var numbers = new double[] { 4, 2448, 6, 12, 6320, 25, 32, 10406, 285, 445, 380 };
var number = from n
in numbers
where n < 445
orderby n
select n;
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
}
}
}
This would produce:
To support conditions of a query, the Enumerable class is equipped with a method named Where. It is overloaded with two versions. The syntax of one the methods in the IEnumerable<> interface is:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
Remember that the this argument is an extension and will not be passed when calling the method. The unique argument of this version of the method is a lambda expresion that takes a logical expression as argument and returns a Boolean value as True or False. Here is an example of calling it:
namespace Numbers
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
var numbers = new double[] { 4, 2448, 6, 12, 6320, 25, 32, 10406, 285, 445, 380 };
var number = numbers.Where<double>(n => n is < 445);
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
}
}
}
This code would produce the same result we saw above. Remember that the parameter type, in this case <double>, is optional.
Getting the Index of a Result
The other version of the IEnumerable<>.Where() uses the following syntax:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);
This version adds a second argument. It is the index of the list member that is accessed during looping. Normally, this argument works the same way we saw for the IEnumerable<>.Select() method.
Conjunctions and Disjunctions
Nesting a Condition
We already know that we can nesting a query in another. Either or both the nesting and the query can have conditions. Consider the following query:
namespace Numerotation
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
IEnumerable<int> numbers = new int[] { 89, 7912, 9445, 1372, 583, 8099, 1285, 6028, 24482, 93832 };
var original = from n
in numbers
select n;
var lessThan445 = from val
in numbers
where val < 7525
select val;
var values = from nbr
in from nbr
in numbers
where nbr < 7525
select nbr
where nbr > 555
select nbr;
// Whole List
foreach (var member in original)
lbxNumbers.Items.Add(member.ToString());
// Numbers that are inferior to 7525
foreach (var v in lessThan445)
lbxSelections.Items.Add(v.ToString());
// Numbers between 555 and 7525
foreach (var w in values)
lbxValues.Items.Add(w.ToString());
}
}
}
This would produce:
To nest a conditional query, you can write the in place of the list of the outside query. You can first declare a variable and write that variable in place of the list in the main query. Here is an example:
namespace Numerotation { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { IEnumerable<int> numbers = new int[] { 89, 7912, 9445, 1372, 583, 8099, 1285, 6028, 24482, 93832 }; var original = numbers.Select(n => n); var lessThan445 = numbers.Where(val => val < 7525); var values = from nbr in lessThan445 where nbr > 555 select nbr; // Whole List foreach (var member in original) lbxNumbers.Items.Add(member.ToString()); // Numbers that are inferior to 7525 foreach (var v in lessThan445) lbxSelections.Items.Add(v.ToString()); // Numbers between 555 and 7525 foreach (var w in values) lbxValues.Items.Add(w.ToString()); } } }
A better solution is to write the query in place of the list. This can be done as follows:
namespace Numerotation
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
IEnumerable<int> numbers = new int[] { 89, 7912, 9445, 1372, 583, 8099, 1285, 6028, 24482, 93832 };
var original = numbers.Select(n => n);
var lessThan445 = numbers.Where(val => val < 7525);
var values = from nbr
in numbers.Where(val => val < 7525)
where nbr > 555
select nbr;
}
}
}
After doing this, you can create the external query with its own Where() call and call the nested Where() call from it. Here is an example:
namespace Numerotation
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
IEnumerable<int> numbers = new int[] { 89, 7912, 9445, 1372, 583, 8099, 1285, 6028, 24482, 93832 };
var original = numbers.Select(n => n);
var lessThan445 = numbers.Where(val => val < 7525);
var values = numbers.Where(val => val < 7525).Where(nbr => nbr > 555);
}
}
}
Remember that you can chain method calls to add operations to a query. Here is an example:
namespace Numerotation
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
IEnumerable<int> numbers = new int[] { 89, 7912, 9445, 1372, 583, 8099, 1285, 6028, 24482, 93832 };
var values = numbers.Select(n => n).Where(val => val < 7525).Where(nbr => nbr > 555).OrderBy(n => n);
foreach (var w in values)
lbxNumbers.Items.Add(w.ToString());
}
}
}
This would produce:
Creating a Conjunction
The ability to call a IEnumerable<>.Where() method after another IEnumerable<>.Where() call provides one way to create a conjunction. In reality, you can create a conjunction as an argument to a Where() method. Here is an example:
namespace Numerotation { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { IEnumerable<int> numbers = new int[] { 89, 7912, 9445, 1372, 583, 8099, 1285, 6028, 24482, 93832 }; var values = numbers.Where(val => val is < 7525 & val > 555); foreach (var w in values) lbxNumbers.Items.Add(w.ToString()); } } }
This would produce:
Creating a Disjunction
To create a disjunction in your query, in the parentheses of your Where() call, pass a lambda expression that includes a logical disjunction. You use the ∧, the |, or the || operators exactly as we reviewed in previous lessons.
LINQ and Numbers
A Range of Numbers
We know how to create an array of (random) natural numbers and store it in a variable to use a query. Here is an example:
namespace Numerotation
{
public partial class Exercise : Form
{
public Exercise()
{
InitializeComponent();
}
private void Exercise_Load(object sender, EventArgs e)
{
var numbers = new int[] { 12, 44, 525, 38, 6, 28, 2448, 32, 632, 04 };
var number = from n in numbers select n;
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
}
}
}
This would produce:
In some cases, you may want to work on a consecutive list of numbers such as 1, 2, 3, 4, 5, 6, 7, and 8. Instead of declaring a formal variable, the Enumerable class provides a method named Range that allows you to specify the first number of a range and a count of consecutive numbers to add to create a range. The syntax of the Enumerable.Range() method is:
public static IEnumerable<int> Range(int start, int count);
The first argument passed to this method is the beginning of the range. The second argument specifies how many numbers to add consecutively from the first. To use this method, you can declare a variable of type IEnumerable and assign a call to Enumerable.Range() that receives both arguments. Here is an example:
private void ExerciseLoad(object sender, EventArgs e)
{
IEnumerable<int> range = Enumerable.Range(22, 8);
var number = from n in range select n;
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
}
Of course, you can also declare the variable as type var. You would receive the same result:
If you want to restrict the result, you can add a where condition to it. Here is an example:
private void ExerciseLoad(object sender, EventArgs e)
{
var range = Enumerable.Range(96, 10);
var number = from n
in range
where n % 2 == 0
select n;
foreach (var member in number)
lbxNumbers.Items.Add(member.ToString());
}
Counting the Number of Records
When you create a LINQ statement, it produces a list. To let you get the number of records in that list, the Enumerable class is equipped with a method named Count. This method is overloaded with two versions whose syntaxes are:
public static int Count<TSource>(this IEnumerable<TSource> source); public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
The first version doesn't take any argument. You can simply call it from a query. Here is an example:
IEnumerable<StoreItem> sale = storeItems.Select(item => item); foreach (var item in sale) { ListViewItem lviCollection = new ListViewItem(item.ItemCode.ToString()); lviCollection.SubItems.Add(item.Category); lviCollection.SubItems.Add(item.ItemName); lviCollection.SubItems.Add(item.UnitPrice.ToString()); lvwStoreItems.Items.Add(lviCollection); } Text = "Music Store - Total Inventory: " + sale.Count().ToString() + " store items";
This would produce:
Remember that you can still use the var keyword to declare the variable that would hold the resulting list. The same var keyword can be used for the result of a method call. Here are examples:
IEnumerableempls = from staffMembers in employees orderby staffMembers.LastName select staffMembers; var total = empls.Count();
Since we have determined that a LINQ statement produces an Enumerable list, if you don't need the list itself, you can declare a variable that is the type returned by a method, put the statement in parentheses, and then access the method outside the closing parenthesis using the period operator. Here is an example:
var total = (from staffMembers in employees orderby staffMembers.LastName select staffMembers).Count();
Remember that the IEnumerable.Count() method returns the number of items in the result of the LINQ statement, not the number of items in the original list. The following examples illustrate it:
private void Exercise_Load(object sender, EventArgs e) { var students = new Student[] { . . . No Change }; IEnumerable<Student> pupils = from studs in students select studs; int boys = (from males in pupils where males.Gender == Genders.Male select males).Count(); int girls = (from females in pupils where ((females.Gender != Genders.Male) && (females.Gender != Genders.Unknown)) select females).Count(); ListViewItem lviStudent = null; ListViewGroup grpBoys = new ListViewGroup("Boys"); ListViewGroup grpGirls = new ListViewGroup("Girls"); ListViewGroup grpOthers = new ListViewGroup("Others"); foreach (var std in pupils) { if (std.Gender == Genders.Male) { lviStudent = new ListViewItem(std.StudentNumber.ToString(), grpBoys); } else if (std.Gender == Genders.Female) { lviStudent = new ListViewItem(std.StudentNumber.ToString(), grpGirls); } else { lviStudent = new ListViewItem(std.StudentNumber.ToString(), grpOthers); } lviStudent.SubItems.Add(std.FirstName); lviStudent.SubItems.Add(std.LastName); lvwStudents.Items.Add(lviStudent); lvwStudents.Groups.Add(grpBoys); lvwStudents.Groups.Add(grpGirls); lvwStudents.Groups.Add(grpOthers); } txtBoys.Text = boys.ToString(); txtGirls.Text = girls.ToString(); }
This would produce:
These different techniques of calling and using the Count() method will be applied to most other methods of the Enumerable class.
An Average of Numbers
If the values you are querying are numbers, you may want to find their average. To assist you, the Enumerable class provides a method named Average that is overloaded with a version for each numeric data type. The syntax for the double type is:
public static double Average(this IEnumerablesource);
Here is an example of calling this method:
namespace Numerotation { public partial class Exercise : Form { public Exercise() { InitializeComponent(); } private void Exercise_Load(object sender, EventArgs e) { var numbers = new double[] { 12.44, 525.38, 6.28, 2448.32, 632.04 }; var number = from n in numbers select n; foreach (var member in number) lbxNumbers.Items.Add(member.ToString()); txtAverage.Text = number.Average().ToString(); } } }
This would produce:
If it is only the average that you want, you can include the LINQ statement in parentheses and call the Average method outside. Here is an example:
var average = (from n in numbers select n).Average(); txtAverage.Text = average.ToString();
Of course, you can add a where condition if you want to restrict the result. The LINQ provides many more methods for numeric values.
Previous | Copyright © 2008-2023, FunctionX, Inc. | Wednesday 15 September 2021 | Home |
|