I often hear the questions, “What is LINQ, what does it do, and why do we need it?” The answer to the first question (and subsequently the other two questions) is that the Language Integrated Query (LINQ) is a set of standard query operators that provide the underlying query architecture for the navigation, filtering, and execution operations of nearly every kind of data source such as XML (using LINQ to XML, previously known as XLINQ), relational data (using LINQ to SQL, previously known as DLINQ), ADO.NET DataSets (using LINQ to DataSet), and in-memory collections.
The best way to begin understanding this wonderful new technology is to take a look at some history and background on how and why LINQ came to be.
Although the public first became aware of LINQ early in the fall of 2005, LINQ had been in development since early 2003. The overall LINQ goal was to make it easier for developers to interact with SQL and XML, primarily because there exists a disconnect between relational data (databases), XML, and the programming languages that communicate (that is, work with) each of them.
Most developers understand the concept of object-oriented (OO) programming and its related technologies and features such as classes, methods, and objects. Object-oriented programming has evolved tremendously over the past 10 years or so, but even in its current state, there’s still a gap when using and integrating OO technology with information that is not natively defined or inherent to it.
For example, suppose that you want to execute a T-SQL query from within your C# application. It would look something like this:
private void Form1_Load(object sender, EventArgs e)
{
string ConnectionString = @"Data Source=(local);
Initial Catalog=AdventureWorks;UID=sa;PWD=yourpassword";
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT LastName, FirstName FROM Person.Contact";
using (SqlDataReader rdr = cmd.ExecuteReader())
{
// do something
}
}
}
If you wanted to use the same code to execute a stored procedure that takes one or more parameters, it might look like this:
private void Form1_Load(object sender, EventArgs e)
{
string ConnectionString = @"Data Source=(local);
Initial Catalog=AdventureWorks;UID=sa;PWD=yourpassword";
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "uspGetBillOfMaterials";
cmd.Parameters.Add("@StartProductID", SqlDbType.Int).Value = 324;
cmd.Parameters.Add("@CheckDate", SqlDbType.DateTime).Value = "07/10/2000";
using (SqlDataReader rdr = cmd.ExecuteReader())
{
// do something
}
}
}
While you and I have probably coded something like this many, many times, it isn’t “friendly” on several levels. First, you are combining two languages into one. You have the language you are coding (in this case C#), plus you have the SQL language in quotation marks, which is not understood in the context of .NET. With the .NET language you have IntelliSense, but you don’t get IntelliSense in the embedded SQL syntax.
More importantly, however, there is no compile-time type checking, which means you can’t tell if something is broken until runtime. Every line of code has to be QA’d just to see if it even begins to work.
Microsoft also packed a lot of features into the .NET Framework that enable developers to work with XML as well. The .NET Framework contains the System.Xml namespace and other supporting namespaces such as System.Xml.XPath, System.Xml.Xsl, and System.Xml.Schema, which provide a plethora of functionality for working with XML. The namespaces contain many classes and methods that make up the XML .NET API architecture. The main classes are the XmlDocument, XmlReader, and XmlWriter.
To add to the complexity of working with different technologies, parsing an XML document isn’t the easiest thing to do, either. Your tools of choice to work with XML are the DOM, XQuery, or XSLT. For example, to read an XML document using existing technology, you would need to do something like the following:
XmlTextReader rdr = new XmlTextReader("C:\Employees.Xml");
while (rdr.Read())
{
XmlNodeType nt = rdr.NodeType;
Switch (nt)
{
case XmlNodeType.Element:
break;
case XmlNodeType.Attribute:
break;
case XmlNodeType.Comment:
break;
case XmlNodeType.Whitespace:
break;
}
}
That’s a lot of code just to read an XML document (and it isn’t even complete). Writing XML isn’t any less confusing, as illustrated here:
XmlTextWriter wrt = new XmlTextWriter("C:\Employees.Xml");
wrt.WriteStartDocument;
wrt.WriteComment("This is an example");
wrt.WriteStartElement("Employees");
wrt.WriteStartElement("Employee");
wrt.WriteStartElement("FirstName");
wrt.WriteString("Scott");
wrt.WriteEndElement();
wrt.WriteEndElement();
wrt.WriteEndElement();
Visually, you don’t know if this will work until you compile the project. Likewise, it is hard to see what the resulting XML will look like.
XML is great and its use continues to grow; you can expect it to be around for a long time. Yet, truth be told, XML is still hard to work with.
In dealing with these hurdles, Microsoft considered two paths. The first path would have required the company to build specific XML or relational data features into each programming language and runtime. That would be a major undertaking and an even bigger hassle to maintain. The second option was to add more general-purpose query capabilities into the .NET Frameworkin other words, a framework of all-purpose querying facilities built into the .NET Framework that both C# and VB.NET could easily take advantage of.
Luckily, Microsoft chose the later option, creating a unified query experience across objects, XML, collections, and data. It accomplished that by taking query set operations, transforms, and constructs and bringing them to the surface, making them high-level concepts within the .NET Framework (for example, on the same level as objects and classes). So, you can now enjoy the benefits of a single declarative pattern that can be expressed in any .NET-based programming language.
The result of making these set operations, transforms, and constructs first-class operations is a set of methods called the standard query operators. These operators provide query capabilities that include sorting, filtering, aggregation, and projection over a large number of different data sources. The standard query operators are the focus of Chapter 4.
Think about it for a minute. A single set of query operators that work within any .NET-based programming language, enabling you to write a query against a database, XML, or an in-memory array using the same syntax? How cool is that? And you get the added benefit of IntelliSense and compile-time type checking! Somebody pinch me.
To illustrate this great technology, take a look at an example that queries the directories of your C drive and writes them to a list box:
DirectoryInfo di = new DirectoryInfo("C:\\");
var dirQuery =
from dir in di.GetDirectories()
orderby di.Name
select new { dir.Name };
foreach {var item in dirQuery)
listBox1.Items.Add(item.Name);
This code uses some of the standard query operators to create a LINQ query. In essence, Microsoft has taken the concept of query set operations and made them first-class operations within the .NET Framework.
Here’s another example. This one queries all the system processes on your PC using the Process class, but notice that it uses the same query syntax as the previous example:
var procQuery =
from proc in Process.GetProcesses()
orderby p.WorkingSet64 descending
select new { p.Id, p.ProcessName, p.WorkingSet64 };
foreach (var item in procQuery)
ListBox1.Items.Add(item.Id + " " +
item.ProcessName + " " +
item.WorkingSet64);
When you run this code, all the processes on your system will be listed in descending order by memory usage.
Simply put, LINQ enables you to query anything that implements the IEnumerable<T> interface. If you can loop through the contents using the foreach statement, then you can query it using LINQ.
The following example illustrates how LINQ works querying relational data. This next example will use a database as the source of data.
var conQuery =
from c in contact
where c.FirstName.StartsWith("S")
orderby c.LastName
select new { c.FirstName, c.LastName, c.EmailAddress };
foreach (var item in conQuery)
ListBox1.Items.Add(item.FirstName + " " +
item.LastName + " " +
item.EmailAddress);
This previous example queries the Person.Contact table in the AdventureWorks database for all contacts whose first name starts with the letter “S”.
The purpose of LINQ is to provide developers with the following benefits:
A simplified way to write queries.
Faster development time by removing runtime errors and catching errors at compile time.
IntelliSense and debugging support for LINQ directly in the development language.
Closing the gap between relational data and object-oriented development.
A unified query syntax to use regardless of the source of data.
What is important to notice is the same syntax that you used to query the system processes was used query a SQL data source. Both of these topics will be discussed in much more detail, including how to easily connect and map to the source database.
So, with that introduction, this chapter introduces the following topics:
LINQ
LINQ to XML
LINQ to SQL
... less