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

Apress-Visual CSharp 2010 Recipes A Problem Solution Approach_4 potx

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 (2 MB, 95 trang )

CHAPTER 9 ■ DATABASE ACCESS

450

The Code
The following example demonstrates how to retrieve results as XML using the FOR XML clause and the
ExecuteXmlReader method:

using System;
using System.Xml;
using System.Data;
using System.Data.SqlClient;

namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_08
{
public static void ConnectedExample()
{
// Create a new SqlConnection object.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
con.ConnectionString = @"Data Source = .\sqlexpress;" +
"Database = Northwind; Integrated Security=SSPI";

// Create and configure a new command that includes the
// FOR XML AUTO clause.
using (SqlCommand com = con.CreateCommand())
{
com.CommandType = CommandType.Text;


com.CommandText = "SELECT CustomerID, CompanyName" +
" FROM Customers FOR XML AUTO";

// Open the database connection.
con.Open();

// Execute the command and retrieve an XmlReader to access
// the results.
using (XmlReader reader = com.ExecuteXmlReader())
{
while (reader.Read())
{
Console.Write("Element: " + reader.Name);
if (reader.HasAttributes)
{
for (int i = 0; i < reader.AttributeCount; i++)
{
reader.MoveToAttribute(i);
Console.Write(" {0}: {1}",
reader.Name, reader.Value);
}

CHAPTER 9 ■ DATABASE ACCESS

451

// Move the XmlReader back to the element node.
reader.MoveToElement();
Console.WriteLine(Environment.NewLine);
}

}
}
}
}
}

public static void DisconnectedExample()
{
XmlDocument doc = new XmlDocument();

// Create a new SqlConnection object.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
con.ConnectionString = @"Data Source = .\sqlexpress;" +
"Database = Northwind; Integrated Security=SSPI";

// Create and configure a new command that includes the
// FOR XML AUTO clause.
SqlCommand com = con.CreateCommand();
com.CommandType = CommandType.Text;
com.CommandText =
"SELECT CustomerID, CompanyName FROM Customers FOR XML AUTO";

// Open the database connection.
con.Open();

// Load the XML data into the XmlDocument. Must first create a
// root element into which to place each result row element.
XmlReader reader = com.ExecuteXmlReader();

doc.LoadXml("<results></results>");

// Create an XmlNode from the next XML element read from the
// reader.
XmlNode newNode = doc.ReadNode(reader);

while (newNode != null)
{
doc.DocumentElement.AppendChild(newNode);
newNode = doc.ReadNode(reader);
}
}

// Process the disconnected XmlDocument.
Console.WriteLine(doc.OuterXml);
}

CHAPTER 9 ■ DATABASE ACCESS

452

public static void Main(string[] args)
{
ConnectedExample();
Console.WriteLine(Environment.NewLine);

DisconnectedExample();
Console.WriteLine(Environment.NewLine);

// Wait to continue.

Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
}
}
9-9. Perform Asynchronous Database Operations Against
SQL Server
Problem
You need to execute a query or command against a SQL Server database as a background task while your
application continues with other processing.
Solution
Use the BeginExecuteNonQuery, BeginExecuteReader, or BeginExecuteXmlReader method of the
System.Data.SqlClient.SqlCommand class to start the database operation as a background task. These
methods all return a System.IAsyncResult object that you can use to determine the operation’s status or
use thread synchronization to wait for completion. Use the IAsyncResult object and the corresponding
EndExecuteNonQuery, EndExecuteReader, or EndExecuteXmlReader method to obtain the result of the
operation.
■ Note Only the SqlCommand class supports the asynchronous operations described in this recipe. The equivalent
command classes for the Oracle, SQL Server CE, ODBC, and OLE DB data providers do not provide this
functionality.
How It Works
You will usually execute operations against databases synchronously, meaning that the calling code
blocks until the operation is complete. Synchronous calls are most common because your code will
CHAPTER 9 ■ DATABASE ACCESS

453

usually require the result of the operation before it can continue. However, sometimes it’s useful to
execute a database operation asynchronously, meaning that you start the method in a separate thread

and then continue with other operations.
■ Note To execute asynchronous operations over a System.Data.SqlClient.SqlConnection connection, you
must specify the value Asynchronous Processing=true in its connection string.
The SqlCommand class implements the asynchronous execution pattern similar to that discussed in
recipe 4-2. As with the general asynchronous execution pattern described in recipe 4-2, the arguments of
the asynchronous execution methods (BeginExecuteNonQuery, BeginExecuteReader, and
BeginExecuteXmlReader) are the same as those of the synchronous variants (ExecuteNonQuery,
ExecuteReader, and ExecuteXmlReader), but they take the following two additional arguments to support
asynchronous completion:
• A System.AsyncCallback delegate instance that references a method that the
runtime will call when the asynchronous operation completes. The method is
executed in the context of a thread-pool thread. Passing null means that no
method is called and you must use another completion mechanism (discussed
later in this recipe) to determine when the asynchronous operation is complete.
• An object reference that the runtime associates with the asynchronous operation.
The asynchronous operation does not use nor have access to this object, but it’s
available to your code when the operation completes, allowing you to associate
useful state information with an asynchronous operation. For example, this object
allows you to map results against initiated operations in situations where you
initiate many asynchronous operations that use a common callback method to
perform completion.
The EndExecuteNonQuery, EndExecuteReader, and EndExecuteXmlReader methods allow you to retrieve
the return value of an operation that was executed asynchronously, but you must first determine when it
has finished. Here are the four techniques for determining if an asynchronous method has finished:
• Blocking: This method stops the execution of the current thread until the
asynchronous operation completes execution. In effect, this is much the same as
synchronous execution. However, in this case, you have the flexibility to decide
exactly when your code enters the blocked state, giving you the opportunity to
carry out some additional processing before blocking.
• Polling: This method involves repeatedly testing the state of an asynchronous

operation to determine whether it’s complete. This is a very simple technique and
is not particularly efficient from a processing perspective. You should avoid tight
loops that consume processor time. It’s best to put the polling thread to sleep for a
period using Thread.Sleep between completion tests. Because polling involves
maintaining a loop, the actions of the waiting thread are limited, but you can
easily update some kind of progress indicator.
CHAPTER 9 ■ DATABASE ACCESS

454

• Waiting: This method uses an object derived from the
System.Threading.WaitHandle class to signal when the asynchronous method
completes. Waiting is a more efficient version of polling and in addition allows
you to wait for multiple asynchronous operations to complete. You can also
specify timeout values to allow your waiting thread to fail if the asynchronous
operation takes too long, or if you want to periodically update a status indicator.
• Callback: This a method that the runtime calls when an asynchronous operation
completes. The calling code does not need to take any steps to determine when
the asynchronous operation is complete and is free to continue with other
processing. Callbacks provide the greatest flexibility, but also introduce the
greatest complexity, especially if you have many concurrently active
asynchronous operations that all use the same callback. In such cases, you must
use appropriate state objects to match completed methods against those you
initiated.
■ Caution When using the asynchronous capabilities of the SQL Server data provider, you must ensure that your
code does not inadvertently dispose of objects that are still being used by other threads. Pay particular attention to
SqlConnection and SqlCommand objects.
The Code
Recipe 4-2 provides examples of all of the completion techniques summarized in the preceding list. The
following example demonstrates the use of an asynchronous call to execute a stored procedure on a SQL

Server database. The code uses a callback to process the returned result set.

using System;
using System.Data;
using System.Threading;
using System.Data.SqlClient;

namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_09
{
// A method to handle asynchronous completion using callbacks.
public static void CallbackHandler(IAsyncResult result)
{
// Obtain a reference to the SqlCommand used to initiate the
// asynchronous operation.
using (SqlCommand cmd = result.AsyncState as SqlCommand)
{
// Obtain the result of the stored procedure.
using (SqlDataReader reader = cmd.EndExecuteReader(result))
{
CHAPTER 9 ■ DATABASE ACCESS

455

// Display the results of the stored procedure to the console.
lock (Console.Out)
{
Console.WriteLine(
"Price of the Ten Most Expensive Products:");


while (reader.Read())
{
// Display the product details.
Console.WriteLine(" {0} = {1}",
reader["TenMostExpensiveProducts"],
reader["UnitPrice"]);
}
}
}
}
}

public static void Main()
{
// Create a new SqlConnection object.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
// You must specify Asynchronous Processing=true to support
// asynchronous operations over the connection.
con.ConnectionString = @"Data Source = .\sqlexpress;" +
"Database = Northwind; Integrated Security=SSPI;" +
"Asynchronous Processing=true";

// Create and configure a new command to run a stored procedure.
// Do not wrap it in a using statement because the asynchronous
// completion handler will dispose of the SqlCommand object.
SqlCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;

cmd.CommandText = "Ten Most Expensive Products";

// Open the database connection and execute the command
// asynchronously. Pass the reference to the SqlCommand
// used to initiate the asynchronous operation.
con.Open();
cmd.BeginExecuteReader(CallbackHandler, cmd);

CHAPTER 9 ■ DATABASE ACCESS

456

// Continue with other processing.
for (int count = 0; count < 10; count++)
{
lock (Console.Out)
{
Console.WriteLine("{0} : Continue processing ",
DateTime.Now.ToString("HH:mm:ss.ffff"));
}
Thread.Sleep(500);
}
}

// Wait to continue.
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
}

}
9-10. Write Database-Independent Code
Problem
You need to write code that can be configured to work against any relational database supported by an
ADO.NET data provider.
Solution
Program to the ADO.NET data provider interfaces in the System.Data namespace, as opposed to the
concrete implementations, and do not rely on features and data types that are unique to specific
database implementations. Use factory classes and methods to instantiate the data provider objects you
need to use.
How It Works
Using a specific data provider implementation (the SQL Server data provider, for example) simplifies
your code, and may be appropriate if you need to support only a single type of database or require
access to specific features provided by that data provider, such as the asynchronous execution for SQL
Server detailed in recipe 9-9. However, if you program your application against a specific data provider
implementation, you will need to rewrite and test those sections of your code if you want to use a
different data provider at some point in the future.
Table 9-6 contains a summary of the main interfaces you must program against when writing
generic ADO.NET code that will work with any relational database’s data provider. The table also
explains how to create objects of the appropriate type that implement the interface. Many of the recipes
CHAPTER 9 ■ DATABASE ACCESS

457

in this chapter demonstrate the use of ADO.NET data provider interfaces over specific implementation,
as highlighted in the table.
Table 9-6. Data Provider Interfaces
Interface Description Demonstrated In
IDbConnection
Represents a connection to a relational database. You must

program the logic to create a connection object of the
appropriate type based on your application’s configuration
information, or use the DbProviderFactory.CreateConnection
factory method (discussed in this recipe).
Recipe 9-1
IDbCommand
Represents a SQL command that is issued to a relational
database. You can create IDbCommand objects of the appropriate
type using the IDbConnection.CreateCommand or
DbProviderFactory.CreateCommand factory method.
Recipe 9-5
IDataParameter
Represents a parameter to an IDbCommand object. You can create
IDataParameter objects of the correct type using the
IDbCommand.CreateParameter, IDbCommand.Parameters.Add, or
DbProviderFactory.CreateParameter factory method.
Recipe 9-6
IDataReader
Represents the result set of a database query and provides access
to the contained rows and columns. An object of the correct type
will be returned when you call the IDbCommand.ExecuteReader
method.
Recipes 9-5 and
9-6
IDbDataAdapter
Represents the set of commands used to fill a
System.Data.DataSet from a relational database and to update
the database based on changes to the DataSet. You must program
the logic to create a data adapter object of the appropriate type
based on your application’s configuration information, or use the

DbProviderFactory.CreateAdapter factory method (discussed in
this recipe).


The System.Data.Common.DbProviderFactory class provides a set of factory methods for creating all
types of data provider objects, making it very useful for implementing generic database code. Most
important, DbProviderFactory provides a mechanism for obtaining an initial IDbConnection instance,
which is the critical starting point for writing generic ADO.NET code. Each of the standard data provider
implementations (except the SQL Server CE data provider) includes a unique factory class derived from
DbProviderFactory. Here is the list of DbProviderFactory subclasses:





CHAPTER 9 ■ DATABASE ACCESS

458

• System.Data.Odbc.OdbcFactory
• System.Data.OleDb.OleDbFactory
• System.Data.OracleClient.OracleClientFactory
• System.Data.SqlClient.SqlClientFactory
You can obtain an instance of the appropriate DbProviderFactory subclass using the
DbProviderFactories class, which is effectively a factory of factories. Each data provider factory is
described by configuration information in the machine.config file, similar to that shown here for the SQL
Server data adapter. This can be changed or overridden by application-specific configuration
information if required.

<configuration>

<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider" invariant="System.Data.SqlClient" ~CCC
description=".Net Framework Data Provider for SqlServer" type= ~CCC
"System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, ~CCC
Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<add name="Odbc Data Provider" />
<add name="OleDb Data Provider" />
<add name="OracleClient Data Provider" />
<add name="SQL Server CE Data />
</DbProviderFactories>
</system.data>
</configuration>

You can enumerate the available data provider factories by calling DbProviderFactories.
GetFactoryClasses, which returns a System.Data.DataTable containing the following columns:
• Name, which contains a human-readable name for the provider factory. Taken from
the name attribute in the configuration information.
• Description, which contains a human-readable description for the provider
factory. Taken from the description attribute of the configuration information.
• InvariantName, which contains the unique name used to refer to the data provider
factory programmatically. Taken from the invariant attribute of the configuration
information.
• AssemblyQualifiedName, which contains the fully qualified name of the
DbProviderFactory class for the data provider. Taken from the type attribute of the
configuration information.
Normally, you would allow the provider to be selected at install time or the first time the application
is run, and then store the settings as user or application configuration data. The most important piece of
information is the InvariantName, which you pass to the DbProviderFactories.GetFactory method to
obtain the DbProviderFactory implementation you will use to create your IDbConnection instances.

CHAPTER 9 ■ DATABASE ACCESS

459

The Code
The following example demonstrates the enumeration of all data providers configured for the local
machine and application. It then uses the DbProviderFactories class to instantiate a DbProviderFactory
object (actually a SqlClientFactory) from which it creates the appropriate IDbConnection. It then uses
the factory methods of the data provider interfaces to create other required objects, resulting in code
that is completely generic.

using System;
using System.Data;
using System.Data.Common;

namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_10
{
public static void Main(string[] args)
{
// Obtain the list of ADO.NET data providers registered in the
// machine and application configuration files.
using (DataTable providers = DbProviderFactories.GetFactoryClasses())
{
// Enumerate the set of data providers and display details.
Console.WriteLine("Available ADO.NET Data Providers:");
foreach (DataRow prov in providers.Rows)
{
Console.WriteLine(" Name:{0}", prov["Name"]);

Console.WriteLine(" Description:{0}",
prov["Description"]);
Console.WriteLine(" Invariant Name:{0}",
prov["InvariantName"]);
}
}

// Obtain the DbProviderFactory for SQL Server. The provider to use
// could be selected by the user or read from a configuration file.
// In this case, we simply pass the invariant name.
DbProviderFactory factory =
DbProviderFactories.GetFactory("System.Data.SqlClient");

// Use the DbProviderFactory to create the initial IDbConnection, and
// then the data provider interface factory methods for other objects.
using (IDbConnection con = factory.CreateConnection())
{
// Normally, read the connection string from secure storage.
// See recipe 9-3. In this case, use a default value.
con.ConnectionString = @"Data Source = .\sqlexpress;" +
"Database = Northwind; Integrated Security=SSPI";

CHAPTER 9 ■ DATABASE ACCESS

460

// Create and configure a new command.
using (IDbCommand com = con.CreateCommand())
{
com.CommandType = CommandType.StoredProcedure;

com.CommandText = "Ten Most Expensive Products";

// Open the connection.
con.Open();

// Execute the command and process the results.
using (IDataReader reader = com.ExecuteReader())
{
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Price of the Ten Most" +
" Expensive Products.");

while (reader.Read())
{
// Display the product details.
Console.WriteLine(" {0} = {1}",
reader["TenMostExpensiveProducts"],
reader["UnitPrice"]);
}
}
}
}

// Wait to continue.
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
}
}

9-11. Discover All Instances of SQL Server on Your Network
Problem
You need to obtain a list of all instances of SQL Server that are accessible on the network.
Solution
Use the GetDataSources method of the System.Data.Sql.SqlDataSourceEnumerator class.
CHAPTER 9 ■ DATABASE ACCESS

461

How It Works
The SqlDataSourceEnumerator class makes it easy to enumerate the SQL Server instances accessible on
the network. You simply obtain the singleton SqlDataSourceEnumerator instance via the static property
SqlDataSourceEnumerator.Instance and call its GetDataSources method. The GetDataSources method
returns a System.Data.DataTable that contains a set of System.Data.DataRow objects. Each DataRow
represents a single SQL Server instance and contains the following columns:
• ServerName, which contains the name of the server where the SQL Server instance
is hosted
• InstanceName, which contains the name of the SQL Server instance or the empty
string if the SQL Server is the default instance
• IsClustered, which indicates whether the SQL Server instance is part of a cluster
• Version, which contains the version of the SQL Server instance
The Code
The following example demonstrates the use of the SqlDataSourceEnumerator class to discover and
display details of all SQL Server instances accessible (and visible) on the network. The IsClustered and
Version columns may be blank for some versions of SQL Server.

using System;
using System.Data;
using System.Data.Sql;


namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_11
{
public static void Main(string[] args)
{
// Obtain the DataTable of SQL Server instances.
using (DataTable SqlSources =
SqlDataSourceEnumerator.Instance.GetDataSources())
{
// Enumerate the set of SQL Servers and display details.
Console.WriteLine("Discover SQL Server Instances:");
foreach (DataRow source in SqlSources.Rows)
{
Console.WriteLine(" Server Name:{0}", source["ServerName"]);
Console.WriteLine(" Instance Name:{0}",
source["InstanceName"]);
Console.WriteLine(" Is Clustered:{0}",
source["IsClustered"]);
Console.WriteLine(" Version:{0}", source["Version"]);
}
}

CHAPTER 9 ■ DATABASE ACCESS

462

// Wait to continue.
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");

Console.ReadLine();
}
}
}
9-12. Create an In-Memory Cache
Problem
You need to create an in-memory cache of part of the database.
Solution
Use System.Data.DataSet to represent the data and System.Data.SqlClient.SqlDataAdapter to read and
sync data with the database.
How It Works
The System.Data.DataSet class contains one or more instances of System.Data.DataTable, each of which
contains instances of System.Data.DataRow, representing data rows from the database. The
SqlDataAdapter class acts as the bridge between the database and the DataSet, allowing you to populate
the DataSet with data and write back any changes to the database when you are done. The sequence for
using a DataSet is as follows:
1. Create a SqlConnection to your database as normal (see recipe 9-1).
2. Create a new instance of DataSet using the default constructor.
3. Create a new instance of SqlDataAdapter, passing in a query string for the data
you require and the SqlConnection you created in step 1 as constructor
arguments.
4. Create an instance of SqlCommandBuilder, passing in the SqlDataAdapter you
created.
5. Call the SqlDataAdapter.Fill instance method, passing the DataSet you
created in step 2 as a method argument.
6. Use the DataSet to access the DataTables contained within—read and modify
data as required.
7. Call the SqlDataAdapter.Update method to write any changes back to the
database.
To create a new row in a table, call the DataTable.NewRow instance method to obtain an instance of

DataRow that has the same schema as the DataTable. The new row is not automatically added to the table
CHAPTER 9 ■ DATABASE ACCESS

463

when you call NewRow—call DataTable.Rows.Add once you have set the values for the row. Changes that
you make to the data in the DataSet are not written back to the database until you call the
SqpDataAdapter.Update method.
The Code
The following example creates a DataSet and fills it with the contents of the Region table of the Northwind
sample database. The DataSet contains one DataTable, whose schema and contents are printed out. A
new record is added and an existing one modified before the changes are written back to the database.

using System;
using System.Data;
using System.Data.SqlClient;

namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_12
{
static void Main(string[] args)
{

// Create a new SqlConnection object.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
con.ConnectionString = @"Data Source = .\sqlexpress;" +
"Database = Northwind; Integrated Security=SSPI";


// Open the database connection.
con.Open();

// Create the query string.
string query = "SELECT * from Region";

// Create the data set.
DataSet dataset = new DataSet();

// Create the SQL data adapter.
SqlDataAdapter adapter = new SqlDataAdapter(query, con);
// Create the command builder so we can do modifications.
SqlCommandBuilder commbuilder = new SqlCommandBuilder(adapter);

// Populate the data set from the database.
adapter.Fill(dataset);

CHAPTER 9 ■ DATABASE ACCESS

464

// Print details of the schema.
Console.WriteLine("\nSchema for table");
DataTable table = dataset.Tables[0];
foreach (DataColumn col in table.Columns)
{
Console.WriteLine("Column: {0} Type: {1}",
col.ColumnName, col.DataType);
}


// Enumerate the data we have received.
Console.WriteLine("\nData in table");
foreach (DataRow row in table.Rows)
{
Console.WriteLine("Data {0} {1}", row[0], row[1]);
}

// Create a new row.
DataRow newrow = table.NewRow();
newrow["RegionID"] = 5;
newrow["RegionDescription"] = "Central";
table.Rows.Add(newrow);

// Modify an existing row.
table.Rows[0]["RegionDescription"] = "North Eastern";

// Enumerate the cached data again.
// Enumerate the data we have received.
Console.WriteLine("\nData in (modified) table");
foreach (DataRow row in table.Rows)
{
Console.WriteLine("Data {0} {1}", row[0], row[1]);
}

// Write the data back to the database.
adapter.Update(dataset);
}

// Wait to continue.

Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
}
}

CHAPTER 9 ■ DATABASE ACCESS

465

Running the example produces the following results:
Schema for table
Column: RegionID Type: System.Int32
Column: RegionDescription Type: System.String

Data in table
Data 1 Eastern
Data 2 Western
Data 3 Northern
Data 4 Southern

Data in (modified) table
Data 1 North Eastern
Data 2 Western
Data 3 Northern
Data 4 Southern
Data 5 Central



Main method complete. Press Enter.
CHAPTER 9 ■ DATABASE ACCESS

466

9-13. Create a DataSet Programmatically
Problem
You need to work with in-memory data without a database.
Solution
Create an instance of System.Sql.DataSet and manually populate it with instances of
System.Data.Datatable. Create a schema for each table and create rows to represent data elements.
How It Works
In the previous recipe, we demonstrated how to use the DataSet and DataTable classes as part of a
memory cache, in order to achieve disconnected data manipulation. However, you can create instances
of these classes to represent data programmatically by calling constructors for the classes directly. The
example code for this recipe illustrates how to do this in order to create the same kind of DataSet and
DataTable that we used previously.
The Code
The following code creates a DataSet that contains a single DataTable and populates it with instances of
DataRow. Once populated, the same queries, modifications, and additions are performed upon it as in the
previous recipe.

using System;
sing System.Data;

namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_13
{
static void Main(string[] args)

{
// Create the data set.
DataSet dataset = new DataSet();

// Create the table and add it to the data set.
DataTable table = new DataTable("Regions");
dataset.Tables.Add(table);

// Create the colums for the table.
table.Columns.Add("RegionID", typeof(int));
table.Columns.Add("RegionDescription", typeof(string));

CHAPTER 9 ■ DATABASE ACCESS

467

// Populate the table.
string[] regions = { "Eastern", "Western", "Northern", "Southern" };
for (int i = 0; i < regions.Length; i++)
{
DataRow row = table.NewRow();
row["RegionID"] = i + 1;
row["RegionDescription"] = regions[i];
table.Rows.Add(row);
}

// Print details of the schema.
Console.WriteLine("\nSchema for table");
foreach (DataColumn col in table.Columns)
{

Console.WriteLine("Column: {0} Type: {1}",
col.ColumnName, col.DataType);
}

// Enumerate the data we have received.
Console.WriteLine("\nData in table");
foreach (DataRow row in table.Rows)
{
Console.WriteLine("Data {0} {1}", row[0], row[1]);
}

// Create a new row.
DataRow newrow = table.NewRow();
newrow["RegionID"] = 5;
newrow["RegionDescription"] = "Central";
table.Rows.Add(newrow);

// Modify an existing row.
table.Rows[0]["RegionDescription"] = "North Eastern";

// Enumerate the cached data again.
// Enumerate the data we have received.
Console.WriteLine("\nData in (modified) table");
foreach (DataRow row in table.Rows)
{
Console.WriteLine("Data {0} {1}", row[0], row[1]);
}

// Wait to continue.
Console.WriteLine(Environment.NewLine);

Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
}
}

The program produces the following output:
CHAPTER 9 ■ DATABASE ACCESS

468

Schema for table
Column: RegionID Type: System.Int32
Column: RegionDescription Type: System.String

Data in table
Data 1 Eastern
Data 2 Western
Data 3 Northern
Data 4 Southern

Data in (modified) table
Data 1 North Eastern
Data 2 Western
Data 3 Northern
Data 4 Southern
Data 5 Central


Main method complete. Press Enter.

9-14. Perform a LINQ Query
Problem
You need to use LINQ to query a database.
CHAPTER 9 ■ DATABASE ACCESS

469

Solution
Create or obtain an instance of DataTable (see recipes 9-12 and 9-13) and call the AsEnumerable instance
method to obtain an IEnumerable<DataRow>, which can be used as a data source for LINQ queries.
How It Works
LINQ performs queries on the IEnumerable<> type, which you can obtain from instances of DataTable
using the AsEnumerable instance method. When using SQLDataAdapter to populate instances of DataTable
with data (see recipe 9-12), remember that you are working with cached data that will not reflect
changes made to the database. See Chapter 16 for recipes that demonstrate LINQ features.
The Code
The following example creates a DataSet that contains a DataTable with all of the rows of the Northwind
Region table, and then performs a LINQ query using the DataTable as the data source:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_14
{

static void Main(string[] args)
{

// Create a new SqlConnection object.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
con.ConnectionString = @"Data Source = .\sqlexpress;" +
"Database = Northwind; Integrated Security=SSPI";

// Open the database connection.
con.Open();

// Create the query string.
string query = "SELECT * from Region";

// Create the data set.
DataSet dataset = new DataSet();

CHAPTER 9 ■ DATABASE ACCESS

470

// Create the SQL data adapter.
SqlDataAdapter adapter = new SqlDataAdapter(query, con);
// Create the command builder so we can do modifications.
SqlCommandBuilder commbuilder = new SqlCommandBuilder(adapter);

// Populate the data set from the database.
adapter.Fill(dataset);


// Obtain the data table.
DataTable table = dataset.Tables[0];

// Perform the LINQ query.
IEnumerable<string> result = from e in table.AsEnumerable()
where e.Field<int>(0) < 3
select e.Field<string>(1);

// Enumerate the results of the LINQ query.
foreach (string str in result)
{
Console.WriteLine("Result: {0}", str);
}
}

// Wait to continue.
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
}
}

Running the program gives the following results:
Result: Eastern
Result: Western


Main method complete. Press Enter.

CHAPTER 9 ■ DATABASE ACCESS

471

9-15. Perform a LINQ Query with Entity Types
Problem
You need to work with types when using LINQ.
Solution
Define and annotate types with the Table and Column annotations and use
System.Data.Linq.DataContext to access the data in your database.
How It Works
LINQ includes support for entity classes, which map the schema from your database into .NET types.
You create an entity type by defining a partial class with members representing columns in a given
database table and apply annotations from the System.Data.Linq.Mapping namespace to give the .NET
Framework details of how to map from the table to instances of your entity type.
■ Tip Visual Studio can automatically create types for you. Select the Add a New Item option for your project, and
then select LINQ to SQL Classes to start a wizard that will generate the source files you require.
The first annotation to apply is Table, which creates the relationship between the partial class you
have defined and the table in the database—this annotation takes one argument, which is, not
surprisingly, the name of the table in question. You must then define one member for each column in
the table (ensuring that the member type matches the schema type for the database table), and apply the
Column annotation. For the Region table in the Northwind database, we would create a class like this:

[Table(Name = "Region")]
public partial class Region
{
[Column]
public int RegionID;
[Column]
public string RegionDescription;

}

To use the entity type, create an instance of System.Data.Linq.DataContext, passing in a
SqlConnection to your database as the constructor argument. You then call the DataContext.GetTable<>
instance method using your entity class as the type annotation—for example:
Table<Region> regionstable = context.GetTable<Region>();
The result from the GetTable method is a strongly typed instance of System.Data.Linq.Table, which
you can use as the data source for a LINQ query. In the clauses of the query, you can refer to the
CHAPTER 9 ■ DATABASE ACCESS

472

members of your entity type to perform filters and select results—see the code for this recipe for a
demonstration.
■ Tip LINQ entity types have a lot of features beyond what we have demonstrated here—see the .NET
documentation for further details. The LINQ to SQL home page is a good starting point:

The Code
The following example defines the type Region to represent rows in the Northwind Region table. A
DataContext is created to access the data, and the Region table is used as the basis for a LINQ query,
returning an IEnumeration<Region> as the result.
■ Note You must add the System.Data.Linq.dll assembly to your project in order to use the
System.Data.Linq and System.Data.Linq.Mapping namespaces.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Data.Linq;

using System.Data.Linq.Mapping;

namespace Apress.VisualCSharpRecipes.Chapter09
{
[Table(Name = "Region")]
public partial class Region
{
[Column]
public int RegionID;
[Column]
public string RegionDescription;
}
class Recipe09_15
{
static void Main(string[] args)
{

CHAPTER 9 ■ DATABASE ACCESS

473

// Create a new SqlConnection object.
using (SqlConnection con = new SqlConnection())
{
// Configure the SqlConnection object's connection string.
con.ConnectionString = @"Data Source = .\sqlexpress;" +
"Database = Northwind; Integrated Security=SSPI";

// Open the database connection.
con.Open();


// Create the data context.
DataContext context = new DataContext(con);

// Get the table we are interested in.
Table<Region> regionstable = context.GetTable<Region>();

IEnumerable<Region> result = from e in regionstable
where e.RegionID < 3
select e;
foreach (Region res in result)
{
Console.WriteLine("RegionID {0} Descr: {1}",
res.RegionID, res.RegionDescription);
}
}

// Wait to continue.
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Main method complete. Press Enter.");
Console.ReadLine();
}
}
}
9-16. Compare LINQ DataSet Results
Problem
You need to compare the results of a LINQ query.
Solution
Use the Union, Intersect, or Except extension method to compare the results of two LINQ queries.
CHAPTER 9 ■ DATABASE ACCESS


474

How It Works
The default result of a LINQ query on a DataSet is an IEnumerable<DataRow>, and LINQ provides
extension methods that operate on this result type to allow you to compare results.
■ Tip See Chapter 16 for more information about LINQ extension methods, recipes for using them, and creating
custom extension methods that you can apply to your own data types.
The three extension methods are Union, Intersect, and Except. In all three cases, you call the
extension method on one result and supply another as the method argument—for example:

IEnumerable<DataRow> result1 = LINQ query on a DataSet
IEnumerable<DataRow> result2 = LINQ query on a DataSet
IEnumerable<DataRow> union = result1.Union(result2)

The Union method combines the contents of the two IEnumerable<DataRow> instances. The
Intersect method returns just those rows that exist in both enumerations. The Except method returns
all of the rows in the first enumeration except those that also exist in the second enumeration.
The result of these methods is another IEnumerable<DataRow>, meaning that you can use the result
to enumerate the data rows or as the basis for a further LINQ query, and you can use the same extension
methods to compare the result against another IEnumerable<DataRow>.
The Code
The following program performs two queries against the same table and then uses the Union, Intersect,
and Except methods to compare the results:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

using System.Data.SqlClient;

namespace Apress.VisualCSharpRecipes.Chapter09
{
class Recipe09_16
{
static void Main(string[] args)
{

// Create a new SqlConnection object.
using (SqlConnection con = new SqlConnection())
{

×