Using LINQ - Storing Data - Beginning Object-Oriented Programming with C# (2012)

Beginning Object-Oriented Programming with C# (2012)

Part IV

Storing Data

Chapter 15

Using LINQ

What you will learn in this chapter:

· What LINQ is

· How to query data objects using LINQ

· The meaning of the var keyword

· The required LINQ namespaces

· The advantages of using LINQ to query data objects

wrox.com code downloads for this chapter

You can find the wrox.com code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=9781118336922 on the Download Code tab. The code in the Chapter15 folder is individually named according to the names throughout the chapter.

SQL is so simple to use; wouldn't it be nice if you could apply its simplicity to data structures other than databases? That's what Microsoft's LINQ is designed to do. LINQ stands for Language INtegrated Query and is a feature that appeared with the debut of Visual Studio 2008.

Using LINQ

Although you can use SQL with relational database objects, LINQ can also query object types where the data source is not a database. You can use LINQ to query an object type, including arrays, class objects, and XML, in addition to relational databases. Visual Studio incorporates the LINQ query engine directly but also has defined an extension definition that enables third-party data sources to tie in to the engine via a translator. Just as SQL queries result in datasets stored in memory, LINQ returns a collection of memory-based objects.

Query Keywords

SQL has specific keywords used in query statements, and LINQ provides a similar set of keywords. Perhaps the easiest way to begin to understand LINQ is to try the following simple example.

Try It Out: A LINQ Program Example (Chapter15ProgramLINQTest.zip)

In this example, the program generates MAXNUM (such as 100) random numbers and displays them in a listbox object. The program then uses LINQ to query that data list for values that fall within a specified range. Figure 15.1 shows a sample run of the program.

1. Create a new project in the normal way.

2. Download the code in the Chapter15ProgramLINQTest.zip file on this book's web page at wrox.com.

3. Create a user interface with two listboxes, five labels, two textboxes, and two button objects using Figure 15.1 as a model.

Figure 15.1 Sample run

image

The user enters an upper and lower (exclusive) boundary for the values to be extracted from the list of random values shown in the listbox on the left. The specified range is set by the values specified in the two textbox objects. The result of the query is shown in the right listbox object inFigure 15.1.

How It Works

The code for the program is shown in Listing 15-1.

Listing 15-1: Program Using LINQ (frmMain.cs)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows.Forms;

public class frmMain : Form

{

private const int MAXNUM = 100; // Max random numbers

static List<int> numbers = new List<int>(); // static list

private Button btnClose;

private ListBox lstOutput;

private ListBox lstFull;

private TextBox txtLow;

private Label label1;

private Label label2;

private TextBox txtHi;

private Label label3;

private Label label4;

private Label label5;

private Button btnCalc;

#region Windows code

//========================== Constructor ============================

public frmMain()

{

InitializeComponent();

GenerateRandomValues();

}

//=========================== Program Start =========================

public static void Main()

{

frmMain main = new frmMain();

Application.Run(main);

}

//========================= Helper Methods ===========================

private void btnCalc_Click(object sender, EventArgs e)

{

int lo;

int hi;

lstOutput.Items.Clear();

SetTheLimits(out lo, out hi);

DoLINQQuery(lo, hi);

}

/****

* Purpose: To generate a MAXNUM sequence of random integer values

*

* Parameter list:

* int lo the lower limit for query

* int hi the upper limit for query

*

* Return value:

* void

*

****/

private void DoLINQQuery(int lo, int hi)

{

var query = from p in numbers // The "Query"

where p > lo && p < hi

select p;

foreach (var val in query) // Display results

{

lstOutput.Items.Add(val.ToString());

}

}

/****

* Purpose: Set the upper and lower limits of the query

*

* Parameter list:

* out int lo reference to the lower limit

* out int hi reference to the upper limit

*

* Return value:

* void

*

****/

private void SetTheLimits(out int lo, out int hi)

{

bool flag = int.TryParse(txtLow.Text, out lo); // Input validation

if (flag == false)

{

MessageBox.Show("Numeric only, 0 to 100", "Input Error");

txtLow.Focus();

}

flag = int.TryParse(txtHi.Text, out hi);

if (flag == false)

{

MessageBox.Show("Numeric only, 0 to 100", "Input Error");

txtHi.Focus();

}

}

/****

* Purpose: To generate a MAXNUM sequence of random integer values

*

* Parameter list:

* void

*

* Return value:

* void

*

****/

private void GenerateRandomValues()

{

int temp;

DateTime current = DateTime.Now;

Random rnd = new Random((int)current.Ticks);

for (int i = 0; i < MAXNUM; i++) // Random values

{

temp = rnd.Next(MAXNUM);

numbers.Add(temp); // Copy into list

lstFull.Items.Add(temp.ToString());

}

}

private void btnClose_Click(object sender, EventArgs e)

{

Close();

}

}

Namespaces and References for LINQ

In this example you use a generic List object, so you need to include the collections-generic namespace in the program. The necessary using statements for the sample program are as follows:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows.Forms;

To have the proper references available, you need to add a few new references to the standard list. Your reference list should include these:

System

System.Core

System.Data

System.Data.DataSetExtensions

System.Data.Linq

System.Drawing

System.Windows.Forms

The new references provide the necessary libraries to use LINQ. (These references don't appear in the code listing but are in the Reference section of the project.)

The btnCalc_Click() event code does all the work. You begin by validating the upper and lower limits for the domain of random numbers generated by an object named rnd of the Random class. Recall that the statement,

List<int> numbers = new List<int>();

defines an object (numbers) that can store a list of int data. The first for loop simply defines a random number between 0 and MAXNUM and sets it into the numbers List object and copies the value to the lstFull listbox object. MAXNUM does double duty, controlling both the upper limit of the random number and the number of random values that are generated. This is SDC (Sorta Dumb Code) because it's rarely a good idea to have a symbolic constant to serve dual purposes. If this is nettlesome to you, add a new constant of your own choosing.

The var Keyword

The following statement illustrates the syntax of a LINQ query. The query statement defines a variable named query, which is of type var:

var query = from p in numbers // The "Query"

where p > lo && p < hi

select p;

A type var appears to enable you to define a variable without specifying what it is. You can, for example, have a definition like this:

var myVar = 61;

This leads some programmers to conclude that var is the same as the object data type. But var is truly different from object. You can prove this to yourself with the following statements:

var myVar = 61;

object myObj = 61;

myVar += 1;

myObj += 1;

The last line generates a compiler error stating that the += operator can't be applied to types object and int. You can further confirm that var is different from an object by attempting to assign myVar and myObj into a primitive value type, such as:

long big1 = myVar;

long big2 = myObj;

The first statement compiles without complaint, but the second draws a type mismatch error and suggests using a cast to fix the problem.

Finally, try to compile the statements:

myVar = "Lynne York";

myObj = "Tom Bangert";

Variable myObj sails through the compiler without causing complaint, but myVar draws an error message stating that it cannot convert a string to an int. This is the key difference between the var type and an object type: var is strongly typed. It also means that var infers its type from the context in which it is used.

So how does the compiler infer var's type? Its type is set at the time it is initialized. You cannot define a var type without initializing it as part of its definition. That is, the statement,

var myVar; // Causes compiler error!

draws a compiler error stating:

Implicitly-typed local variables must be initialized

Therefore, the actual type that var assumes is dictated by the expression that initializes it. Because the compiler must type the var variable when it is defined, any var type definitions must be initialized at the point of their definition.

Using var in a Query

Consider this statement from Listing 15-1:

var query = from p in numbers // The "Query"

where p > lo && p < hi

select p;

Because numbers is defined generically as a list of type int data, query assumes an int data type. The statement works like a FROM-WHERE clause in SQL to examine the numbers data set and extract those values that fall between lo and hi (exclusively). Think of the values that fall within the specified range as becoming part of a temporary data set named p, which is then assigned into query.

The statement,

foreach (var val in query) // Display results

lstOutput.Items.Add(val.ToString());

defines a second var type named val that, because of the initialization context in which it is defined, assumes the same data type that the query holds. The foreach loop simply iterates over the query collection and adds the values in query to the lstOutput listbox object.

Although LINQ doesn't give you anything that you couldn't write yourself without using it, LINQ sure makes it easier. The added bonus is that its syntax mimics SQL's, making it easier to use if you already know SQL.

In the following Try It Out, you recode the program to use LINQ and string data rather than integer data. The basics are much the same, as you can see in Listing 15-2.

Try It Out: Another LINQ Example Using String Data (Chapter15ProgramLINQExample02.zip)

Now try another LINQ example. In this example, assume that the data are names followed by that person's state. The user types in a state abbreviation for the state she wants to examine from those shown in the left listbox object and then clicks the Calculate button. The right listbox object then displays those entries that match the selected state abbreviation. Figure 15.2 shows a sample run of the program.

Figure 15.2 Sample run using LINQ with string data

image

The following steps detail how to set up the sample project.

1. Create a new project in the normal manner.

2. Download the project source code from file Chapter15ProgramLINQExample02.zip.

3. Create a user interface similar to that shown in Figure 15.2.

How It Works

The code for the program is shown in Listing 15-2. The code is similar to that shown in Listing 15-10, so I limit my comments to those sections that are different. First, note that I define two static variables (passes and count) near the top of the listing. I use the static specifier because I want those variables available the instant the program loads. After the frmMain() constructor builds the form's image in memory and initializes its data, the left listbox object is filled in via a call to ShowAll().

Listing 15-2: Using LINQ with String Data (frmMain.cs)

using System;

using System.Linq;

using System.Windows.Forms;

public class frmMain : Form

{

static int passes = 0;

static int count;

private Button btnClose;

private ListBox lstOutput;

private ListBox lstFull;

private Label label1;

private TextBox txtState;

private Button btnCalc;

#region Windows code

public frmMain()

{

InitializeComponent();

ShowAll();

}

public static void Main()

{

frmMain main = new frmMain();

Application.Run(main);

}

private void btnCalc_Click(object sender, EventArgs e)

{

ShowAll();

}

private void ShowAll()

{

int i;

var friends = new[] {

new {name = "Tom", state = "IN"},

new {name = "Terry", state = "MT"},

new {name = "Tammy", state = "IN"},

new {name = "Jane", state = "OH"},

new {name = "Don", state = "IN"},

new {name = "John", state = "IN"},

new {name = "Linda", state = "FL"},

};

if (passes == 0)

{

count = friends.GetUpperBound(0);

i = 0;

for (i = 0; i <= count; i++)

{

lstFull.Items.Add(friends[i].name + " " + friends[i].state);

}

passes++;

}

else

{

lstOutput.Items.Clear();

var query = from p in friends // The "Query"

where p.state == txtState.Text.ToUpper()

select p;

foreach (var val in query) // Display results

lstOutput.Items.Add(val.name + " " + val.state);

}

}

private void btnClose_Click(object sender, EventArgs e)

{

Close();

}

}

The ShowAll() method constructs a test data set using the var type and names it friends. (It would be nice to define friends as static data with class scope, but var types can assume only local scope, remember?) If passes is 0, the call was from the constructor, so the code moves thefriends data to the left listbox object (lstFull) and increments passes. The next time ShowAll() is called after a button click event, filling in lstFull is bypassed because passes is no longer 0. In that case, the else statement block executes. The right listbox object (lstOutput) clears and the query processes.

The following query statement uses a LINQ where predicate to restrict the result of the query to the state selected by the user:

var query = from p in Friends // The "Query"

where p.state == txtState.Text.ToUpper()

select p;

foreach (var val in query) // Display results

lstOutput.Items.Add(val.name + " " + val.state);

The foreach statement block then iterates through the result of the LINQ query (as stored in query) and displays the relevant information in the right listbox object (lstOutput).

Of course, the test data set can consist of more complex objects. For example, you could extend the set to something like

var friends = new[] {

new {name = "Tom", state = "IN", age = 48},

new {name = "Terry", state = "MT", age = 61},

new {name = "Tammy", state = "IN", age = 46},

new {name = "Jane", state = "OH", age = 65},

new {name = "Don", state = "IN", age = 60},

new {name = "John", state = "IN", age = 61},

new {name = "Debbie", state = "IN", age = 58},

};

and change the query to

var query = from p in Friends // The "Query"

where p.age < age

select p;

where age is set from a textbox object and used as the where predicate in a search by age. Again, although you can accomplish the same results without using LINQ, the economy of expression with LINQ makes it the wise choice. Anytime you can accomplish the same task with less code, the less code route is almost always the way to go.

Other SQL-like keywords and operators are also available in LINQ. Although I am not prepared to discuss all of them given the limited look at SQL, Table 15.1 gives you a list of some of the more important keywords and operators. You can use the online help to explore further, if you want.

Table 15.1 Partial list of LINQ keywords

Keyword/Operator

Description

select, selectmany

Like SELECT in SQL, selectmany may be used with another collection and enables the result set to return pairs.

where

Similar to the WHERE predicate in SQL.

join, groupjoin

Enables result sets to span multiple tables based upon matching keys in the tables.

take, takewhile

Selects the first N objects from a collection. takewhile uses a predicate to further refine the query.

skip, skipwhile

A complement of the take operator. The set skips the first N objects in the collection. skipwhile is the complement of takewhile.

oftype

Enables you to select elements of a certain type.

concat

Enables concatenation of two collections.

orderby, thenby

Specifies the primary sort order for a collection. The default is ascending order. You can use orderbydescending to reverse the default order. thenby enables subsequent orders after the primary sort key.

reverse

Reverses the current order of the collection.

groupby

Returns a collection of objects that supports the IGrouping<key, values> interface.

distinct

Removes all duplicates from the result set.

union, intersect, except

Used to perform specialized operations on two sequences.

equalall

Checks to see if all elements in two collections are equal.

first,firstordefault, last, lastordefault

Uses a predicate to return the first element for which the predicate is logic True. An exception is thrown if no match is found in the collection. firstordefault is like first but returns the first item in the collection if no match is found (that is, if no exception is thrown). last works in a similar fashion but looks for the last match in the collection.

single

Uses a predicate to find a match but throws an exception if none is found.

elementat

Returns an element of the collection at the specified index.

any, all, contains

Uses a predicate to see if there are any matches (returns logic True or False), if they all match, or if the collection contains a match (returns logic True or False).

count, sum, min, max, average, aggregate

Like the aggregate functions in SQL.

Table 15.1 is not exhaustive, but it can help you start. (For more information, you can find a nice, short summary at: http://www.ezineasp.net/post/List-of-LINQ-Query-Keywords.aspx.) If you'd like to see some sample code using LINQ, check out http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b.

LINQ is an important addition to C# and can make some programming tasks significantly easier to develop. You might try experimenting with some of the operators in Table 15.1, using the code in Listing 15-1 as a starting point.

Summary

This chapter presented a brief introduction to LINQ, which brings a lot of SQL query functionality to data structures other than databases. I encourage you to experiment with the sample programs in this chapter and try to create a different array on your own. After you do that, add data to the tables so that they use LINQ instead of SQL to query the database. This should solidify your understanding of the database concepts in this chapter as well as LINQ.

Exercises

You can find the answers to the following exercises in Appendix A.

1. Using the Friends database table as an example, how could you use LINQ to process a database query?

2. Rewrite the query in Listing 15-1 so that the result of the query is presented in ascending order in the listbox.

3. If you're at a cocktail party and someone approaches you and asks, “Why should I use LINQ instead of SQL in my code?” what would you say? (What? This couldn't happen?)

What You Have Learned in This Chapter

Topic

Key Points

LINQ

Language Integrated Query.

LINQ query

How LINQ may be used to query data objects other than databases.

var

The data tied to LINQ whose type is determined by the context in which it is used.

LINQ namespaces

Certain namespaces must be available to programs that use LINQ.

LINQ advantages

The ability to apply SQL-like queries to objects other than databases.