Whereas the LINQ query and the T-SQL query are executed similarly, the T-SQL query syntax is a bit different.. In the next example, the LINQ query uses the same syntax to query data from
Trang 1(4) WHERE (5) GROUP BY (6) WITH (7) HAVING (10) ORDER BY
Notice that theFROMclause is processed first, while theSELECTclause is processed almost last Any
clause that is not specified in the query is simply skipped by the query processing engine This
informa-tion is important because it points out the similarities between LINQ query syntax and how SQL Server
processes a query
For more information on SQL logical query flow, please see Chapter 8, ‘‘Introducing Basic Query Flow.’’
In a LINQ query, the first clause is theFROMclause, which specifies the source of the data, and it must
end with either aSELECTclause or aGROUPclause The first clause, called a generator, defines where
the data will be coming from when the query is executed It also specifies the range variable that is used
as a reference for each element in the data source For example, the following uses LINQ to return a list
of the directories in the root of a hard drive:
DirectoryInfo di = new DirectoryINfo("C:\\");
var query =
from dir in di.GetDirectories() order by di.Name
select new { dir.Name };
foreach (var item in query) Console.WriteLine(item.Name);
In this example, the first statement creates a new instance of theDirectoryInfoclass This is what is
used to retrieve a list of the directories
The second statement is the LINQ statement The variablevaris the query variable This variable does
not store actual result data The results are produced during the iteration of the query, which is done in
theforeachloop When theforeachstatement executes, the query results are returned during each
iteration in the variable item
Whereas the LINQ query and the T-SQL query are executed similarly, the T-SQL query syntax is a bit
different In T-SQL, the query would be executed internally following the steps described earlier With
LINQ, the query does not need to go through the rewriting process
In the next example, the LINQ query uses the same syntax to query data from theContacttable in the
AdventureWorksdatabase in SQL Server:
var query = from c in Contact where c.FirstName.StartsWith("S") orderby c.LastName
select c;
Trang 2Notice in this query that the syntax is exactly the same as the first query An additionalWHEREclause
has been added, but syntactically it is the same This is what is great about LINQ Similar query
expres-sions can be written against two completely different sources of data
Query syntax and method syntax
LINQ has the ability to write queries using both query syntax and method syntax The examples shown
so far have used query syntax, which is writing the query as a query expression, as follows:
IEnumberable<string> query =
from c in Contact
where c.FirstName.StartsWith("S")
select c;
This declarative syntax is easy to read and understand, but developers also have the option of writing
queries using method syntax When a LINQ query is compiled, the query expression is translated into
method syntax because the NET CLR (common language runtime) doesn’t understand query syntax
Thus, at compile time, query expressions are translated into method calls because this is what the CLR
understands
The following shows the previous query written using method syntax:
IEnumberable<string> query = contact.Where(c =>
c.FirstName.StartsWith("S"));
For clarification, take a look at a couple more examples This first example adds an additional filter to
the previous example:
IEnumberable<string> query =
from c in Contact
where c.FirstName.StartsWith("S")
&& c.LastName.StartsWith("A")
select c;
Here is the same query in method syntax:
IEnumberable<string> query = contact.Where(a =>
a.FirstName.StartsWith("S") && a.LastName.StartsWith("A"));
Just to complicate things a bit more, this next example adds anOrderByclause:
IEnumberable<string> query =
from c in Contact
where c.FirstName.StartsWith("S")
&& c.LastName.StartsWith("A")
order by c.LastName
select c;
Trang 3The preceding query written using method syntax is as follows:
IEnumberable<string> query = contact.Where(c =>
c.FirstName.StartsWith("S") && c.LastName.StartsWith("A")) OrderBy(c => c.LastName);
The output of both queries is identical
Choosing a query syntax
As you have seen, although there are syntactical differences between query syntax and method syntax,
execution is essentially the same However, the NET CLR does not understand query syntax, so it is
translated into method syntax anyway The CLR understands method calls, so query expressions are
translated into method syntax (method calls) and executed as such
Although there is no semantic difference between method syntax and query syntax, I recommended that
developers use query syntax whenever possible, as it is easier to read, understand, and maintain
Now that you have a good understanding of LINQ, the standard query operators, and LINQ query
expressions, the rest of this chapter focuses on the LINQ providers and offers detailed examples of each,
beginning with LINQ to SQL
LINQ to SQL
LINQ to SQL is part of ADO.NET which provides a run-time infrastructure for mapping relational data
as objects In essence, it is an ORM (object-relational mapping) framework that enables the direct 1-to-1
mapping of a SQL Server database and its objects to NET classes without losing the ability to query
One of most important things that developers need to understand when working with LINQ to SQL is
theDataContextclass TheDataContextis the main medium through which objects are retrieved
from the database and changes are resubmitted TheDataContextis much like an ADO.NET
connec-tion in that it is initialized with a connecconnec-tion string
TheDataContexthas the responsibility of converting objects, which means that because LINQ to SQL
deals with objects, theDataContextconverts the objects into SQL queries, as well as reassembling the
results back into queryable objects
TheDataContexthas several overloads, one of which is just a simple connection string specifying the
connection information, as shown here:
DataContext db = new DataContext("Initial Catalog=AdventureWorks; i Integrated Security=sspi");
It is recommended, however, that you use strongly typedDataContexts This is as simple as creating
a new class that inherits from theDataContextclass:
[Database(Name = "AdventureWorks")]
public class AdventureWorks : DataContext {
public AdventureWorks(string connection) : base(connection) {}
}
Trang 4Here, the strongly typedDataContextis given the same name as the database to use — in this case,
theAdventureWorks(2005 and 2008) database Therefore, no database needs to be specified in the
connection string
In the preceding example, the[Database]attribute is applied to any strongly typedDataContext
declaration This attribute is optional, but when it is used aNameproperty must also be used to
sup-ply the name of the database to which you are mapping This attribute maps the database in theName
property to your entity class
With theDataContextcreated, the next step is to create mappings to the specific objects Database
tables are represented by entity classes in LINQ to SQL An entity class is a normal class annotated with
a specific tag that maps that class to a specific table in the database
TheTableattribute, part of theSystem.Data.Linq.Mappingnamespace, maps an entity class to a
database table or view This attribute has a single property,Name, which specifies the name of the
rela-tional table or view
In the following example, theTableattribute is applied to a class to define a mapping between the
Person.Contacttable in the AdventureWorks database and a class namedContact:
[Table(Name="Person.Contact")]
public class Contact
{
//
}
Once the table is mapped to an entity class, table columns must be mapped to class properties This is
accomplished by applying theColumnattribute, which maps a database table column to a member of
an entity class, as shown here:
[Column(DbType="nvarchar(50) not null")]
public string FirstName
Several properties are commonly applied with this attribute, including the following:
■ Name: The name of the table column
■ DbType: Database type of the column
■ IsPrimaryKey: Specifies that the associated column is the primary key of the corresponding
table
■ IsDbGenerated: Specifies that the associated column auto-generates its values
■ CanBeNull: Specifies that the associated column can contain null values
So far, the connection has been defined via theDataContext, and the table and column mappings
have been defined When this is all put together, it looks like the following:
public class AdventureWorks : DataContext
{
public AdventureWorks(string connection) : base(connection) {}
public Table<Contact> Contact;
}
Trang 5public class Contact {
[Column(DbType="nvarchar(50) not null")]
public string FirstName }
To help this sink in, this section offers a couple of examples The first example applies the mappings
manually, and the second example illustrates an easier way
Example 1: Manually applying the mappings
For this first example, follow these steps:
1 Create a new Visual Studio C# Windows project.
2 Add a reference to theSystem.Data.Linqcomponent
3 On the form, add a button and a list box.
4 Add a new class to the project and name itAdventureWorks
5 Open the new class and replace all the code that is there with the following code (Because
I named my project LINQ, the namespace is called LINQ Be sure to change the namespace name if you named your project something different.)
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
using System.ComponentModel;
using System;
namespace LINQ {
[Database(Name = "AdventureWorks")]
public class AdventureWorks : DataContext {
public AdventureWorks(string connection) : base(connection) {
} public Table<Contacts> Contact;
} [Table(Name="Person.Contact")]
public class Contacts {
[Column(DbType = "int not null", IsDbGenerated = true, IsPrimaryKey = true)]
Trang 6public int ContactID;
[Column(DbType = "nvarchar(8)")]
public string Title;
[Column(DbType = "nvarchar(50)")]
public string FirstName;
[Column(DbType = "nvarchar(50)")]
public string MiddleName;
[Column(DbType = "nvarchar(50)")]
public string LastName;
[Column(DbType = "nvarchar(50)")]
public string EmailAddress;
[Column(DbType = "int")]
public int EmailPromotion;
[Column(DbType = "bit")]
public byte NameStyle;
}
}
This code first defines theDataContextclass The next section of code defines a class that
maps that class to thePerson.Contacttable (in SQL Server 2005) as well as maps several
columns from the table to members of the class
Note some of the properties used in theColumnattribute applied to theContactID
mem-ber TheContactIDcolumn in thePerson.Contacttable is a primary key column that
auto-generates its IDs Thus, in order for LINQ to support this and recognize this functionality,
theIsDbGeneratedandIsPrimaryKeyproperties need to be used
6 In the Click event of the button on the form, add the following code:
AdventureWorks db = new AdventureWorks("Data Source=ServerName;
Initial Catalog=AdventureWorks;Integrated Security=sspi");
var query = from con in db.Contact where con.FirstName.StartsWith("S") select con;
foreach (var cont in query) listBox1.Items.Add(cont.FirstName);
7 Compile the project to ensure that there are no errors, and then run it When the form
dis-plays, press the button The list box should be populated with the first name of all contacts
whose first name begins with the letter S
Prior to moving on to the next example, let’s take a closer look at this one The class created earlier
used LINQ to SQL to define the mapping between the relational data and the class On the form, an
Trang 7instance of the class was created, and a LINQ query was created and used, employing a few of the
standard query operators The query was then iterated over, at which point the data was returned to the
application
The thing to pay attention to here is the query itself As the query was typed, IntelliSense displayed the
column names of the table, as well as a list of the available standard query operators and methods This
is LINQ in all its glory! Equally, notice that the syntax used to retrieve relational data is exactly the same
as what was used to query the directories on the local drive in the example shown earlier
This first example demonstrated how to create the mapping classes manually in order to provide a good
understanding of how the mapping works However, there is an easier way
Example 2: The easy way
For this second example, follow these steps:
1 In Visual Studio, right-click the project in the Solution Explorer and select Add➪ New Item
2 When the Add New Item dialog appears, select the LINQ to SQL Classes item from the list of
templates Name itAWDB.dbmland click OK
A LINQ to SQL designer page will open in the Visual Studio environment This object-relational designer provides a visual surface for creating LINQ to SQL entity classes and rela-tionships based on database objects In essence, this is where the object model is defined that maps database objects within an application
3 The empty design surface represents aDataContextthat is ready to be configured From here, open the Server Explorer and, if necessary, create a connection to the AdventureWorks database Once that is created, database objects can be dragged onto the designer
4 For this example, drag thePerson.Contacttable onto the designer and then save the project Next, modify the code behind the button on the form to look like the following:
AWDBDataContext db = new AWDBDataContext();
var query = from con in db.Contacts where con.FirstName.StartsWith("S") select con;
foreach (var cont in query) listBox1.Items.Add(cont.FirstName);
The only thing that changes is the first line; everything else remains the same Run the project and click the button The list box will be populated with the same data presented in the first example
5 Before moving on, however, a few things need to be highlighted In the Solution Explorer,
expand the AWDB node Underneath that node are two files:AWDB.dbml.layoutand
AWDB.designer.cs The layout file simply contains the layout information for the designer If you open this file you will see that it is simply an XML file
The important file is thedesigner.csfile Open this file and notice that it looks very similar
to the file manually created in the first example It contains a bit more information than the one you manually created earlier, but overall it is the same
Trang 8One of the differences between the two examples is that connection string information needed to be
passed in the first example, whereas no connection information needed to be passed in the second
example This is because the Server Explorer connection information was automatically saved and
added to the project’s resource file when the OR mapping was created In thedesigner.csfile, the
DataContextclass contains the following:
public AWDBDataContext() :
base(global::LINQ.Properties.Settings.Default AdventureWorksConnectionString, mappingSource)
{
OnCreated();
}
Right-click on the lineAdventureWorksConnectionStringand select Go To Definition, which
will take you to where the connection string is defined in the project settings Notice that the
AWDBDataContexthas several overloads, which enables different connection information to be passed
to theDataContext
Much more could be said about LINQ to SQL, but this should provide a solid foundation for
under-standing how it works Your homework assignment for this section is to add more tables to the
designer, look at their relationships, and modify the LINQ query to utilize the relationships (hint: use
theJOINoperator)
LINQ to SQL and Stored Procedures
Afew words on using LINQ to SQL and stored procedures I am a firm believer in using stored procedures
for several reasons:
■ They provide a layer of abstraction
■ They provide increased maintainability
■ They enable performance improvement through database query optimizations
The great thing about LINQ is that it supports accessing data by calling and executing stored procedures For
example, given the following stored procedure:
CREATE PROCEDURE [dbo].[uspGetSalesOrderDetail]
@SalesOrderID [int]
AS
BEGIN
SET NOCOUNT ON;
SELECT SalesOrderDetailID,
OrderQty,
continued
Trang 9ProductID,
UnitPrice,
LineTotal
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = @SalesOrderID
END;
Calling a stored procedure is as simple as the following:
var query = db.uspGetSalesOrderDetail(43659)
foreach (SalesOrderDetailResult salesOrderDetail in query)
{
//do something }
However, while using stored procedures does have its advantages, it is still an individual call as to when and
where stored procedures are appropriate For example, if I know that a query will always return a single row
(and maybe only few columns), then I may forgo using a stored procedure in that case If I have a complex
T-SQL query, then I will more than likely use a stored procedure
My recommendation is to experiment and find what works best given the scenario in your application Using
stored procedures provides certain benefits, such as those listed earlier, but not all scenarios need stored
procedures; and as you become familiar with LINQ and LINQ to SQL, you will find what works best in each
situation
LINQ to XML
I love XML The fact that it is entirely possible to query XML using LINQ just rocks my world It
is an excellent form of moving and storing data Those who have used other technologies to query
XML know that it can be somewhat difficult and cumbersome LINQ to XML is heaven sent This
section introduces LINQ to XML and demonstrates how easy it is to create and query XML using LINQ
to XML
The component that gives LINQ to XML its power is theSystem.Xml.Linqnamespace, and
its corresponding classes These classes provide the capability to work with XML with ease,
leav-ing behind the need to work with complex and cumbersome technologies such as the DOM and
XQuery
Table 34-2 describes the 19 classes contained in theSystem.Xml.Linqnamespace that enable
devel-opers to easily work with XML
Trang 10TABLE 34-2
LINQ to XML Classes
XAttribute Represents an XML attribute
XCData Represents a CDATA text node
XComment Represents an XML comment
XContainer An abstract base class representing nodes that have child
nodes XDeclaration Represents an XML declaration
XDocument Represents an XML document
XDocumentType Represents an XML DTD
XElement Represents an XML element
XName Represents the name of an XML element or attribute
Xnamespace Represents an XML namespace
XNode An abstract class representing nodes of an XML element
tree XNodeDocumentOrderComparer Provides mechanisms for node comparisons regarding
their order within the XML document XNodeEqualityComparer Provides mechanisms for node comparisons regarding
their equality value XObject An abstract class representing XNodes and XAttributes
XObjectChange The event type when an XObject event is raised
XObjectChangeEventArgs Provides information and data for the Changing and
Changed events XObjectChangeEventHandler The method that handles the XObject’s Changed and
Changing events XProcessingInstruction Represents an XML processing instruction