Tải bản đầy đủ (.pdf) (50 trang)

Lập trình ứng dụng nâng cao (phần 8) pot

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (773.1 KB, 50 trang )

332
|
Chapter 14: Working with XML
// Simple customer class
public class Customer
{
[XmlAttribute( )]
public string FirstName { get; set; }
[XmlIgnore( )]
public string LastName { get; set; }
public string EmailAddress { get; set; }
// Overrides the Object.ToString( ) to provide a
// string representation of the object properties.
public override string ToString( )
{
return string.Format("{0} {1}\nEmail: {2}",
FirstName, LastName, EmailAddress);
}
}
// Main program
public class Tester
{
static void Main( )
{
Customer c1 = new Customer
{
FirstName = "Orlando",
LastName = "Gee",
EmailAddress = ""
};
//XmlSerializer serializer = new XmlSerializer(c1.GetType( ));


XmlSerializer serializer = new XmlSerializer(typeof(Customer));
StringWriter writer = new StringWriter( );
serializer.Serialize(writer, c1);
string xml = writer.ToString( );
Console.WriteLine("Customer in XML:\n{0}\n", xml);
Customer c2 = serializer.Deserialize(new StringReader(xml)) as
Customer;
Console.WriteLine("Customer in Object:\n{0}", c2.ToString( ));
Console.ReadKey( );
}
}
}
Output:
Customer in XML:
<?xml version="1.0" encoding="utf-16"?>
Example 14-6. Customizing XML serialization with attributes (continued)
XML Serialization
|
333
The only changes in this example are a couple of added XML serialization attributes
in the Customer class:
[XmlAttribute( )]
public string FirstName { get; set; }
The first change is to specify that you want to serialize the FirstName property into an
attribute of the
Customer element by adding the XmlAttributeAttribute to the property:
[XmlIgnore( )]
public string LastName { get; set; }
The other change is to tell XML serialization that you in fact do not want the LastName
property to be serialized at all. You do this by adding the XmlIgnoreAttribute to the

property. As you can see from the sample output, the
Customer object is serialized
exactly as we asked.
However, you have probably noticed that when the object is deserialized, its
LastName property is lost. Because it is not serialized, the XmlSerializer is unable to
assign it any value. Therefore, its value is left as the default, which is an empty string.
The goal is to exclude from serialization only those properties you don’t need or can
compute or can retrieve in other ways.
Runtime XML Serialization Customization
Sometimes it may be necessary to customize the serialization of objects at runtime.
For instance, your class may contain an instance of another class. The contained
class may be serialized with all its properties as child elements. However, you may
want to have them serialized into attributes to save some space. Example 14-7 illus-
trates how you can achieve this.
<Customer xmlns:xsi=" /> xmlns:xsd=" /> FirstName="Orlando">
<EmailAddress></EmailAddress>
</Customer>
Customer in Object:
Orlando
Email:
Example 14-7. Customizing XML serialization at runtime
using System;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
Example 14-6. Customizing XML serialization with attributes (continued)
334
|
Chapter 14: Working with XML
namespace Programming_CSharp

{
// Simple customer class
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
// Overrides the Object.ToString( ) to provide a
// string representation of the object properties.
public override string ToString( )
{
return string.Format("{0} {1}\nEmail: {2}",
FirstName, LastName, EmailAddress);
}
}
// Main program
public class Tester
{
static void Main( )
{
Customer c1 = new Customer
{
FirstName = "Orlando",
LastName = "Gee",
EmailAddress = ""
};
Type customerType = typeof(Customer);
XmlAttributeOverrides overrides = new XmlAttributeOverrides( );
foreach (PropertyInfo prop in customerType.GetProperties( ))
{

XmlAttributes attrs = new XmlAttributes( );
attrs.XmlAttribute = new XmlAttributeAttribute( );
overrides.Add(customerType, prop.Name, attrs);
}
XmlSerializer serializer = new XmlSerializer(customerType, overrides);
StringWriter writer = new StringWriter( );
serializer.Serialize(writer, c1);
string xml = writer.ToString( );
Console.WriteLine("Customer in XML:\n{0}\n", xml);
Customer c2 = serializer.Deserialize(new StringReader(xml)) as
Customer;
Console.WriteLine("Customer in Object:\n{0}", c2.ToString( ));
Example 14-7. Customizing XML serialization at runtime (continued)
XML Serialization
|
335
The Customer class in this example has no custom XML serialization attributes.
Therefore, all its properties are serialized into child elements, as you have seen in
previous examples. When an instance of it is serialized at runtime in the main func-
tion, we use a combination of reflection and advanced serialization techniques to
ensure that the properties are serialized into attributes instead.
In .NET XML serialization, you instruct the serialization engine to override its
default behavior with your custom requirements. Because you are going to use the
Customer type a lot, you store it locally so that it you can use it later:
Type customerType = typeof(Customer);
To specify your custom requirements, you use the XmlAttributeOverrides class:
XmlAttributeOverrides overrides = new XmlAttributeOverrides( );
foreach (PropertyInfo prop in customerType.GetProperties( ))
{
XmlAttributes attrs = new XmlAttributes( );

attrs.XmlAttribute = new XmlAttributeAttribute( );
overrides.Add(customerType, prop.Name, attrs);
}
The first step is to create a new XmlAttributeOverrides instance. You can now use .
NET reflection to go through all the properties of the target class, using its
GetProperties method. For each property, you override its default serialization behav-
ior by adding an
XmlAttributes object to the XmlAttributeOverrides object. To spec-
ify that you want to serialize the property as an attribute, you assign an
XmlAttributeAttribute object to the XmlAttributes.XmlAttribute property. This is
the equivalent of adding the
XmlAttributeAttribute to a property at design time, as
you did in the last example.
The
XmlAttributeOverrides.Add method takes three input parameters. The first is the
type of the object, the second is the name of the property, and the last is the cus-
tomer serialization behavior.
Console.ReadKey( );
}
}
}
Output:
Customer in XML:
<?xml version="1.0" encoding="utf-16"?>
<Customer xmlns:xsi=" />xmlns:xsd=" FirstName="
Orlando" LastName="Gee" EmailAddress="" />
Customer in Object:
Orlando Gee
Email:
Example 14-7. Customizing XML serialization at runtime (continued)

336
|
Chapter 14: Working with XML
To ensure that the XML serializer use the customer serialization overrides, you must
pass in the
overrides object to its constructor:
XmlSerializer serializer = new XmlSerializer(customerType, overrides);
The rest of this example stays unchanged from the last example. You can see from
the sample output that all the properties are indeed serialized into attributes instead
of child elements. When the object is deserialized, the customer overrides are also
recognized and the object is reconstructed correctly.
337
Chapter 15
CHAPTER 15
Putting LINQ to Work15
LINQ may be the most anticipated, most exciting (and to some, most feared) feature
in C# 3.0. The previous two chapters were, in large measure, a necessary introduc-
tion, an appetizer to whet your appetite and get you ready for the main meal: using
LINQ to retrieve meaningful data in production applications.
Before we begin, let’s be clear: your DBA is terrified of LINQ, and not just as a mat-
ter of job security. Improperly used, LINQ has the ability to put queries into the
hands of inexperienced, untrained goofballs (us) who know little or nothing about
writing efficient queries, and who will bring carefully honed data-intensive enterprise
systems to their knees (fun, eh?). OK, I said it out loud, so let’s all stop panicking.
As with all programming, the trick is to write the program, get it working, and then
optimize. It may be that after you have your program up and working (and profiled),
you’ll discover that there are some places that you’ve used LINQ that you’d be bet-
ter off using stored procedures running within your database (that’s what databases
do for a living), but we don’t know that a priori, and the advantages of LINQ are so
tremendous (e.g., the ability to use an object-oriented unified syntax to access all

your data regardless of source) that it cries out for a “code now, optimize later if
needed” approach.
The two most common sources you’ll use LINQ with are, no doubt, SQL and XML,
but they are certainly not the only sources of data. You may well find yourself retriev-
ing data from:
• Files
• Flat databases
• Mail messages
• Web services
• Legacy systems
• In memory data structures
338
|
Chapter 15: Putting LINQ to Work
And most exciting are sources you haven’t anticipated yet. With the understanding
of LINQ fundamentals you gained in Chapter 13, and the grounding in XML you
gained in Chapter 14, you are now just about ready to dig in and put LINQ to work.
Getting Set Up
Examples in this section use the SQL Server 2005 Adventure Works LT sample data-
base. To set up this database, download it from:
/>aspx?ReleaseId=4004
Please note that although this database is a simplified version of the more
comprehensive AdventureWorks, the two are quite different, and the
examples in this chapter will not work with the full AdventureWorks
database. Please select the AdventureWorksLT MSI package applicable
for your platform—32-bit, x64, or IA64. If SQL Server is installed in the
default directory, install the sample database to C:\Program Files\
Microsoft SQL Server\MSSQL.1\MSSQL\Data\. Otherwise, install the
database to the Data subdirectory under its installation directory.
If you are using SQL Server Express included in Visual Studio 2008, you will need to

enable the Named Pipes protocol:
1. Open SQL Server Configuration Manager under Start
➝ All Programs ➝
Microsoft SQL Server 2005 ➝ Configuration Tools ➝ SQL Server Configuration
Manager.
2. In the left pane, select SQL Server Configuration Manager (Local)
➝ SQL Server
2005 Network Configuration
➝ Protocols for SQLEXPRESS.
3. In the right pane, right-click the Named Pipes protocol and select Enable, as
shown in Figure 15-1.
4. In the left pane, select SQL Server 2005 Services, then right-click SQL Server
(SQLEXPRESS), and select Restart to restart SQL Server, as shown in Figure 15-2.
5. Attach the sample database to SQL Server Express using one of the following
methods:
a. If you already have SQL Server Client tools installed, open SQL Server
Management Studio under Start
➝ All Programs ➝ Microsoft SQL Server
2005
➝ SQL Server Management Studio and connect to the local SQL Server
Express database.
LINQ to SQL Fundamentals
|
339
b. Download SQL Server Express Management Studio from the Microsoft SQL
Server Express page ( />aspx), and install it on your machine. Then, open it and connect to the local
SQL Server Express database.
6. In the left pane, right-click Databases and select Attach (see Figure 15-3).
7. On the Attach Databases dialog click Add.
8. Click OK to close this dialog, and OK again to close the Attach Database dialog.

LINQ to SQL Fundamentals
To begin, open Visual Studio, and create a new application named “Simple Linq to
SQL” as a console application. Once the IDE is open, click on View, and open the
Server Explorer and make a connection to the AdventureWorksLT database, and test
that connection.
Figure 15-1. Enabling the Named Pipes protocol in SQL Server 2005 Express
Figure 15-2. Restarting SQL Server 2005 Express
340
|
Chapter 15: Putting LINQ to Work
With that in place, you are ready to create a program that uses LINQ to connect
your SQL database. You’ll need to include the
System.Data.Linq namespace in the
references for your project as shown in Figure 15-4 so that the last two
using state-
ments will compile.
This will also create the mapping between each class property and the correspond-
ing database column:
public class Customer
{
Figure 15-3. Attaching the database to SQL Server 2005 Express
LINQ to SQL Fundamentals
|
341
[Column] public string FirstName { get; set; }
[Column] public string LastName { get; set; }
[Column] public string EmailAddress { get; set; }
Complete analysis follows Example 15-1.
Figure 15-4. Adding a reference to System.Data.Linq
Example 15-1. Simple LINQ to SQL

using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
namespace Simple_Linq_to_SQL
{
// customer class
[Table(Name="SalesLT.Customer")]
public class Customer
{
[Column] public string FirstName { get; set; }
[Column] public string LastName { get; set; }
[Column] public string EmailAddress { get; set; }
// Overrides the Object.ToString( ) to provide a
// string representation of the object properties.
public override string ToString( )
342
|
Chapter 15: Putting LINQ to Work
The key to this program is in the first line of Main( ), where you define db to be of
type
DataContext.ADataContext object is the entry point for the LINQ to SQL frame-
work, providing a bridge between the application code and database-specific
commands. Its job is to translate high-level C# LINQ to SQL code to corresponding
database commands and execute them behind the scenes. It maintains a connection
to the underlying database, fetches data from the database when requested, tracks
changes made to every entity retrieved from the database, and updates the database
as needed. It maintains an “identity cache” to guarantee that if you retrieve an entity
more than once, all duplicate retrievals will be represented by the same object
instance (thereby preventing database corruption; for more information, see the

“Database Corruption” sidebar).
Once the
DataContext is instantiated, you can access the objects contained in the
underlying database. This example uses the Customer table in the AdventureWorksLT
database using the
DataContext’s GetTable( ) function:
Table<Customer> customers = db.GetTable<Customer>( );
{
return string.Format("{0} {1}\nEmail: {2}",
FirstName, LastName, EmailAddress);
}
}
public class Tester
{
static void Main( )
{
DataContext db = new DataContext(
@"Data Source=.\SqlExpress;
Initial Catalog=AdventureWorksLT;
Integrated Security=True");
Table<Customer> customers = db.GetTable<Customer>( );
var query =
from customer in customers
where customer.FirstName == "Donna"
select customer;
foreach(var c in query)
Console.WriteLine(c.ToString( ));
Console.ReadKey( );
}
}

}
Output:
Donna Carreras
Email:
Example 15-1. Simple LINQ to SQL (continued)
LINQ to SQL Fundamentals
|
343
This function is a generic function so that you can specify that the table should be
mapped to a collection of
Customer objects.
DataContext has a great many methods and properties, one of which is a Log. This
property allows you to specify the destination where it logs the SQL queries and
commands executed. By redirecting it to where you can access it, you can see how
LINQ does its magic. For instance, you can redirect the
Log to Console.Out so that
you can see the output on the system console:
Output:
SELECT [t0].[FirstName], [t0].[LastName], [t0].[EmailAddress]
FROM [SalesLT].[Customer] AS [t0]
WHERE [t0].[FirstName] = @p0
@p0: Input String (Size = 5; Prec = 0; Scale = 0) [Donna]
Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1
Database Corruption
The data in a large database can be “corrupted” in many ways—that is, the data can
inadvertently come to misrepresent the information you hoped to keep accurate.
A typical scenario would be this: you have data representing the books in your store
and how many are available. When you make a query about a book, the data is
retrieved from the database into a temporary record (or object) that is no longer con-
nected to the database until you “write it back”—any changes to the database are not

reflected in the record you are looking at unless you refresh the data (this is necessary
to keep a busy database responsive).
Suppose that Joe takes a call asking how many copies of Programming C# are on hand.
He calls up the record in his database and finds to his horror that they are down to a
single copy. While he is talking with his customer, Jane, a second seller, takes a call
looking for the same book. She sees one book available and sells it to her customer,
while Joe is discussing the merits of the book with his customer. Joe’s customer decides
to make the purchase, but by the time he does it is too late; Jane has already sold the
last copy. Joe tries to put the sale through, but the book that quite clearly is showing
as available is no longer there. You now have one very unhappy customer and a sales-
person that has been made to look like an idiot. Oops.
I mention in the text that LINQ ensures that multiple retrievals of a database record
are represented by the same object instance; this makes it much harder for the afore-
mentioned scenario to occur, as both Joe and Jane are working on the same record in
memory; thus, if Jane were to change the “number on hand,” that would be reflected
in Joe’s representation of the object—they are looking at the same data, not at inde-
pendent snapshots.
344
|
Chapter 15: Putting LINQ to Work
Using Visual Studio LINQ to SQL Designer
Rather than working out the data relationships in the underlying database and map-
ping them in the
DataContext manually, you can use the designer built into Visual
Studio. This is a very powerful mechanism that makes working with LINQ painfully
simple. To see how this works, first open the AdventureWorksLT database in SQL
Server Management Studio Express and examine the Customer, CustomerAddress,
and Address tables so that you feel comfortable you understand their relationship, as
illustrated by the Entity-Relationship diagram shown in Figure 15-5.
Create a new Visual Studio console application called AdventureWorksDBML. Make

sure the Server Explorer is visible and you have a connection to AdventureWorksLT,
as shown in Figure 15-6. If the connection is not available, follow the instructions
mentioned earlier to create it.
Figure 15-5. AdventureWorksLT database diagram
Using Visual Studio LINQ to SQL Designer
|
345
To create your LINQ to SQL classes, right-click on the project, and choose Add ➝
New Item, as shown in Figure 15-7.
When the New Item dialog opens, choose LINQ to SQL Classes. You can use the
default name (probably DataClasses1), or replace it with a more meaningful name. In
this case, replace it with AdventureWorksAddress, and click Add. The name you select
will become the name of your
DataContext object with the word DataContext appended.
Therefore, the data context name in this case will be
AdventureWorksAddressDataContext.
The center window shows changes to the Object Relational Designer. You can now
drag tables from Server Explorer or Toolbox to the designer. Drag the Address, Cus-
tomer, and CustomerAddress tables from the Server Explorer onto this space, as
shown in Figure 15-8.
In the image, two tables have been dragged on, and the third is about to be dropped.
Once your tables are dropped, Visual Studio 2008 automatically retrieves and
displays the relationship between the tables. You can arrange them to ensure that the
relationships between the tables are displayed clearly.
Figure 15-6. Server Explorer window
346
|
Chapter 15: Putting LINQ to Work
Once you’ve done that, two new files have been created: AdventureWorksAddress.dbml.
layout and AdventureWorksAddress.designer.cs. The former has the XML representa-

tion of the tables you’ve put on the surface, a short segment of which is shown here:
<?xml version="1.0" encoding="utf-8"?>
<ordesignerObjectsDiagram dslVersion="1.0.0.0" absoluteBounds="0, 0, 11, 8.5"
name="AdventureWorksAddress">
<DataContextMoniker Name="/AdventureWorksAddressDataContext" />
<nestedChildShapes>
<classShape Id="4a893188-c5cd-44db-a114-0444cced4057" absoluteBounds="1.125,
1.375, 2, 2.5401025390625">
<DataClassMoniker Name="/AdventureWorksAddressDataContext/Address" />
<nestedChildShapes>
<elementListCompartment Id="d59f1bc4-752e-41db-a940-4a9938014ca7"
absoluteBounds="1.1400000000000001, 1.835, 1.9700000000000002, 1.9801025390625"
name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
</nestedChildShapes>
</classShape>
<classShape Id="c432968b-f644-4ca3-b26b-61dfe4292884" absoluteBounds="5.875, 1,
2, 3.6939111328124996">
<DataClassMoniker Name="/AdventureWorksAddressDataContext/Customer" />
<nestedChildShapes>
<elementListCompartment Id="c240ad98-f162-4921-927a-c87781db6ac4"
absoluteBounds="5.8900000000000006, 1.46, 1.9700000000000002, 3.1339111328125"
name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
Figure 15-7. Selecting Add→New Item
Using Visual Studio LINQ to SQL Designer
|
347
</nestedChildShapes>
</classShape>
The .cs file has the code to handle all the LINQ to SQL calls that you otherwise
would have to write by hand. Like all machine-generated code, it is terribly verbose;

here is a very brief excerpt:
public Address( )
{
OnCreated( );
this._CustomerAddresses = new EntitySet<CustomerAddress>(new
Action<CustomerAddress>(this.attach_CustomerAddresses),
new Action<CustomerAddress>(this.detach_CustomerAddresses));
}
[Column(Storage="_AddressID", AutoSync=AutoSync.OnInsert,
DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
public int AddressID
{
get
{
return this._AddressID;
}
set
{
Figure 15-8. Dragging tables onto the work surface
348
|
Chapter 15: Putting LINQ to Work
if ((this._AddressID != value))
{
this.OnAddressIDChanging(value);
this.SendPropertyChanging( );
this._AddressID = value;
this.SendPropertyChanged("AddressID");
this.OnAddressIDChanged( );
}

}
}
The classes that are generated are strongly typed, and a class is generated for each
table you place in the designer.
For a review of strongly typed versus loosely typed classes, see
Chapter 9, particularly the section on Generics.
The DataContext class exposes each table as a property, and the relationships
between the tables are represented by properties of the classes representing data
records. For example, the
CustomerAddress table is mapped to the CustomerAddresses
property, which is a strongly typed collection (LINQ table) of CustomerAddress objects.
You can access the parent
Customer and Address objects of a CustomerAddress object
through its
Customer and Address properties, respectively. This makes it quite easy to
write the code to retrieve data.
Appending a Method to a Generated Class
One of the wonderful things about the partial class keyword added in C# 2.0 is that
you can add a method to the classes generated by the designer. In this case, we are over-
riding the
ToString method of the Customer class to have it display all its members in a
relatively easy-to-read manner:
public partial class Customer
{
public override string ToString( )
{
StringBuilder sb = new StringBuilder( );
sb.AppendFormat("{0} {1} {2}",
FirstName, LastName, EmailAddress);
foreach (CustomerAddress ca in CustomerAddresses)

{
sb.AppendFormat("\n\t{0}, {1}",
ca.Address.AddressLine1,
ca.Address.City);
}
sb.AppendLine( );
return sb.ToString( );
}
}
Retrieving Data
|
349
Retrieving Data
Replace the contents of Program.cs with the code shown in Example 15-2 to use the
generated LINQ to SQL code to retrieve data from the three tables you’ve mapped using
the designer.
Example 15-2. Using LINQ to SQL designer-generated classes
using System;
using System.Linq;
using System.Text;
namespace AdventureWorksDBML
{
// Main program
public class Tester
{
static void Main( )
{
AdventureWorksAddressDataContext dc = new
AdventureWorksAddressDataContext( );
// Uncomment the statement below to show the

// SQL statement generated by LINQ to SQL.
// dc.Log = Console.Out;
// Find one customer record.
Customer donna = dc.Customers.Single(c => c.FirstName == "Donna");");
Console.WriteLine(donna);
// Find a list of customer records.
var customerDs =
from customer in dc.Customers
where customer.FirstName.StartsWith("D")
orderby customer.FirstName, customer.LastName
select customer;
foreach (Customer customer in customerDs)
{
Console.WriteLine(customer);
}
}
}
// Add a method to the generated Customer class to
// show formatted customer properties.
public partial class Customer
{
public override string ToString( )
{
StringBuilder sb = new StringBuilder( );
sb.AppendFormat("{0} {1} {2}",
FirstName, LastName, EmailAddress);
foreach (CustomerAddress ca in CustomerAddresses)
350
|
Chapter 15: Putting LINQ to Work

Creating Properties for Each Table
As you can see, you begin by creating an instance of the DataContext object you
asked the tool to generate:
AdventureWorksAddressDataContext dc = new AdventureWorksAddressDataContext( );
When you use the designer, one of the things it does, besides creating the
DataContext class, is define a property for each table you’ve placed in the designer (in
this case, Customer, Address, and CustomerAddress). It names those properties by
making them plural. Therefore, the properties of
AdventureWorksAddressDataContext
include Customers, Addresses, and CustomerAddresses.
One side effect of this convention is that it would be a good idea to
name your database tables in singular form to avoid potential confu-
sion in your code. By default, the LINQ to SQL designer names the
generated data classes the same as the table names. If you use plural
table names, the class names will be the same as the
DataContext
property names. Therefore, you will need to manually modify the gen-
erated class names to avoid such naming conflicts.
{
sb.AppendFormat("\n\t{0}, {1}",
ca.Address.AddressLine1,
ca.Address.City);
}
sb.AppendLine( );
return sb.ToString( );
}
}
}
Output:
Donna Carreras

12345 Sterling Avenue, Irving
(only showing the first 5 customers):
Daniel Blanco
Suite 800 2530 Slater Street, Ottawa
Daniel Thompson
755 Nw Grandstand, Issaquah
Danielle Johnson
955 Green Valley Crescent, Ottawa
Darrell Banks
Norwalk Square, Norwalk
Darren Gehring
509 Nafta Boulevard, Laredo
Example 15-2. Using LINQ to SQL designer-generated classes (continued)
Retrieving Data
|
351
You can access these properties through the DataContext instance:
dc.Customers
These properties are themselves table objects that implement the IQueryable inter-
face, which itself has a number of very useful methods that allow you to filter,
traverse, and project operations over the data in a LINQ table.
Most of these methods are extension methods of the LINQ types, which means they
can be called just as though they were instance methods of the object that imple-
ments
IQueryable<T> (in this case, the tables in the DataContext). Therefore, because
Single is a method of IQueryable that returns the only element in a collection that
meets a given set of criteria, we’ll use that to find the one customer whose first name
is Donna. If there is more than one customer with that specific first name, only the
first customer record is returned:
Customer donna = dc.Customers.Single(c => c.FirstName == "Donna");

Let’s unpack this line of code.
You begin by getting the
Customers property of the DataContext instance, dc:
dc.Customers
What you get back is a Customer table object, which implements IQueryable. You can
therefore call the method
Single on this object:
dc.Customers.Single(condition);
The result will be to return a Customer object, which you can assign to a local vari-
able of type
Customer:
Customer donna = dc.Customers.Single(condition);
Notice that everything we are doing here is strongly typed. This is
goodness.
Inside the parentheses, you must place the expression that will filter for the one
record we need, and this is a great opportunity to use lambda expressions:
c => c.FirstName == "Donna"
You read this as “c goes to c.FirstName where c.FirstName equals
Donna.”
In this notation, c is an implicitly typed variable (of type Customer). LINQ to SQL
translates this expression into a SQL statement similar to the following:
Select * from Customer where FirstName = ‘Donna’;
352
|
Chapter 15: Putting LINQ to Work
Please note that this is just an arbitrary sample SQL. You can see the exact SQL as
generated by LINQ to SQL by redirecting the
DataContext log and examining the
output, as described earlier in this chapter.
This SQL statement is executed when the

Single method is executed:
Customer donna = dc.Customers.Single(c => c.FirstName == "Donna");
This Customer object (donna) is then printed to the console:
Console.WriteLine(donna);
The output is:
Donna Carreras
12345 Sterling Avenue, Irving,
Note that although you searched only by first name, what you retrieved was a com-
plete record, including the address information. Also note that the output is created
just by passing in the object, using the overridden method we created for the tool-
generated class (see the earlier sidebar, “Appending a Method to a Generated Class”).
A LINQ Query
The next block uses the new-to-C# 3.0 keyword var to declare a variable customerDS
which is implicitly typed by the compiler, based on the information returned by the
LINQ query:
var customerDs =
from customer in dc.Customers
where customer.FirstName.StartsWith("D")
orderby customer.FirstName, customer.LastName
select customer;
This query is similar to a SQL query (as noted in the previous chapter), and as you
can see, you are selecting from the
DataContext Customers property (e.g., the Cus-
tomer table) each customer whose
FirstName property (e.g., the FirstName column)
begins with D. You are ordering by
FirstName and then LastName, and returning all of
the results into
customerDs, whose implicit type is a TypedCollection of Customers.
With that in hand, you can iterate through the collection and print the data about

these customers to the console, treating them as
Customer objects rather than as data
records:
foreach (Customer customer in customerDs)
{
Console.WriteLine(customer);
}
This is reflected in this excerpt of the output:
Delia Toone
755 Columbia Ctr Blvd, Kennewick
Updating Data Using LINQ to SQL
|
353
Della Demott Jr
25575 The Queensway, Etobicoke
Denean Ison
586 Fulham Road,, London
Denise Maccietto
Port Huron, Port Huron
Derek Graham
655-4th Ave S.W., Calgary
Derik Stenerson
Factory Merchants, Branson
Diane Glimp
4400 March Road, Kanata
Updating Data Using LINQ to SQL
To add or modify data to the database using LINQ, you interact with objects in C#,
make your changes, and then tell the
DataContext to SubmitChanges, allowing LINQ
to take care of the details. This is an extremely object-oriented way to approach data

storage. Your code remains strongly typed and yet decoupled from the underlying
persistence mechanism.
If you want to add new data to the database, you instantiate a new object and then
save it. If you want to modify data already persisted (stored) in the database, you
retrieve the object, modify it, and then store it. The key to Example 15-3 is that from
a C# perspective, you are interacting with objects and letting LINQ worry about the
details of interacting with SQL Server.
Example 15-3. Modifying data using LINQ to SQL
using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Text;
namespace Modifying_Data_Using_Linq_To_SQL
{
// Main program
public class Tester
{
static void Main( )
{
AddCustomer( );
UpdateCustomer( );
Console.ReadKey( );
354
|
Chapter 15: Putting LINQ to Work
}
private static void AddCustomer( )
{

Console.WriteLine("Adding a new customer ");
AdventureWorksDataContext dc = new AdventureWorksDataContext( );
// Uncomment the statement below to show the
// SQL statement generated by LINQ to SQL.
// dc.Log = Console.Out;
// Add a new customer with address
Customer douglas = new Customer( );
douglas.FirstName = "Douglas";
douglas.LastName = "Adams";
douglas.EmailAddress = "";
douglas.PasswordHash = "fake";
douglas.PasswordSalt = "fake";
douglas.ModifiedDate = DateTime.Today;
douglas.rowguid = Guid.NewGuid( );
Address addr = new Address( );
addr.AddressLine1 = "1c Sharp Way";
addr.City = "Seattle";
addr.PostalCode = "98011";
addr.StateProvince = "Washington";
addr.CountryRegion = "United States";
addr.ModifiedDate = DateTime.Today;
addr.rowguid = Guid.NewGuid( );
CustomerAddress ca = new CustomerAddress( );
ca.AddressType = "Main Office";
ca.Address = addr;
ca.Customer = douglas;
ca.ModifiedDate = DateTime.Today;
ca.rowguid = Guid.NewGuid( );
dc.Customers.Add(douglas);
dc.SubmitChanges( );

ShowCustomersByFirstName("Douglas");
}
// Update a customer record
private static void UpdateCustomer( )
{
Console.WriteLine("Updating a customer ");
AdventureWorksDataContext dc = new AdventureWorksDataContext( );
// Uncomment the statement below to show the
// SQL statement generated by LINQ to SQL.
//dc.Log = Console.Out;
Example 15-3. Modifying data using LINQ to SQL (continued)
Updating Data Using LINQ to SQL
|
355
Customer dAdams = dc.Customers.Single(
c => (c.FirstName == "Douglas" && c.LastName == "Adams"));
Console.WriteLine("Before:\n{0}", dAdams);
dAdams.Title = "Mr.";
// Add a new shipping address
Address addr = new Address( );
addr.AddressLine1 = "1 Warehouse Place";
addr.City = "Los Angeles";
addr.PostalCode = "30210";
addr.StateProvince = "California";
addr.CountryRegion = "United States";
addr.ModifiedDate = DateTime.Today;
addr.rowguid = Guid.NewGuid( );
CustomerAddress ca = new CustomerAddress( );
ca.AddressType = "Shipping";
ca.Address = addr;

ca.Customer = dAdams;
ca.ModifiedDate = DateTime.Today;
ca.rowguid = Guid.NewGuid( );
dc.SubmitChanges( );
Customer dAdams1 = dc.Customers.Single(
c => (c.FirstName == "Douglas" && c.LastName == "Adams"));
Console.WriteLine("After:\n{0}", dAdams);
}
// Find a list of customer records with a specific first name.
private static void ShowCustomersByFirstName(string firstName)
{
AdventureWorksDataContext dc = new AdventureWorksDataContext( );
var customers =
from customer in dc.Customers
where customer.FirstName == "Douglas"
orderby customer.FirstName, customer.LastName
select customer;
Console.WriteLine("Customers whose first name is {0}:", firstName);
foreach (Customer customer in customers)
Console.WriteLine(customer);
}
}
// Add a method to the generated Customer class to
// show formatted customer properties.
public partial class Customer
{
public override string ToString( )
{
Example 15-3. Modifying data using LINQ to SQL (continued)
356

|
Chapter 15: Putting LINQ to Work
The test program takes two actions: AddCustomer and then UpdateCustomer, each of
which is encapsulated in a method call.
Adding a Customer Record
AddCustomer begins by creating an instance of the Customer class and populating its
properties:
Customer douglas = new Customer( );
douglas.FirstName = "Douglas";
douglas.LastName = "Adams";
douglas.EmailAddress = "";
douglas.PasswordHash = "fake";
douglas.PasswordSalt = "fake";
douglas.ModifiedDate = DateTime.Today;
douglas.rowguid = Guid.NewGuid( );
It does the same for the Address class:
Address addr = new Address( );
addr.AddressLine1 = "1c Sharp Way";
addr.City = "Seattle";
addr.PostalCode = "98011";
addr.StateProvince = "Washington";
addr.CountryRegion = "United States";
addr.ModifiedDate = DateTime.Today;
addr.rowguid = Guid.NewGuid( );
Finally, the class that joins an address to a customer is created:
CustomerAddress ca = new CustomerAddress( );
ca.AddressType = "Main Office";
ca.Address = addr;
ca.Customer = douglas;
ca.ModifiedDate = DateTime.Today;

ca.rowguid = Guid.NewGuid( );
StringBuilder sb = new StringBuilder( );
sb.AppendFormat("{0} {1} {2} {3}",
Title, FirstName, LastName, EmailAddress);
foreach (CustomerAddress ca in CustomerAddresses)
{
sb.AppendFormat("\n\t{0}: {1}, {2}",
ca.AddressType,
ca.Address.AddressLine1,
ca.Address.City);
}
sb.AppendLine( );
return sb.ToString( );
}
}
}
Example 15-3. Modifying data using LINQ to SQL (continued)

×