
Pro CSharp 2008 And The .NET 3.5 Platform [eng]-1
.pdf
772 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
Console.WriteLine("Bad data! Try again"); break;
}
} while (!userDone);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
invDAL.CloseConnection();
}
}
Implementing the ShowInstructions() Method
The ShowInstructions() method does what you would expect:
private static void ShowInstructions()
{
Console.WriteLine("I: Inserts a new car."); Console.WriteLine("U: Updated an existing car."); Console.WriteLine("D: Deletes an existing car."); Console.WriteLine("L: List current inventory."); Console.WriteLine("S: Show these instructions."); Console.WriteLine("P: Look up pet name."); Console.WriteLine("Q: Quits program.");
}
Implementing the ListInventory() Method
The ListInventory() method obtains the DataTable returned from the GetAllInventory() method of the InventoryDAL object. After this point, we call a (yet to be created) function named
DisplayTable():
private static void ListInventory(InventoryDAL invDAL)
{
// Get the list of inventory.
DataTable dt = invDAL.GetAllInventory(); DisplayTable(dt);
}
The DisplayTable() helper method displays the table data using the Rows and Columns properties of the incoming DataTable (again, full details of the DataTable object appear in the next chapter, so don’t fret over the details):
private static void DisplayTable(DataTable dt)
{
// Print out the column names.
for (int curCol = 0; curCol < dt.Columns.Count; curCol++)
{
Console.Write(dt.Columns[curCol].ColumnName.Trim() + "\t");
} |
|
Console.WriteLine("\n---------------------------------- |
"); |

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
773 |
// Print the DataTable.
for (int curRow = 0; curRow < dt.Rows.Count; curRow++)
{
for (int curCol = 0; curCol < dt.Columns.Count; curCol++)
{
Console.Write(dt.Rows[curRow][curCol].ToString().Trim() + "\t");
}
Console.WriteLine();
}
}
Implementing the DeleteCar() Method
Deleting an existing automobile is as simple as asking the user for the ID of the car to blow out of the data table and passing this to the DeleteCar() method of the InventoryDAL type:
private static void DeleteCar(InventoryDAL invDAL)
{
//Get ID of car to delete.
Console.Write("Enter ID of Car to delete: "); int id = int.Parse(Console.ReadLine());
//Just in case we have a primary key
//violation!
try
{
invDAL.DeleteCar(id);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Implementing the InsertNewCar() Method
Inserting a new record into the Inventory table is simply a matter of asking the user for the new bits of data (via Console.ReadLine() calls) and passing this data into the InsertAuto() method of
InventoryDAL:
private static void InsertNewCar(InventoryDAL invDAL)
{
// First get the user data. int newCarID;
string newCarColor, newCarMake, newCarPetName;
Console.Write("Enter Car ID: "); newCarID = int.Parse(Console.ReadLine()); Console.Write("Enter Car Color: "); newCarColor = Console.ReadLine(); Console.Write("Enter Car Make: "); newCarMake = Console.ReadLine(); Console.Write("Enter Pet Name: "); newCarPetName = Console.ReadLine();

774 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
// Now pass to data access library.
invDAL.InsertAuto(newCarID, newCarColor, newCarMake, newCarPetName);
}
Implementing the UpdateCarPetName() Method
The implementation of UpdateCarPetName() is very similar:
private static void UpdateCarPetName(InventoryDAL invDAL)
{
// First get the user data. int carID;
string newCarPetName;
Console.Write("Enter Car ID: "); carID = int.Parse(Console.ReadLine()); Console.Write("Enter New Pet Name: "); newCarPetName = Console.ReadLine();
// Now pass to data access library. invDAL.UpdateCarPetName(carID, newCarPetName);
}
Invoking Our Stored Procedure
Obtaining the pet name of a given automobile is also very similar to the previous methods, given that the data access library has encapsulated all of the lower-level ADO.NET calls:
private static void LookUpPetName(InventoryDAL invDAL)
{
// Get ID of car to look up.
Console.Write("Enter ID of Car to look up: "); int id = int.Parse(Console.ReadLine()); Console.WriteLine("Petname of {0} is {1}.",
id, invDAL.LookUpPetName(id));
}
With this, our console-based front end is finished. Figure 22-16 shows a test run.
■Source Code The AutoLotCUIClient application is included under the Chapter 22 subdirectory.

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
775 |
Figure 22-16. Inserting, updating, and deleting records via command objects
Asynchronous Data Access Using SqlCommand
Currently, all of our data access logic is happening on a single thread of execution. However, allow me to point out that since the release of .NET 2.0, the SQL data provider has been enhanced to support asynchronous database interactions via the following new members of SqlCommand:
•BeginExecuteReader()/EndExecuteReader()
•BeginExecuteNonQuery()/EndExecuteNonQuery()
•BeginExecuteXmlReader()/EndExecuteXmlReader()
Given your work in Chapter 18, the naming convention of these method pairs may ring a bell. Recall that the .NET asynchronous delegate pattern makes use of a “begin” method to execute a task on a secondary thread, whereas the “end” method can be used to obtain the result of the asynchronous invocation using the members of IAsyncResult and the optional AsyncCallback delegate. Because the process of working with asynchronous commands is modeled after the standard

776CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
delegate patterns, a simple example should suffice (so be sure to consult Chapter 18 for full details of asynchronous delegates).
Assume you wish to select the records from the Inventory table on a secondary thread of execution using a data reader object. Here is a completely new Console Application (which does not make use of our InventoryDAL.dll assembly) named AsyncCmdObjectApp:
■Note When you wish to enable access data in an asynchronous manner, you must update your connection string with Asynchronous Processing=true (the default value is in fact, false).
static void Main(string[] args)
{
Console.WriteLine("***** Fun with ASNYC Data Readers *****\n");
//Create and open a connection that is async-aware.
SqlConnection cn = new SqlConnection(); cn.ConnectionString =
@"Data Source=(local)\SQLEXPRESS;Integrated Security=SSPI;" + "Initial Catalog=AutoLot;Asynchronous Processing=true";
cn.Open();
//Create a SQL command object that waits for approx 2 seconds. string strSQL = "WaitFor Delay '00:00:02';Select * From Inventory"; SqlCommand myCommand = new SqlCommand(strSQL, cn);
//Execute the reader on a second thread.
IAsyncResult itfAsynch;
itfAsynch = myCommand.BeginExecuteReader(CommandBehavior.CloseConnection);
//Do something while other thread works.
while (!itfAsynch.IsCompleted)
{
Console.WriteLine("Working on main thread..."); Thread.Sleep(1000);
}
Console.WriteLine();
// All done! Get reader and loop over results.
SqlDataReader myDataReader = myCommand.EndExecuteReader(itfAsynch); while (myDataReader.Read())
{
Console.WriteLine("-> Make: {0}, PetName: {1}, Color: {2}.", myDataReader["Make"].ToString().Trim(), myDataReader["PetName"].ToString().Trim(), myDataReader["Color"].ToString().Trim());
}
myDataReader.Close();
}
The first point of interest is the fact that you need to enable asynchronous activity using the new Asynchronous Processing segment of the connection string. Also note that you have padded into the command text of your SqlCommand object a WaitFor Delay segment simply to simulate a long-running database interaction.

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
777 |
Beyond these points, notice that the call to BeginExecuteDataReader() returns the expected IAsyncResult-compatible type, which is used to synchronize the calling thread (via the IsCompleted property) as well as obtain the SqlDataReader once the query has finished executing.
■Source Code The AsyncCmdObjectApp application is included under the Chapter 22 subdirectory.
Understanding Database Transactions
To wrap up our examination of the connected layer of ADO.NET, we will take a look at the concept of a database transaction. Simply put, a transaction is a set of database operations that must either all work or all fail as a collective whole. As you would imagine, transactions are quite important to ensure that table data is safe, valid, and consistent.
Transactions are very important when a database operation involves interacting with multiple tables or multiple stored procedures (or a combination of database atoms). The classic transaction example involves the process of transferring monetary funds between two bank accounts. For example, if you were to transfer $500.00 from your savings account into your checking account, the following steps should occur in a transactional manner:
•The bank should remove $500.00 from your savings account.
•The bank should then add $500.00 from to your checking account.
It would be a very bad thing indeed if the money was removed from the savings account, yet was not transferred to the checking account (due to some error on the bank’s part), as you are now out $500.00! However, if these steps were wrapped up into a database transaction, the DBMS would ensure that all related steps occur as a single unit. If any part of the transaction fails, the entire operation is “rolled back” to the original state. On the other hand, if all steps succeed, the transaction is “committed.”
■Note You may have heard of the acronym ACID when examining transactional literature. This represents the four key properties of a prim-and-proper transaction, specifically Atomic (all or nothing), Consistent (data remains stable throughout the transaction), Isolated (transactions do not step on each other’s feet) and Durable (transactions are saved and logged).
As it turns out, the .NET platform supports transactions in a variety of ways. Most importantly for this chapter is the transaction object of your ADO.NET data provider (SqlTransaction in the case of System.Data.SqlClient). In addition, the .NET base class libraries provide transactional supports within numerous APIs, including the following:
•System.EnterpriseServices: This namespace provides types that allow you to integrate with the COM+ runtime layer, including its support for distributed transactions.
•System.Transactions: This namespace contains classes that allow you to write your own transactional applications and resource managers for a variety of services (MSMQ, ADO.NET, COM+, etc.).
•Windows Communication Foundation: The WCF API provides services to facilitate transactions.
•Windows Workflow Foundations: The WF API provides transactional support for workflow activities.

778 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
In addition to the baked-in transactional support found within the .NET base class libraries, it is also possible to make use of the SQL language itself of your database management system. For example, you could author a stored procedure that makes use of the BEGIN TRANSACTION, ROLLBACK, and COMMIT statements.
Key Members of an ADO.NET Transaction Object
While transactional-aware types exist throughout the base class libraries, we will focus on transaction objects found within an ADO.NET data provider, all of which derive from DBTransaction
and implement the IDbTransaction interface. Recall from the beginning of this chapter that IDbTransaction defines a handful of members:
public interface IDbTransaction : IDisposable
{
IDbConnection Connection { get; } IsolationLevel IsolationLevel { get; } void Commit();
void Rollback();
}
Notice first of all that the Connection property, which will return to you a reference to the connection object that initiated the current transaction (as you’ll see, you obtain a transaction object from a given connection object). The Commit() method is called when each of your database operations have succeeded. By doing so, each of the pending changes will be persisted in the data store. Conversely, the Rollback() method can be called in the event of a runtime exception, which will inform the DMBS to disregard any pending changes, leaving the original data intact.
■Note The IsolationLevel property of a transaction object allows you to specify how aggressively a transaction should be guarded against the activities of other parallel transactions. By default, transactions are isolated completely until committed. Consult the .NET Framework 3.5 SDK documentation for full details regarding the values of the IsolationLevel enumeration.
Beyond the members defined by the IDbTransaction interface, the SqlTransaction type defines an additional member named Save(), which allows you to define save points. This concept allows you to roll back a failed transaction up until a named point, rather than rolling back the entire transaction. Essentially, when you call Save() using a SqlTransaction object, you are able to specify a friendly string moniker. When calling Rollback(), you are able to specify this same moniker as an argument to effectively do a “partial rollback.” When calling Rollback() with no arguments, all of the pending changes will indeed be rolled back.
Adding a Transaction Method to InventoryDAL
To illustrate the use of the ADO.NET transactions, begin by using the Server Explorer of Visual Studio 2008 to add a new table named CreditRisks to the AutoLot database, which has the same exact columns (CustID [which is the primary key], FirstName, and LastName) as the Customers table created earlier in this chapter. As suggested by the name, CreditRisks is where the undesirable customers are banished if they fail a credit check.
■Note We will be using this new transactional functionality in Chapter 26 when we examine the Windows Workflow Foundation API, so be sure to add the CreditRisks table to the AutoLot database as just described.

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
779 |
Much like the savings-to-checking money transfer example described previously, the act of moving a risky customer from the Customers table into the CreditRisks table should occur under the watchful eye of a transactional scope (after all, we will want to remember the ID and names of those who are not creditworthy). Specifically, we need to ensure that either we successfully delete the current credit risks from the Customers table and add them to the CreditRisks table or neither of these database operations occurs.
To illustrate how to programmatically work with ADO.NET transactions, open the AutoLotDAL Code Library project you created earlier in this chapter. Add a new public method named ProcessCreditRisk() to the InventoryDAL class that will deal with a perceived a credit risk as follows:
// A new member of the InventoryDAL class.
public void ProcessCreditRisk(bool throwEx, int custID)
{
//First, look up current name based on customer ID. string fName = string.Empty;
string lName = string.Empty; SqlCommand cmdSelect = new SqlCommand(
string.Format("Select * from Customers where CustID = {0}", custID), sqlCn); using (SqlDataReader dr = cmdSelect.ExecuteReader())
{
while (dr.Read())
{
fName = (string)dr["FirstName"]; lName = (string)dr["LastName"];
}
}
//Create command objects that represent each step of the operation.
SqlCommand cmdRemove = new SqlCommand(
string.Format("Delete from Customers where CustID = {0}", custID), sqlCn);
SqlCommand cmdInsert = new SqlCommand(string.Format("Insert Into CreditRisks" + "(CustID, FirstName, LastName) Values" +
"({0}, '{1}', '{2}')", custID, fName, lName), sqlCn);
// We will get this from the connection object.
SqlTransaction tx = null; try
{
tx = sqlCn.BeginTransaction();
//Enlist the commands into this transaction. cmdInsert.Transaction = tx; cmdRemove.Transaction = tx;
//Execute the commands. cmdInsert.ExecuteNonQuery(); cmdRemove.ExecuteNonQuery();
//Simulate error.
if (throwEx)
{
throw new ApplicationException("Sorry! Database error! Tx failed...");
}

780 CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER
// Commit it! tx.Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
// Any error will roll back transaction. tx.Rollback();
}
}
Here, we are using an incoming bool parameter to represent whether we will throw an arbitrary exception when attempting to process the offending customer. This will allow us to easily simulate an unforeseen circumstance that will cause the database transaction to fail. Obviously, this is only done here for illustrative purposes; a true database transaction method would certainly not want to allow the caller to force the logic to fail on a whim!
Once we obtain the customer’s first and last name based on the incoming custID parameter, note that we are using two SqlCommand objects that represent each step in the transaction we will be kicking off, and we obtain a valid SqlTransaction object from the connection object via BeginTransaction(). Next, and most importantly, we must enlist each command object by assigning the Transaction property to the transaction object we have just obtained. If you fail to do so, the Insert/Delete logic will not be under a transactional context.
After we call ExecuteNonQuery() on each command, we will throw an exception if (and only if) the value of the bool parameter is true. In this case, all pending database operations are rolled back. If we do not throw an exception, both steps will be committed to the database tables once we call Commit(). Compile your modified AutoLotDAL project to ensure you do not have any typos.
Testing Our Database Transaction
While you could update your previous AutoLotCUIClient application with a new option to invoke the ProcessCreditRisk() method, let’s create a new Console Application named AdoNetTransaction to do so. Set a reference to your AutoLotDAL.dll assembly, and import the AutoLotConnectedLayer namespace.
Next, open your Customers table for data entry by right-clicking the table icon from the Server Explorer and selecting Show Table Data. Add a new customer who will be the victim of a low credit score, for example:
•CustID: 333
•FirstName: Homer
•LastName: Simpson
Now, update your Main() method as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Simple Transaction Example *****\n");
// A simple way to allow the tx to succeed or not. bool throwEx = true;
string userAnswer = string.Empty;
Console.Write("Do you want to throw an exception (Y or N): "); userAnswer = Console.ReadLine();
if (userAnswer.ToLower() == "n")

CHAPTER 22 ■ ADO.NET PART I: THE CONNECTED LAYER |
781 |
{
throwEx = false;
}
InventoryDAL dal = new InventoryDAL();
dal.OpenConnection(@"Data Source=(local)\SQLEXPRESS;Integrated Security=SSPI;" + "Initial Catalog=AutoLot");
// Process customer 333. dal.ProcessCreditRisk(throwEx, 333); Console.ReadLine();
}
If you were to run your program and elect to throw an exception, you would find that Homer is not removed from the Customers table, as the entire transaction has been rolled back. However, if you did not throw an exception, you would find that Customer ID 333 is no longer in the Customers table and has been placed in the CreditRisks table (see Figure 22-17).
Figure 22-17. The result of our database transaction
■Source Code The AdoNetTransaction project is included under the Chapter 22 subdirectory.
Summary
ADO.NET is the native data access technology of the .NET platform, which can be used in two distinct manners: connected or disconnected. In this chapter, you examined the connected layer and came to understand the role of data providers, which are essentially concrete implementations of several abstract base classes (in the System.Data.Common) namespace and interface types (in the System.Data namespace). As you have seen, it is possible to build a provider-neutral code base using the ADO.NET data provider factory model.
Using connection objects, transaction objects, command objects, and data reader objects of the connected layer, you are able to select, update, insert, and delete records. Also recall that command objects support an internal parameter collection, which can be used to add some type safety to your SQL queries and are quite helpful when triggering stored procedures.