DO .NET provides many rich features that can be used to retrieve and display data in a number of ways, but even given such flexibility, sometimes developers find themselves repeating the same code again and again. For example, every data-driven application requires access to a database. You need to write code to connect to the database, open a connection, execute dynamic SQL or stored procedures, retrieve the results, and close the connection. In other words, this is “plumbing” code that you will be forced to write in almost any application you develop, even though it doesn’t add any value to the core business users.
Typically, the only things in this code that might differ between applications are the SQL statements or stored procedure names, the command parameters, and the connection string. As long as you can parameterize these variables, you can abstract most of the plumbing code into reusable classes that can be leveraged across multiple applications. That’s a worthwhile effort?but it’s no longer worth doing yourself, because that’s exactly what Microsoft (with community support) has done in its set of Enterprise Library (known as EntLib) Application Blocks. This article introduces the EntLib Data Access Block by showing examples of how to use it to write robust data access code.
The Enterprise Data Access Block
The Data Access Block addresses the most common tasks developers face when developing database applications. By providing a set of encapsulated methods, the Data Access Block greatly simplifies the most common methods of accessing a database. Each method contains the logic required to retrieve the appropriate data, and also manage the database connection. In addition, the Data Access Block is database-agnostic; it supplements the code in ADO.NET 2.0 by letting you write data access code that works across different relational databases?without rewriting code. The classes contain code that provides database-specific implementations for features such as parameter handling and cursors.
Moreover the Data Access Block also provides specific derived classes for SQL Server and Oracle databases. In addition, the GenericDatabase class allows you to use the application block with any configured ADO.NET 2.0 DbProviderFactory object. Finally, you can extend the application block by adding new database types that include database-specific features or that provide a custom implementation of an existing database.
Microsoft has redesigned version 2.0 of the Data Access Block to take advantage of new ADO.NET 2.0 features. You can download the EntLib Data Access Block from MSDN.
Using the Data Access Block
To use the Data Access Block successfully, you will need to go through the steps listed below:
- Add a reference to the Microsoft.Practices.EnterpriseLibrary.Common.dll and Microsoft.Practices.EnterpriseLibrary.Data.dll assemblies from your solution. You can do this by using the “Add Reference” option and navigating to the
:Program FilesMicrosoft Enterprise Library January 2006in folder. - Add the necessary configuration entries to the web.config or app.config file or a custom configuration file. To this end, you add the below
element under the root element. Then you also add the
element as shown below: In this example, I have marked AdventureWorks as the default database, declared separately under the
element. - Import the core Microsoft.Practices.EnterpriseLibrary.Data namespace for the Data Access Block.
- Start writing code against the classes in the preceding namespace.
Using the Database Object
Whenever you work with the Data Access Block, you’ll have to deal with the Database class first. The Database class represents the database itself, and provides methods (see Table 1) that you can use to perform CRUD (Create, Read, Update and Delete) operations against the database.
Table 1: The table lists Database class methods that you use to perform CRUD operations against the database.
Method | Description |
ExecuteDataSet | Executes a SQL query or stored procedure and returns the results in the form of a DataSet object. |
ExecuteNonQuery | Executes a SQL query or stored procedure and returns the number of records affected. |
ExecuteReader | Executes a SQL query or stored procedure and returns the results in the form of an IDataReader object. |
ExecuteScalar | Executes a SQL query or stored procedure and returns the first column of the first row in the result set. |
LoadDataSet | Executes a command and adds a new DataTable to the existing DataSet. |
UpdateDataSet | Synchronizes the DataSet contents with the database by executing appropriate INSERT, UPDATE, and DELETE statements. |
Now that you have a basic overview of the methods of the Database class, the next few sections provide examples that demonstrate how you use the class.
Execute a Query Returning a DbDataReader
I’ll start with a simple example. You’ll execute a simple SQL statement against the AdventureWorks database and retrieve the results in the form of a DbDataReader object, which you’ll then bind to a GridView control in an ASP.NET page. Listing 1 contains the full page code. Here’s the Page_Load script:
![]() | ? |
Figure 1. Binding a DbDataReader: The page shows the result of binding the DbDataReader object returned by the ExecuteReader() method to a GridView control. |
The preceding code first invokes the CreateDatabase() method of the DatabaseFactory class to obtain an instance of the Database object. As the name suggests, the DatabaseFactory is a class containing factory methods that create Database objects. The CreateDatabase() method is overloaded and returns a default database when you invoke the parameter-less version of the method as shown above. To set the default database, assign the defaultDatabase attribute value to the appropriate database configuration key in the Web.config file as shown below.
After obtaining an instance of the Database object, you then invoke the GetSqlStringCommand() method to construct an instance of the ADO.NET 2.0 DbCommand object. After that, you execute the SQL through the Database.ExecuteReader() method passing in the DbCommand as an argument. Navigate to the page using a browser. Because the method binds the returned DbDataReader to a GridView control, you’ll see output similar to Figure 1.
Execute a SQL Query Returning a DataSet
The Database class provides a database-agnostic approach, meaning that you can leverage the class across a wide range of relational databases such as SQL Server, Oracle and others. However, it also contains database-specific classes, such as SqlDatabase (derived from Database), which represents a SQL Server database, and OracleDatabase, which lets you program against the Oracle database. You should use these database-specific classes when you’re programming solely for one of the supported databases.
Note that neither the SqlDatabase nor the OracleDatabase classes provide static methods; you must create an instance to work with them. The following page code shows how to execute a SQL query against the AdventureWorks database using the SqlDatabase class.
<%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.Common" %> <%@ Import Namespace= "Microsoft.Practices. EnterpriseLibrary.Data" %> <%@ Import Namespace= "Microsoft.Practices. EnterpriseLibrary.Data.Sql" %> Executing a SQL Select Statement to retrieve a DataSet
Author’s Note: The namespaces you import (Imports) and the page HTML code for the rest of the examples in this article are similar to the preceding example, so I’ll omit those in the remaining examples; however, you can download the sample code to get complete sample pages that you can experiment with. |
You start by retrieving the connection string from the Web.config file using the WebConfigurationManager class, then you supply the connection string as an argument to the SqlDatabase class constructor. After you have an instance of the SqlDatabase class, you work with it in the same manner as the generic Database class, because SqlDatabase inherits from Database. For example, the SqlDatabase class also exposes methods such as GetSqlStringCommand(), and ExecuteDataSet() through which you can retrieve a DbCommand object instance, execute a SQL query, and return the results in the form of a DataSet object. Although the preceding example uses a SELECT SQL statement, you can easily perform the same operation using a stored procedure.
Executing a Stored Procedure to Retrieve a Single Item
Sometimes, you might want to retrieve only one item from a stored procedure or SQL query. For example, after inserting a new row into the database, you might want to return the newly inserted identity value to the consumers.
For this purpose, the Database class provides a method named ExecuteScalar() that accepts a DbCommand object as an input argument. To showcase this, I will leverage a stored procedure named GetEmployeeNationalID that takes an EmployeeID as an argument and returns the NationalID value for that employee. The stored procedure is declared as follows:
Create Procedure GetEmployeeNationalID @EmployeeID int AS Select NationalIDNumber from HumanResources.Employee Where EmployeeID = @EmployeeID
The following ASP.NET page excerpt shows the code required to invoke the GetEmployeeNationalID stored procedure.
First, the code adds the EmployeeID input parameter to the DbCommand object using the AddInParameter() method. Note that the EmployeeID parameter is not prefixed with an ampersand (@), which is how you would normally indicate a stored procedure parameter for SQL Server?and how the previous version of the Data Access Block worked. In this new version of the Data Access Block, the Database class is generic, obviating the need to specify database-specific prefixes such as ampersands and question marks. The ExecuteScalar() method executes the stored procedure and returns the NationalID, which the code displays on the client using the Response.Write() statement at the end of the script.
Executing a Stored Procedure to Retrieve a Single Row
Sometimes you need to retrieve a single row instead of a group of rows. Instead of returning this single row as a DataSet or a DataReader, you can have the stored procedure return the single row as a set of output parameters. To do this, you use the ExecuteNonQuery() method of the Database class. As part of the preparation of the DbCommand, you need to add the output parameters to the DbCommand object. Here’s the stored procedure declaration.
Create Procedure GetEmployeeDetails @EmployeeID int, @NationalIDNumber nvarchar(15) OUTPUT, @LoginID nvarchar(256) OUTPUT, @Title nvarchar(50) OUTPUT AS Select @NationalIDNumber = NationalIDNumber, @LoginID = LoginID, @Title = Title from HumanResources.Employee Where EmployeeID = @EmployeeID GO
The following Page_Load code executes the GetEmployeeDetails stored procedure and returns the NationalIDNumber, LoginID, and Title column values as output parameters.
In the preceding code, you add the output parameters using the AddOutParameter() method of the DbCommand object. After executing the stored procedure, you simply retrieve the output parameter values using the GetParameterValue() method of the DbCommand object.
Transactional Code Block for Performing Multiple Updates
There are times where you may want to execute multiple operations against a database, but perform them all within the scope of a single transaction. The EntLib Data Access Block enables this scenario by providing the Database.CreateConnection() method that allow you to get a reference to an ADO.NET 2.0 DbConnection object. Using the DbConnection object, you can obtain a reference to a DbTransaction object by calling the BeginTransaction() method, assigning the return value to a DbTransaction variable. Subsequently, you can then easily control the transaction behavior by invoking either the Commit() or Rollback() methods when the execution succeeds or fails, respectively. The following pseudo code shows how to execute multiple stored procedures within the scope of a single transaction.
Database db = DatabaseFactory.CreateDatabase(); //Two operations, one to add the order //and another to add order details string sqlCommand = "InsertOrder"; DbCommand orderCommand = db.GetStoredProcCommand(sqlCommand); //Add InsertOrder parameters sqlCommand = "InsertOrderDetails"; DbCommand orderDetailsCommand = db.GetStoredProcCommand(sqlCommand); //Add InsertOrderDetails parameters using (DbConnection connection = db.CreateConnection()) { connection.Open(); DbTransaction transaction = connection.BeginTransaction(); try { //Execute the InsertOrder db.ExecuteNonQuery(orderCommand, transaction); //Execute the InsertOrderDetails db.ExecuteNonQuery( orderDetailsCommand, transaction); //Commit the transaction transaction.Commit(); } catch { //Roll back the transaction. transaction.Rollback(); } }
The majority of the work happens inside the using block. That block starts the transaction using the BeginTransaction() method and also commits or rolls back the transaction using either Commit() or Rollback().
Keep It Short
As you have seen from this article, the EntLib Data Access Block obviates the need to write the repetitive data access plumbing code by providing a set of highly reusable classes for performing operations against the database. Through the use of these classes, you can eliminate errors, bugs, and typos in your application and focus on the core business logic of the application thereby gain productivity.