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

Beginning Microsoft Visual C# 2008 PHẦN 8 docx

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 (1.54 MB, 135 trang )

Chapter 27: LINQ to SQL
909
);
foreach (Order o in item.Orders) {
Console.WriteLine(“\t\t{0}\t{1}”, o.OrderID, o.OrderDate);
}

};

Console.WriteLine(“Press Enter/Return to continue ”);
Console.ReadLine();
}

6. Compile and execute the program (you can just press F5 for Start Debugging). You will see the
information for customers in the U.S.A. and their orders as follows (this is the last part of the
output; the first part scrolls off the top of the console window):
Customer: Trail’s Head Gourmet Provisioners Kirkland, WA
3 orders: Order ID Order Date
10574 6/19/1997 12:00:00 AM
10577 6/23/1997 12:00:00 AM
10822 1/8/1998 12:00:00 AM
Customer: White Clover Markets Seattle, WA
14 orders: Order ID Order Date
10269 7/31/1996 12:00:00 AM
10344 11/1/1996 12:00:00 AM
10469 3/10/1997 12:00:00 AM
10483 3/24/1997 12:00:00 AM
10504 4/11/1997 12:00:00 AM
10596 7/11/1997 12:00:00 AM
10693 10/6/1997 12:00:00 AM
10696 10/8/1997 12:00:00 AM


10723 10/30/1997 12:00:00 AM
10740 11/13/1997 12:00:00 AM
10861 1/30/1998 12:00:00 AM
10904 2/24/1998 12:00:00 AM
11032 4/17/1998 12:00:00 AM
11066 5/1/1998 12:00:00 AM
Press Enter/Return to continue

As before, press Enter/Return to finish the program and make the console screen disappear.
How It Works
You modified your previous program instead of creating a new program from scratch so you did not
have to repeat all the steps to create the
Northwind.dbml LINQ to SQL mapping source file (note that
the sample code has separate projects, each with its own instance of
Northwind.dbml ).
By dragging the
Orders table in from the Database Explorer window, you added the Order class to the

Northwind.dbml source file to represent the Orders table in your mapping of the Northwind database.
The O/R Designer also detected the relationship in the database between Customers and Orders,
adding an
Orders collection member to the Customer class to represent the relationship. All this was
done automatically, as when you add new controls to a form.
c27.indd 909c27.indd 909 3/24/08 5:23:53 PM3/24/08 5:23:53 PM
Part IV: Data Access
910
If you look before and after you add a database object to the O/R Designer pane, you will see that the
class definition for the added object(s) appears in the
Northwind.designer.cs generated source file.
As noted before, do not modify this generated source file; use it only for reference purposes.

Next, you added the newly available
Orders member to the select clause of the query:
select new {
ID=c.CustomerID,
Name=c.CompanyName,
City=c.City,
State=c.Region,
Orders=c.Orders
};

Orders is a special typed LINQ set ( System.Data.Linq.EntitySet < Order > ) that represents the
relationship between two tables in the relational database. It implements the
IEnumerable/
IQueryable
interfaces so it can be used as a LINQ data source itself or iterated with a foreach
statement just like any collection or array.
Like the
Table object shown in the previous example, the EntitySet is similar to a typed collection
of
Order objects (like a List < Order > ), but only those orders submitted by a particular customer will
appear in the
EntitySet member for a particular Customer instance.
The
Order objects in the customer ’ s EntitySet member correspond to the order rows in the database
having the same customer ID as that customer ’ s ID.
Navigating the relationship simply involves building a nested
foreach statement to iterate through
each customer and then each customer ’ s orders:
foreach (var item in queryResults) {


Console.WriteLine(
“Customer: {0} {1}, {2}\n{3} orders:\tOrder ID\tOrder Date”,
item.Name, item.City, item.State, item.Orders.Count
);
foreach (Order o in item.Orders) {
Console.WriteLine(“\t\t{0}\t{1}”, o.OrderID, o.OrderDate);
}
};

Rather that just use the default ToString() formatting, you format the output for readability so you
can show the hierarchy properly with the list of orders under each customer. The format string

“ Customer: {0} {1}, {2}\n{3} orders:\tOrder ID\tOrder Date ” has a placeholder for the
name, city, and state of each customer on the first line, and then prints a column header for that
customer ’ s orders on the next line. You use the LINQ aggregate
Count() method to print the count
of the number of that customer ’ s orders, and then print out the order ID and order date on each line in
the nested
foreach statement:
Customer: White Clover Markets Seattle, WA
14 orders: Order ID Order Date
10269 7/31/1996 12:00:00 AM
10344 11/1/1996 12:00:00 AM

c27.indd 910c27.indd 910 3/24/08 5:23:53 PM3/24/08 5:23:53 PM
Chapter 27: LINQ to SQL
911
The formatting is still a bit rusty in that you see the time of the order when all that really matters is the
date. However, you will get a chance to clean that up in the next example.


Drilling Down Further with LINQ to SQL
To get to the interesting data of how much each customer is spending, you need to add one more level of
detail and drill down into the details of each order, using the
Order Details table. Again, LINQ to SQL
and the O/R Designer make this very easy to do, as you will see in the following Try It Out.

Try It Out Drilling Down Further LINQ to SQL
In Visual C# 2008, modify the project for the previous example, BegVCSharp_27_1_
FirstLINQtoSQLQuery, in the directory C:\BegVCSharp\Chapter27 as shown in the following steps:
1. Click on the Order Details table in the Database Explorer window and drag it into the

Northwind.dbml pane as shown in Figure 27 - 16 .
Figure 27-16
c27.indd 911c27.indd 911 3/24/08 5:23:53 PM3/24/08 5:23:53 PM
Part IV: Data Access
912
2. Compile the project so that the Order_Detail object appears when you start entering code in
the next step.
3. Open the main source file Program.cs . In the Main() method the query should appear
like this:
static void Main(string[] args)
{
NorthwindDataContext northWindDataContext = new NorthwindDataContext();

var queryResults = from c in northWindDataContext.Customers
where c.Country == “USA”
select new {
ID=c.CustomerID,
Name=c.CompanyName,
City=c.City,

State=c.Region,
Orders=c.Orders
};

4. Modify the foreach clause to print the query results:
foreach (var item in queryResults) {

Console.WriteLine(
“Customer: {0} {1}, {2}\n{3} orders: Order ID\tOrder Date\tTotal
Amount”,
item.Name, item.City, item.State, item.Orders.Count);
foreach (Order o in item.Orders) {
Console.WriteLine(
“\t{0,10}\t{1,10}\t{2,10}”,
o.OrderID,
o.OrderDate.Value.ToShortDateString(),
o.Order_Details.Sum(od = > od.Quantity * od.UnitPrice)
.ToString(“C2”));
}
};

Console.WriteLine(“Press Enter/Return to continue ”);
Console.ReadLine();
}

5. Compile and execute the program (you can just press F5 for Start Debugging). Again, the
result shown here is just the last part of the output):
Customer: Trail’s Head Gourmet Provisioners Kirkland, WA
3 orders: Order ID Order Date Total Amount
10574 6/19/1997 $764.30

10577 6/23/1997 $569.00
10822 1/8/1998 $237.90
c27.indd 912c27.indd 912 3/24/08 5:23:54 PM3/24/08 5:23:54 PM
Chapter 27: LINQ to SQL
913
Customer: White Clover Markets Seattle, WA
14 orders: Order ID Order Date Total Amount
10269 7/31/1996 $676.00
10344 11/1/1996 $2,856.00
10469 3/10/1997 $1,125.50
10483 3/24/1997 $704.00
10504 4/11/1997 $1,388.50
10596 7/11/1997 $1,476.10
10693 10/6/1997 $2,334.00
10696 10/8/1997 $996.00
10723 10/30/1997 $468.45
10740 11/13/1997 $1,770.00
10861 1/30/1998 $3,523.40
10904 2/24/1998 $1,924.25
11032 4/17/1998 $8,902.50
11066 5/1/1998 $928.75
Press Enter/Return to continue

As you did before, press Enter/Return to finish the program and make the console screen disappear.
How It Works
You modified your previous program instead of creating a new program from scratch so you did not
have to repeat all the steps to create the
Northwind.dbml LINQ to SQL mapping source file.
You dragged the
Order Details table in from the Database Explorer window, adding the

Order_Detail class to the Northwind.dbml source file and adding an Order_Details_ collection
member to the
Order class to represent the relationship between Orders and Order Details .
Because class names and members in C# cannot contain spaces, the O/R Designer added an underscore
to form the names
Order_Detail and Order_Details .
Like the
Orders member of Customer itself, the Order_Details member of Orders is an EntitySet
representing the relationship.
You did not have to modify the
from where select clauses of the previous LINQ query at all;
instead, all the changes are confined to the
foreach processing loop:
foreach (var item in queryResults) {

Console.WriteLine(
“Customer: {0} {1}, {2}\n{3} orders: Order ID\tOrder Date\tTotal
Amount”,
item.Name, item.City, item.State, item.Orders.Count);
foreach (Order o in item.Orders) {
Console.WriteLine(
“\t{0,10}\t{1,10}\t{2,10}”,
o.OrderID,
o.OrderDate.Value.ToShortDateString(),
o.Order_Details.Sum(od = > od.Quantity * od.UnitPrice)
.ToString(“C2”));
}

};


c27.indd 913c27.indd 913 3/24/08 5:23:54 PM3/24/08 5:23:54 PM
Part IV: Data Access
914
You added “ Total Amount ” to the end of the header for each customer in the first line of the outer

foreach loop. In the inner foreach loop you cleaned up the formatting of each entry by adding a
field width of 10 to each
Console.Writeline() placeholder so that it reads “ \t{0,10}\t{1,10}\
t{2,10} ”
. In the list of fields, you passed the OrderID , and the OrderDate formatted as a short date
string to eliminate the unused time portion of the
OrderDate .
The total for each order is calculated from the
Order_Details . Things get a bit interesting here, but
you ’ ve already seen each element in the previous chapter.
To get the sum of all the order details for a single order, you use the LINQ
Sum() aggregate operator,
passing a lambda expression (
od = > od.Quantity * od.UnitPrice ) that calculates the total price
of each order (the subtotal for each order detail is the quantity times the unit price (that is, three
widgets @ $2 each = $6)). You format this total as currency with two decimal places
( “ C2 “ ), and that
finishes the example.

Grouping, Ordering, and Other Advanced
Queries in LINQ to SQL
Now that you know how the total spending for each customer is calculated, you may begin to have
questions at a higher level. Which customers are buying the most? What regions or countries are they
located in? What products are selling best? To answer decision support questions such as these, grouping
and ordering are required. The

group and orderby operations are the same in your code in LINQ to SQL
as they are in LINQ to Objects, but use SQL data sources for which LINQ can take advantage of the
underlying
group and orderby capabilities in the SQL database, depending on your query construction.
In the next example, you try this out by constructing a query to find what countries have had the
largest total sales.

Try It Out Grouping, Ordering, and Other Advanced Queries
In Visual C# 2008, modify the project for the previous example, BegVCSharp_27_1_
FirstLINQtoSQLQuery, in the directory C:\BegVCSharp\Chapter27 as shown in the following steps:
1. Open the main source file Program.cs . Replace the LINQ query in the Main() method with
the following queries:
static void Main(string[] args)
{
NorthwindDataContext northWindDataContext = new NorthwindDataContext();
var totalResults =
from c in northWindDataContext.Customers
select new
{
Country = c.Country,
Sales =
c.Orders.Sum(o = >
c27.indd 914c27.indd 914 3/24/08 5:23:54 PM3/24/08 5:23:54 PM
Chapter 27: LINQ to SQL
915
o.Order_Details.Sum(od = > od.Quantity * od.UnitPrice)
)
};

var groupResults =

from c in totalResults
group c by c.Country into cg
select new { TotalSales = cg.Sum(c = > c.Sales), Country = cg.Key }
;

var orderedResults =
from cg in groupResults
orderby cg.TotalSales descending
select cg
;

2. Continue to modify the Main() method in Program.cs by replacing the foreach loop
following the LINQ query with the code shown here:
Console.WriteLine(“Country\t\tTotal Sales\n \t ”);

foreach (var item in orderedResults) {
Console.WriteLine(“{0,-15}{1,12}”,
item.Country, item.TotalSales.ToString(“C2”));
}

Console.WriteLine(“Press Enter/Return to continue ”);
Console.ReadLine();
}

3. Compile and execute the program (you can just press F5 for Start Debugging). You will see the
total sales by country in descending order as follows:
Country Total Sales

USA $263,566.98
Germany $244,640.63

Austria $139,496.63
Brazil $114,968.48
France $85,498.76
Venezuela $60,814.89
UK $60,616.51
Sweden $59,523.70
Ireland $57,317.39
Canada $55,334.10
Belgium $35,134.98
Denmark $34,782.25
Switzerland $32,919.50
Mexico $24,073.45
Finland $19,778.45
c27.indd 915c27.indd 915 3/24/08 5:23:55 PM3/24/08 5:23:55 PM
Part IV: Data Access
916
Spain $19,431.89
Italy $16,705.15
Portugal $12,468.65
Argentina $8,119.10
Norway $5,735.15
Poland $3,531.95
Press Enter/Return to continue

As before, press Enter/Return to finish the program and make the console screen disappear.
How It Works
Again you modified your previous program to reuse the Northwind.dbml LINQ to SQL mapping.
For this example you did not have to add any classes to
Northwind.dbml because you used the
existing mapping classes for

Customer , Order ,and Order_Details .
You replaced the previous LINQ query with three new queries using the same data source as before
but processing the results very differently.
The first LINQ query is similar to previous queries in this chapter, using northWindDataContext
.Customers
as the data source:
var totalResults =
from c in northWindDataContext.Customers
select new
{
Country = c.Country,
Sales =
c.Orders.Sum(o = >
o.Order_Details.Sum(od = > od.Quantity * od.UnitPrice)
)
};

For this query you do not want to restrict the results to the U.S.A. only, so there is no where clause.
Instead of the
Region property, you select Country because this queries groups and orders by
country. You do not select the customer
ID , Name , or City because these are not used in the result and
it is inefficient to ask for more data from the database than you are going to use. The
Sum() operation
using the lambda expression
od = > od.Quantity * od.UnitPrice to get the sum of all the order
details is now moved to the LINQ query itself.
Next, you specify a group query to group the total sales by country, using the preceding query results
as the data source:
var groupResults =

from c in totalResults
group c by c.Country into cg
select new { TotalSales = cg.Sum(c = > c.Sales), Country = cg.Key }
;

This query is very similar to the group query you created for LINQ to Objects in the previous chapter,
using the country as the key to group results and by summing the total sales (in this case,
Sum() is
adding up the customer total sales for all customers within a country) the operation is nested inside
another
Sum() to sum up the sales for all the Orders of each Customer .
c27.indd 916c27.indd 916 3/24/08 5:23:55 PM3/24/08 5:23:55 PM
Chapter 27: LINQ to SQL
917
For the last query, you order the results with the ordered results query, using the group query as
the data source. A separate query is needed for this because group queries can be ordered only by the
key field:
var orderedResults =
from cg in groupResults
orderby cg.TotalSales descending
select cg
;

Finally, you do some output formatting, printing a header for the Country and Total Sales
columns in the result, and then in the
foreach processing loop printing the two columns in justified
format (left - justified in a 15 - character - wide field for the
Country , right - justified in a 12 - character - wide
field for the
Total Sales :

Console.WriteLine(“Country\t\tTotal Sales\n \t ”);

foreach (var item in orderedResults) {
Console.WriteLine(“{0,-15}{1,12}”,
item.Country, item.TotalSales.ToString(“C2”));
}

Displaying Generated SQL
If you are familiar with SQL databases you may be wondering what SQL database commands are being
generated by LINQ to SQL. Even if you are not familiar with SQL, it is sometimes instructive to look at
the generated SQL to understand how LINQ to SQL is interacting with the database (and perhaps to
appreciate what LINQ to SQL is doing by taking care of writing the complex SQL for you!) .
In the following Try It Out, you will modify the previous group query example to display the generated
SQL, and then look at how it is executed.


Try It Out Displaying Generated SQL
Follow these steps to create the example in Visual C# 2008:
1. Modify the project for the previous example, BegVCSharp_27_1_FirstLINQtoSQLQuery, in
the directory C:\BegVCSharp\Chapter27 as shown in the following steps.
2. Open the main source file Program.cs . Add the highlighted lines to the Main() method:
static void Main(string[] args)
{
NorthwindDataContext northWindDataContext = new NorthwindDataContext();
var totalResults =
from c in northWindDataContext.Customers
select new
c27.indd 917c27.indd 917 3/24/08 5:23:55 PM3/24/08 5:23:55 PM
Part IV: Data Access
918

{
Country = c.Country,
Sales =
c.Orders.Sum(o = >
o.Order_Details.Sum(od = > od.Quantity * od.UnitPrice)
)
};
Console.WriteLine(“ SQL for totalResults query ”);;
Console.WriteLine(totalResults);
Console.WriteLine(“Press Enter/Return to continue ”);
Console.ReadLine();

var groupResults =
from c in totalResults
group c by c.Country into cg
select new { TotalSales = cg.Sum(c = > c.Sales), Country = cg.Key }
;
Console.WriteLine(“ SQL for groupResults query ”);;
Console.WriteLine(groupResults);
Console.WriteLine(“Press Enter/Return to continue ”);
Console.ReadLine();

var orderedResults =
from cg in groupResults
orderby cg.TotalSales descending
select cg
;
Console.WriteLine(“ SQL for orderedResults query ”);;
Console.WriteLine(groupResults);
Console.WriteLine(“Press Enter/Return to continue ”);

Console.ReadLine();

Console.WriteLine(“Country\t\tTotal Sales\n \t ”);

foreach (var item in orderedResults) {
Console.WriteLine(“{0,-15}{1,12}”,
item.Country, item.TotalSales.ToString(“C2”));
}

Console.WriteLine(“Press Enter/Return to continue ”);
Console.ReadLine();
}

3. Compile and execute the program (you can just press F5 for Start Debugging). You will see the
following SQL output for the first query on the console screen:
SQL for totalResults query
SELECT [t0].[Country], (
SELECT SUM([t4].[value])
FROM (
SELECT SUM([t3].[value])(
SELECT SUM((CONVERT(Decimal(29,4),[t2].[Quantity])) *
[t2].[UnitPrice])
c27.indd 918c27.indd 918 3/24/08 5:23:55 PM3/24/08 5:23:55 PM
Chapter 27: LINQ to SQL
919
FROM [dbo].[Order Details] AS [t2]
WHERE [t2].[OrderID] = [t1].[OrderID]
) AS [value], [t1].[CustomerID]
FROM [dbo].[Orders] AS [t1]
) AS [t3]

WHERE [t3].[CustomerID] = [t0].[CustomerID]
) AS [value]
FROM [dbo].[Customers] AS [t0]
Press Enter/Return to continue

Do not press Enter yet; leave the console screen up and read the How It Works section to follow the
output as it is generated.
How It Works
The default ToString() method for a LINQ to SQL query prints the generated SQL, so what you are
seeing is the output from the call to
Console.WriteLine(totalResults) ; aren ’ t you glad you
didn ’ t have to write this SQL yourself?
This SQL command will be passed to SQL Server to return the results to LINQ to SQL. LINQ to SQL
translates the LINQ
select , from , and where clauses into SQL SELECT , FROM , and WHERE clauses.
It also translates the LINQ
Sum() aggregate operator into an invocation of the SQL SUM() aggregate
operator.
The operation of these SQL functions is similar enough to the equivalent LINQ functions that they are
not explained in detail; however, be aware that not every LINQ clause or method translates into an
exact SQL equivalent. It just so happens that this query has a fairly literal SQL translation, with a few
extras such as calling the SQL
CONVERT() function to get the correct decimal data type for the Sales
column in the result, and adding SQL
WHERE clauses to join the Order Details , Orders , and

Customer table results on the OrderID and CustomerID fields — messy SQL details that LINQ to
SQL takes care of for you.
Note also that this SQL query has not executed yet — because of LINQ ’ s deferred execution, this
displays what SQL commands LINQ to SQL will send to SQL Server when the query is actually

executed (when you loop through the results with a
foreach loop). Now press Enter/Return to see
the next screen of generated SQL, for the group results query:
SQL for groupResults query
SELECT SUM([t4].[value]) AS [value], [t4].[Country]
FROM (
SELECT [t0].[Country], (
SELECT SUM([t4].[value])
FROM (
SELECT SUM([t3].[value])(
SELECT SUM((CONVERT(Decimal(29,4),[t2].[Quantity])) *
[t2].[UnitPrice])
FROM [dbo].[Order Details] AS [t2]
WHERE [t2].[OrderID] = [t1].[OrderID]
) AS [value], [t1].[CustomerID]
FROM [dbo].[Orders] AS [t1]
c27.indd 919c27.indd 919 3/24/08 5:23:56 PM3/24/08 5:23:56 PM
Part IV: Data Access
920
) AS [t3]
WHERE [t3].[CustomerID] = [t0].[CustomerID]
) AS [value]
FROM [dbo].[Customers] AS [t0]
) AS [t4]
GROUP BY [t4].[Country]
Press Enter/Return to continue

Note that the SQL for the group query contains the same SQL you just saw for the total results
query inside it; because its data source is the
totalResults query as specified in the from clause

(
from c in totalResults ), it wraps the SQL for the totalResults query in a nested SQL query
with a
GROUP BY clause:
SELECT SUM([t4].[value]) AS [value], [t4].[Country]
FROM (

SQL for totaResults

) AS [t4]
GROUP BY [t4].[Country]

Again, this has not been sent to the server yet; because you pass these results to yet another nested
query you are essentially building up a single complex SQL command with multiple LINQ to SQL
queries. Now press Enter/Return to see the next screen of generated SQL:
SQL for orderedResults query
SELECT [t5].[value] AS [TotalSales], [t5].[Country]
FROM (
SELECT SUM([t4].[value]) AS [value], [t4].[Country]
FROM (
SELECT [t0].[Country], (
SELECT SUM([t3].[value])
FROM (
SELECT (
SELECT SUM((CONVERT(Decimal(29,4),[t2].[Quantity])) *
[t2].[UnitPrice])
FROM [dbo].[Order Details] AS [t2]
WHERE [t2].[OrderID] = [t1].[OrderID]
) AS [value], [t1].[CustomerID]
FROM [dbo].[Orders] AS [t1]

) AS [t3]
WHERE [t3].[CustomerID] = [t0].[CustomerID]
) AS [value]
FROM [dbo].[Customers] AS [t0]
) AS [t4]
GROUP BY [t4].[Country]
) AS [t5]
ORDER BY [t5].[value] DESC
Press Enter/Return to continue

As before, this SQL query contains the SQL for the preceding queries inside it. It wraps the GROUP BY
results in an
ORDER BY query:
c27.indd 920c27.indd 920 3/24/08 5:23:56 PM3/24/08 5:23:56 PM
Chapter 27: LINQ to SQL
921
SELECT [t5].[value] AS [TotalSales], [t5].[Country]
FROM (

SQL for groupResults

) AS [t5]
ORDER BY [t5].[value] DESC

This has still not been sent to the server yet; however, when you press Enter/Return next, you execute
the
foreach loop over the results of the orderby query, causing this SQL query to be the one actually
executed. Because of deferred execution, only one SQL query is actually sent to the database server,
which is almost always more efficient than processing the results of multiple queries.
Whether the SQL is executed efficiently is up to the query optimizer built into almost all modern SQL

database servers — it will analyze the nested queries and build a query plan that executes the query in
the most optimal manner possible. While some SQL query optimizers are better than others, most
modern ones do a better job than the average application programmer — so it is OK to let LINQ to
SQL and the SQL database do the heavy lifting together.
It is good to be aware of what SQL is being generated and be able to tell the SQL experts in your
organization what SQL your program is generating, but with LINQ to SQL you don ’ t have to create
the SQL yourself.
If you have a special need to execute specific SQL queries against your data, see the ADO.NET classes
described in the next chapter, which are designed to enable you to specify the SQL you send to the
server. LINQ to SQL also provides an
ExecuteQuery() method in the DataContex t class for your
database, for situations when you primarily want to use the SQL generated by LINQ to SQL classes
but have an occasional need to send a specific SQL command to the SQL database.
Next you will learn another way in which Visual C# 2008 and LINQ to SQL make your work easier,
when you look at how you can generate user interfaces bound to database queries with very little
work required on your part.

Data Binding with LINQ to SQL
In order for end users to interact with a database application, you need to design a set of forms for data
entry and reports.
It traditionally required much tedious coding to update business objects from the GUI forms and then
make method calls to the business layer to query and update the database as users browse it and update
or enter new data.
Previously you saw how the O/R Designer generates LINQ to SQL classes to handle reading the
database and navigating relationships. Well, there ’ s more good news for you!
Visual C# 2008 includes data binding in the forms designers that works with LINQ to SQL so that you
don ’ t have to have write detailed code to get data from your end - user forms to the database classes. You
can point - and - click in Visual C# 2008 to generate code for a sophisticated graphical database application
that works pretty much right out of the Forms Designer with practically no hand - coding required. You
will see how this works in the next Try It Out.

c27.indd 921c27.indd 921 3/24/08 5:23:56 PM3/24/08 5:23:56 PM
Part IV: Data Access
922


Try It Out Data Binding with LINQ To SQL
Follow these steps to create the example in Visual C# 2008:
1. Create a new Windows Forms project called BegVCSharp_27_2_LINQtoSQLDataBinding in
the directory C:\BegVCSharp\Chapter27.
2. Add the LINQ to SQL class Northwind.dbml and connect to the Northwind database as you
did in the first LINQ to SQL query example at the beginning of the chapter.
If you need a reminder of how to do this, go back to the “ First LINQ to SQL Query ” Try It Out at the
beginning of this chapter and repeat steps 3 through 13.
3. Drag the Customers and Orders tables into Northwind.dbml using the O/R Designer. Your
project screen should now look like the one shown in Figure 27 - 17 .
Figure 27-17
Figure 27-18
4. Compile the project so that the classes defined in Northwind.dbml objects will be available in
the following steps.
5. Add a new data source to your project by selecting Data Add New Data Source as shown in
Figure 27 - 18 .
c27.indd 922c27.indd 922 3/24/08 5:23:57 PM3/24/08 5:23:57 PM
Chapter 27: LINQ to SQL
923
6. In the Data Source Configuration Wizard dialog, click Object as the data source type as shown
in Figure 27 - 19 . Then click Next.
Figure 27-19
Figure 27-20
7. Choose the Customer object (see Figure 27 - 20 ) and then click Finish.
c27.indd 923c27.indd 923 3/24/08 5:23:57 PM3/24/08 5:23:57 PM

Part IV: Data Access
924
8. Click on the design pane for Form1.cs so it appears in the foreground of your project code
windows. Show your newly added data source by selecting Data
Show Data Sources (see
Figure 27 - 18 if you do not find it), and expand the
Customer node in the data source as
shown in Figure 27 - 21 .
Figure 27-22
Figure 27-21
9. Expand the size of Form1.cs a bit because you are going to add a number of controls to it.
10. Click on the Customer node drop - down box (see Figure 27 - 22 ) and choose Details as the
default control type to create for this table.
c27.indd 924c27.indd 924 3/24/08 5:23:58 PM3/24/08 5:23:58 PM
Chapter 27: LINQ to SQL
925
A series of data entry fields will appear on the form (refer to Figure 27 - 23 ) as well as two new
objects:
customerBindingSource and customerBindingNavigator .
What are these strange pseudo - controls at the bottom of your form? They do not seem to
appear on the screen if you run the application.
Actually, one of the new objects does appear on the form. This is the

customerBindingNavigator , a navigation bar at the top of your form for moving through
the rows in the database, as shown at the top of Figure 27 - 23 .
The customerBindingSource object controls the binding of the controls on the form to the
Customer LINQ to SQL source. While not actually visible on the form, it interacts with the
controls and your LINQ to SQL classes to keep them synchronized with the database.
Figure 27-23
11. Click on the Customer node and drag it into the Form1.cs design window (see Figure 27 - 23 ).

c27.indd 925c27.indd 925 3/24/08 5:23:58 PM3/24/08 5:23:58 PM
Part IV: Data Access
926
12. Click on the Orders node and drag it into the design window for Form1.cs to the right the

Customer data entry fields (see Figure 27 - 24 ).
Figure 27-24
A data grid control will appear on the form (refer to Figure 27 - 24 ) as well as a new binding
source, the
orderBindingSource . No navigator was added because Orders is a dependent
member of the
Customer object and its navigation is controlled by the position in the parent

Customers table.
13. Double - click on the title bar of Form1 in the Form1.cs designer window to create an event
handler for
Form1 ’ s Form_Load event. The code view of Form1.cs appears, with the stub of
the event handler ready to be filled in.
14. Add the following lines to the Form1 class in Form1.cs in the code editor:

public partial class Form1 : Form
{
NorthwindDataContext northwindDataContext = new NorthwindDataContext();

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)

{
customerBindingSource.DataSource = northwindDataContext.Customers;
}
}

c27.indd 926c27.indd 926 3/24/08 5:23:58 PM3/24/08 5:23:58 PM
Chapter 27: LINQ to SQL
927
15. Compile and execute the program (you can just press F5 for Start Debugging). You will see the
information for customers in the U.S.A. and their orders, similar to that shown in Figure 27 - 25
for Germany.
Figure 27-25
Use the navigation buttons in the upper - left corner to move through the customer records.
You can see the customer fields change values and each customer ’ s orders display in the data
grid as you scroll through the data. Pretty good functionality for just having to write two lines
of code!
How It Works
The process for creating and using the Northwind.dbml LINQ to SQL mapping is the same as in
previous examples. Just as with previous console examples, you must create an instance of the

NorthwindDataContext class through which access to the other LINQ to SQL objects is obtained.
Here, you made this instance a part of the
Form1 class instead of adding it to a Main() method:
public partial class Form1 : Form
{
NorthwindDataContext northwindDataContext = new NorthwindDataContext();

The key line of code that connects the bound data - entry fields to the NorthwindDataContext class is
the code you added to the
Form_Load() method:

private void Form1_Load(object sender, EventArgs e)
{
customerBindingSource.DataSource = northwindDataContext.Customers;

Although the data controls were generated for your LINQ to SQL class when you specified an object
as the data source, the assignment to the
DataSource member is what tells C# from which object
instance to fill the controls.
However, once the initial instance is bound, the rest of the behavior happens automatically behind the
scenes when users interact with the controls on the screen, and you don ’ t have to code any of it
yourself! What could be easier?

c27.indd 927c27.indd 927 3/24/08 5:23:59 PM3/24/08 5:23:59 PM
Part IV: Data Access
928
Updating Bound Data with LINQ to SQL
One piece of functionality that you may notice is missing in the preceding example is making changes to
the database. The Save button, indicated by a floppy disk icon at the right of the navigator bar (see
Figure 27 - 26 ), is disabled by default until you turn it on with your code.
Figure 27-26
This means any changes you make to the data in the program will not be saved permanently in the
database. Luckily, it is very easy to turn on the Save functionality, as you will see in this Try It Out.


Try It Out Updating Bound Data with LINQ to SQL
Follow these steps to create the example in Visual C# 2008:
1. Modify the existing Windows forms project BegVCSharp_27_2_LINQtoSQLDataBinding in
the directory C:\BegVCSharp\Chapter27.
2. In the Form1.cs design window, double - click on the Save button in the navigator bar.
3. An empty event handler for the customerBindingNavigatorSaveItem_Click event

appears in the
Form1.cs code window. Add the following line to the handler method:
private void customerBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
northwindDataContext.SubmitChanges();
}

4. Back in the Form1.cs design window, set the Enabled property for the

customerBindingNavigatorSaveItem control to True .
5. If you chose “ No ” to not copy the Northwnd.MDF database file as recommended in previous
examples, skip to step 6. If you chose “ Yes, ” click on
Northwnd.MDF in the Solution Explorer.
In the Properties window, set the Copy to Output Directory property to Copy if Newer, as
shown in Figure 27 - 27 . This ensures that Visual C# 2008 does not overwrite your changed
local copy when starting your project.
6. Compile and execute the program (press F5 for Start Debugging).
7. Modify a field (e.g., enter a value for Region on the first customer record, ALKFI). After
making the change, navigate to the next record with the right arrow, and then press the Save
button. Navigate back to the first record and you will find that the change persists. It will also
persist if you exit the program and restart.
c27.indd 928c27.indd 928 3/24/08 5:23:59 PM3/24/08 5:23:59 PM
Chapter 27: LINQ to SQL
929
How It Works
The SubmitChanges() method you call in the Save button event handler will save all pending
changes in the memory of the
DataContext instance to the database:
northwindDataContext.SubmitChanges();


This includes all database objects loaded into memory and their related rows. It does not include data
entered in a field but not saved to memory yet; that is why you need to navigate to the next record
before clicking Save. Appropriate SQL insert and update statements are executed against the database
based on the changes you made to your objects.

Summary
That completes your tour of LINQ to SQL. Highlights of this chapter included the following:
The concept of object - relational mapping (ORM) and how LINQ to SQL implements this concept
for C# .
Learning to use the O/R Designer in Visual C# 2008 to create objects for a specific database .


Figure 27-27
c27.indd 929c27.indd 929 3/24/08 5:23:59 PM3/24/08 5:23:59 PM
Part IV: Data Access
930
Using LINQ to SQL queries with the objects created by the O/R Designer .
Navigating relationships between database objects using both explicit query code and
generated code .
Grouping and ordering to make decision support queries with LINQ to SQL .
Binding LINQ to SQL objects to graphical controls .
Updating the database using LINQ to SQL ’ s
SubmitChanges() method.
In the next chapter you will use LINQ with nonrelational XML data.
Exercises
For each exercise here, add the Northwind.dbml LINQ to SQL mapping class to your solution as
described in the chapter. Drag the Customers,
Employees , Order Details , Orders , and Products
tables into the O/R Designer pane for
Northwind.dbml , as shown in Figure 27 - 28 .






Figure 27-28
c27.indd 930c27.indd 930 3/24/08 5:24:00 PM3/24/08 5:24:00 PM
Chapter 27: LINQ to SQL
931
1. Use LINQ to SQL to display detail information from the Products and Employees tables in the
Northwind database.
2. Create a LINQ to SQL query to show the top - selling products in the Northwind database.
3. Create a group query to show top - selling products by country.
4. Create a set of data - bound controls to edit product information for the Northwind data.
c27.indd 931c27.indd 931 3/24/08 5:24:00 PM3/24/08 5:24:00 PM
c27.indd 932c27.indd 932 3/24/08 5:24:00 PM3/24/08 5:24:00 PM
2 8
ADO.NET and LINQ
over DataSet
The previous chapter introduced LINQ (Language - Integrated Query) and showed how it works
with objects. This chapter introduces ADO.NET, which is the traditional way of accessing
databases with previous versions of C# and .NET, and then it introduces LINQ over DataSet,
which is the version of LINQ that cooperates with ADO.NET.
All examples in this chapter use the SQL Server Northwind example database (except where
specifically noted). See the previous chapter for instructions on installing the SQL Server
Northwind example database.
In particular, this chapter looks at the following:
An overview of ADO.NET and the structure of its main classes .
Reading data with a
DataReader and with a DataSet .

Updating the database, adding records, and deleting records .
Working with relationships in ADO.NET .
Reading and writing XML documents in ADO.NET .
Executing SQL commands directly from ADO.NET .
Executing stored procedures from ADO.NET .
Querying ADO.NET objects with LINQ over DataSet .
After an overview of ADO.NET, you will learn the concepts behind it. Then you can create some
simple projects and start using the ADO.NET classes.








c28.indd 933c28.indd 933 3/24/08 5:24:34 PM3/24/08 5:24:34 PM

×