Академический Документы
Профессиональный Документы
Культура Документы
In this article, we will drill down deeper in to the design of a n-tier architecture and
our focus will be on the data access tier(DAT) (Figure 0).Our goal is to design and
implement an appropriate data access tier(DAT) for scalable, reliable and interoperable
web application. I have divided this article in to two major parts. In the first part , we
will discuss about architectural goals, typed DataSet, EventLogs and HttpModules ,
which are very helpful to understand the second part of this article . We will build in
the second part of this article a small(but fine) n-tier web application and our focus will
be on the design and implementation of data access tier. You need a SqlServer
(database: Northwind) and VS.net to follow this article.
Contents:
1. Designing Goals of a data access tier(DAT)
1.1 Architectural Goals
1.2 Installation of the prototype
1.3 Typed DataSets
1.4 Event Logs
1.5 HttpModules
2.0 Implementing versatile data access tier(DAT)
2.1 Implemeting a super class for data access tier.
2.2 A performance test to evaluate SqlDataReader and DataSet objects in the business
tier.
2.3 How can I extend this data access tier (DAT) to access multi-databases.
2.4 How can I apply database transactions with different isolation level?
2.5 How can I insert and retrieve images?
2.6 Does this Data Access Tier supports presentation tier such like custom paging?
1
Figure 0 shows us a typical 3 Tier Application that is dissected
in to three major disjunctive layers, which are data tier (DA) ,middle tier and
presentation tier (PT).
If you are not familiar with n-tier architecture , I like to recommend following articles.
http://www.c-sharpcorner.com/Tutorials/Building3TierAppPA.asp
2
Let us look some of the buzzwords closer, what they really mean?
Reliability of a software system can be defined as the probability that a system will
perform its task without failure. To achieve higher reliability, a system must provide
utilities to track possible errors.
3
Figure 1 shows partial ER-Diagram of the database Northwind
So, we have now great goals. In order to achieve these goals , we have to lay
a proper groundwork. Our data access tier (DAT) must be designed to accomplish
following tasks.
4
Figure 2 shows a screen shot of the demo web application DAPrototype
Assembly Description
5
HttpTraceModule contents a HttpModule to trace errors in the presentation layer
<appSettings>
<!-- Database Connection -->
<add key="LocalConnection"
value="server=localhost;database=Northwind;uid=sa;pwd=moses;
pooling=true; Max Pool Size=100;" />
<!-- Event Log for Data Access Tier -->
<add key="daMachine" value="ABRAHAM"/>
<add key="daLog" value="DALog"/>
<add key="daSource" value="DASource"/>
<!-- Event Log for Business Tier -->
<add key="blMachine" value="ABRAHAM"/>
<add key="blLog" value="BLLog"/>
<add key="blSource" value="BLSource"/>
<!-- Event Log for Prsentation Tier -->
<add key="plMachine" value="ABRAHAM"/>
<add key="plLog" value="PLLog"/>
<add key="plSource" value="PLSource"/>
6
Figure 4
7
Figure 5
In this case , Dataset “ German” , needs two tables State and City and a data relation
to represent this XML document. We don’t want to dig here further, because it is
beyond our focus.
Let us look a typical data access scenario. Say , you using one of the overloaded method
SqlDataAdapter.Fill to populate a DataSet(untyped) from a database . The
SqlDataAdapter.Fill method takes a DataSet and a name of the DataTable as
parameters. DataTables and DataColumns will be only created in the Dataset, if they
don’t already exist in the DataSet. In this case, it will retrieve the required schema
from the data source using the SqlDataAdapter.Schema method , after that DataTables
and DataColumns will be created, which will fit the loading data.
DataSet (untyped) offers great deal of plasticity at runtime, but they are error-prone,
because developers must anticipate the tables and its attributes, while they code. In
worst case , developers must trace back from business layer (BL) to stored procedures to
find out the content of a DataSet. Additionally, it is not always possible to follow back
A such case is given, when you receive a DataSet from a third party via Webservice
and it is not well documented The most errors(e.g. type mismatch errors) concerning
DataSets can be trapped only during the runtime. You can wipe out this sort of problem
using typed DataSets.
Typed DataSets are subclasses of the class DataSet and it has built-in and solid-stated
(hard coded) DataTables and DataRows, which are specified to represent a certain
relational data model. Metaphorically spoken, untyped DataSets are like T-Shirts and
a typed DataSet can be compared to a made-to-measure suit.
1. It is easy read and write source code because hard-coded DataTables and
DataRows exposes attributes and their types during the coding time. If you use
untyped DataSet then you have to search them explicitly in the DataTable and
DataRow collections and you must know their types.
2. Type mismatch errors can be discovered during the compile time.
3. Access to DataTables and DataRows of a typed Dataset is slightly faster
because the access is determined at compile time and you don’t need to search
DataTables and DataRows through accessing collections at runtime.
Now , we like show you, how create to a typed DataSet using of Visual Studio
IntelliSence. We like create a typed DataSet DSCustomer ,in which we can store data
from customers and their orders. We will use store procedures SPSelCustomers and
SPSelOrders (See Fig 6) to fetch data from master-detail tables Customer and
Orders
8
CREATE PROCEDURE SPSelCustomers
(
@CustomerID varchar(5)
)
AS
SELECT
C.CustomerID,C.CompanyName,C.Address,C.City
FROM Customers C
WHERE C.CustomerID LIKE @CustomerID + '%'
GO
In order create the typed DataSet DSCustomer , we must first design a blueprint . A
XSD schema serves this purpose(See Fig 7). Follow these steps to create such a schema.
9
(Click on the XML view and type the following statements on the file
DSOrders.xsd , which are marked by an underline
Note: If you want use typed Datasets in Web service, then it is important to keep in mind
that xml annotations to create friendly name won’t work with WSDL (web Service
Description Language).
10
Figure 8
The following code snippet demonstrates that is very comfortable to write and read
source code with the typed DataSet “DSCustomer” , for it exposes its built-in
DataTable and DataRows as properties .
}
}
}
11
1.4 Event Logs
Event Logs are centralized Windows resources to protocol important software and
hardware events and it is indispensable to determine error sources. You can easily
connect event logs of remote computers on your local computer, consequently event
logs are suitable to analyze a n-tier application which is deployed on more than one
machine.
You can use the EventLog class (namespace: System.Diagnostics) to deal with event
logs and they allow you write different types of entries (e.g. information, warning, error
etc) that helps us to express severity of occurred events. The following code snippet
shows the basics of the class EventLog .
}
else
{
Console.WriteLine( source for BL exists already);
// Delete EventLog.Delete(strLogBL,strMachine);
}
Of course , you can delete an existing EventLog using the event the EventLog.Delete
method. Writing on Event Logs consumes resources(CPU time and disk space),
therefore it is recommended to write only important events.
12
1.4 HttpModules
HttpModule is an useful instrument to customize ASP.NET applications. In other words
, when CLR processes a web-request from a client, you can use HttpModules to take
influence on every stage of html- render process. We will see later in details that
request-processing in ASP.net relies on the pipeline process model., which enables
parallel processing (Webgarten ) and enhance expendability and manageability of a web
application .
13
Let us look , what happens in a server , when it processes a request.
Say, a client submits a request for the page Test.aspx .
Step 1:
IIS will extract page name’s suffix, in our concrete example “ .aspx “ , in order to load
an appropriate ISAPI extension.. IIS will look in the metabase and load for the suffix
“.aspx “ the extension aspnet_isapi.dll . (You can manage the extension- loading
process by editing the Application Configuration dialog(See Fig 10). You can open this
dialog by launching the Internet Services and expand -> right click on Default Web Site
-> select the option “Properties” -> select the tab “Home Directory “
-> push the button “Configuration” )
Figure 10
14
Step 2
Now, our request is now passed to a System.Web.HttpRuntime object, which marks the
entry point of the process pipeline and the frontier of managed code . In fact, the
HttpRuntime class is responsible for processing the requests, but it funnels incoming
requests through the pipeline for further processing. The client request will be wrapped
into an instance of the HttpContext class and delivers it to an appropriate instance of
HttpApplication class . ASP.Net treats every virtual directory as a separate application.
The HttpRuntime object will examine the client request and uses an instance of the
HttpApplicationFactory class to create or find a proper instance of the HttpApplication
class.
Step 3
The HttpApplication class is responsible for managing the entire life cycle of request
execution process ; therefore, it raises events to mark pipeline stages(e.g .
BeginRequest, PreRequestHandlerExcecute, PostReuestHandler, EndRequesthandler,
Error …etc). You can now write handlers to subscribe events of the HttpApplication
class in order to customize execution process . There are two major ways to implement
it. You can use directly the Global class, which inherits the HttpApplication class
(implemented in the file Global.asax.cs ), but if you hardcode the handlers in the
instance Gloabal , then it will reduce manageability and flexibility of the application.
HttpModules are designed to solve this problem in an elegant manner . Spoken in the
language of design and pattern, HttpModules can be considered as observers of the
subject HttpApplication , because they are designed to intercept events of the class
HttpApplication . They implement the interface IHttpModule and complied in to a
separate assembly .We will show you later in an concrete example, how to write a
HttpModule. Let us move to next step.
Note :
A HttpApplication object can be used to process many requests, but it can process
only one request at a time and ASP.Net doesn’t treat a HttpApplication object as
singleton , particularly in webgarden. So , this class is not appropriate to hold properties
which deals with whole application such like number of current users.
15
Step 4.
Now, we come to the climax of request processing. In this phase ,contents of the server
side controls are transformed into HTML code and saved into the instance of the
HttpContext class and this scenario can be described through the pattern abstract
factory . Let us look, how this procedure realized.
interface IHttpModule
{
void Init(HttpApplication httpApplication);
void Dispose();
}
16
The method “void Init” is normally used subscribe events from HttpApplication and
method “ void Dispose” is a potential candidate to release unmanaged resources .
using System;
using System.Web;
using System.Diagnostics;
using System.Configuration;
using System.Text;
namespace HttpTraceModule
{
/// <summary>
/// It used to record errors on the presentation layer(PL)
/// </summary>
public class HttpTraceModule:IHttpModule
{
/// <summary>
/// Event Log for the Presentation Layer
/// </summary>
EventLog elPL;
/// <summary>
///
/// </summary>
public HttpTraceModule()
{
// Initialize Trace Listener
string strLog = ConfigurationSettings.AppSettings["plLog"];
string strSource = ConfigurationSettings.AppSettings["plSource"];
string strMachine = ConfigurationSettings.AppSettings["plMachine"];
if(EventLog.SourceExists(strLog,strMachine))
{
elPL = new EventLog(strLog,strMachine,strSource);
}
/// <summary>
/// interface contract method
/// </summary>
/// <param name="httpApplication"></param>
public void Init(HttpApplication httpApplication)
{
// Subscribe HttpApplication events
17
httpApplication.BeginRequest += new
EventHandler(Application_BeginRequest);
httpApplication.EndRequest += new EventHandler(Application_EndRequest);
}
/// <summary>
/// interface contract method
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Writes errors on the log of the Presentation Layer
/// </summary>
/// <param name="oException"></param>
/// <param name="oContext"> </param>
private void WriteError(HttpContext oContext,Exception oException)
{
StringBuilder oBuilder = new StringBuilder();
// Record the requested URL and the client
oBuilder.Append("An Error took place, while attempting to request the
URL: ");
oBuilder.Append(oContext.Request.RawUrl);
oBuilder.Append("**");
oBuilder.Append("Client Address:");
oBuilder.Append(oContext.Request.UserHostAddress);
oBuilder.Append("**");
// Record the error message
if( oException != null)
{
oBuilder.Append( oException.Message);
}
if(elPL != null)
{
elPL.WriteEntry(oBuilder.ToString(),EventLogEntryType.Error);
}
/// <summary>
/// Callback method for the event HttpApplication.EndRequest
/// It is used to record all errors on the log of the Presentation Layer
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void Application_EndRequest(object source, EventArgs e)
{
HttpApplication hApplication = (HttpApplication) source;
HttpContext oContext = hApplication.Context;
Exception oException = oContext.Error;
if(oException != null)
{
this.WriteError(oContext,oException);
}
18
}
/// <summary>
/// Callback method for the event HttpApplication.BeginRequest
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void Application_BeginRequest(object source, EventArgs e)
{
HttpApplication hApplication = (HttpApplication) source;
HttpContext oContext = hApplication.Context;
}
}
}
Figure 11
19
Selecting DataRows using DataSet
Step 1:
Create objects for the database connection to the database and SqlCommand object
for the stored procedure “strSP” .
SqlConnection dbConnection_L = new SqlConnection(strLocalServer);
SqlCommand dbCommand_L = new SqlCommand (strSP,dbConnection_L);
dbCommand_L.CommandType = CommandType.StoredProcedure;
Step 2:
Create an instance of SqlDataAdapter class and associate with the SqlCommand
object: dbCommand_L.
SqlDataAdapter dbAdapter_L = new SqlDataAdapter();
dbAdapter_L.SelectCommand = dbCommand_L;
Step 3:
Add the query parameters to the SqlCommand object:
SqlParameter oParameterIn = new SqlParameter(“@ParaIn”
,SqlDbType.Int,4);
oParameterIn.Direction = ParameterDirection.Input;
oParameterIn.Value = 7;
dbCommand_L.Parameters.Add(oParameterIn);
Step 4:
Open the database connection : dbConnection_L.Open(); We don’t need to open the
connection explicitly here, because dbAdapter_L object will open the connection
implicitly in the step 5.
Step 5:
Fill the Dataset:
DataSet dsOut = new Dataset();
string strTable = Customer ;
dbAdapter_L.Fill(dsOut,strTable);
Step 6:
Close the database connection: dbConnection_L.Close();
Step 1:
Create an object for the connection to the database and an instance of SqlCommand
for the stored procedure “strSP “.
SqlConnection dbConnection_L = new SqlConnection(strLocalServer);
20
SqlCommand dbCommand_L = new SqlCommand (strSP,dbConnection_L);
dbCommand_L.CommandType = CommandType.StoredProcedure;
Step 2:
We don’t need the step 2 here.
Step 3:
Add the query parameters to the SqlCommand object:
SqlParameter oParameterIn = new SqlParameter(“@ParaIn”
,SqlDbType.Int,4);
oParameterIn.Direction = ParameterDirection.Input;
oParameterIn.Value = 7;
dbCommand_L.Parameters.Add(oParameterIn);
Step 4:
Open the database connection : dbConnection_L.Open();
Step 5:
Receive the reader by executing the SqlCommand .ExcecuteReader method
SqlDataReader oReader =
dbCommand_L.ExecuteReader(CommandBehavior.CloseConnection);
Step 6:
Because of the enumerator “ CommandBehavior CloseConnection” we don’t need
the step 6 .
Step 1:
Create a connection object to the database and an instance of SqlCommand class
for the stored procedure “strSP”.
SqlConnection dbConnection_L = new SqlConnection(strLocalServer);
SqlCommand dbCommand_L = new SqlCommand (strSP,dbConnection_L);
dbCommand_L.CommandType = CommandType.StoredProcedure;
Step 2:
21
We don’t require this step here, because we don’t need any instance of a class
SqlDataAdapter in this process.
Step 3:
Add the in- parameters and out-parameters to the SqlCommand object:
Step 4:
Open the database connection explicitly : dbConnection_L.Open();
Step 5:
Execute the non-query and retrieve output-parameter value
int nAffected = dbCommand_L.ExecuteNonQuery();
int nOut = (int) oParameterOut.Value;
Step 6:
Close the database connection: dbConnection_L.Close();
As you can see, we have here ideal condition to design a super class .First of all, we must
capsule the important data access objects as protected elements in the basic class
DABasis ( Please refer parallel the Figure 12 and source code on the file
DAPrototype/DAT/DABaisis.cs )
// Objects for the local database access
protected SqlConnection dbConnection_L;
protected SqlCommand dbCommand_L;
protected SqlDataAdapter dbAdapter_L;
protected OleDbTransaction dbTransaction_L;
We use here the suffix “_L” , in order to express that these object deals with the local
sql server. This notation will enhance clarity, if our DAT deals with many databases.
22
/// <summary>
/// This method initialize data access utilities for the local
/// Sqlserver this method must be always called at first
/// </summary>
/// <param name=strSP>name of the stored procedure</param>
protected virtual void Prepair_L(string strSP)
{
try
{
dbConnection_L = new SqlConnection(strLocalServer);
dbCommand_L = new SqlCommand (strSP,dbConnection_L);
dbCommand_L.CommandType = CommandType.StoredProcedure;
}
catch(Exception oException)
{
string strMessage = Occurred in Prepa_L() ;
ErrorLog(strMessage,oException);
}
23
Figure 12 shows the class diagram of the data access tier(DAT)
The functionalities of Step 3 will be implemented in the derived class, but the super
class supports this step with a parameter-factory . The parameter-factory is designed
according to the pattern abstract factory and it will supply us with several type of
SqlParameters with variations Input and Output direction, which are used set and
retrieve values from the parameters . Further more , super class DABasis supplies a
24
method virtual void AddParameter_L(object oParameter) to add an instance
of SqlParameter class to the dbCommand_L object . Thus makes your code compact
and neat.
The super class DABasis supports step 4 with the void Open_L() method to open
the database to the local server and the complement step 6 is covered by the void
Close_L() method .
In the first practical example, we want to retrieve order details for an order using the
DataSet object.The workflow for the implementation can be sketched through
following steps.
GO
Figure 13
25
/// <summary>
/// Retrieves order details for an OrderID
/// </summary>
/// <param name=nOrderID>OrderID</param>
/// <param name=dsOrderDetail>typed DataSet</param>
public void GetOrderDetail(int nOrderID,out DSOrderDetail dsOrderDetail)
{
// Create the typed Dataset for output
dsOrderDetail = new DSOrderDetail();
try
{
// lock the intrinsic DataAcces utilities
Monitor.Enter(this);
// intialize DataAcces utilities for the local SqlServer
Prepair_L(“SPSelOrderDetail”);
dbAdapter_L = new SqlDataAdapter();
dbAdapter_L.SelectCommand = dbCommand_L;
// Add query parameters to the command
SqlParameter pmOrderID = pmFactory_In.GetPMInt4(“@OrderID”);
pmOrderID.Value = nOrderID;
AddParameter_L(pmOrderID);
string strTable = dsOrderDetail.OrderDetails.TableName;
// Retrive the typed DataSet
GetDataSet_L(dsOrderDetail,strTable);
}
catch(Exception oException)
{
string strError;
strError = An Error Occured in DAOrder:GetOrderDetailOrders;
this.ErrorLog(strError,oException);
}
finally
{
this.Close_L();
Monitor.Exit(this);
}
}
Figure 14
Note: In order to protect intrinsic data –access objects from concurring threads, We are
using the Monitor.Enter(this) method and it is complement
Monitor.Exit(this) in the public void GetOrderDetail(int nOrderID,out
DSOrderDetail dsOrderDetail)method . Moreover, we will use always try-catch-
finally block in the data access tier (DAT) and we perform the kernel operation in
26
the try-block, in the catch –block : we write possible errors in the Eventlogs , in the
finally-block : we clean used -resources which we used in the try – block;
)
AS
UPDATE [Order Details]
SET Quantity = @Quantity
WHERE ProductID=@ProductID AND OrderID=@OrderId
Figure 15
/// <summary>
/// Updates quantity of a product in an order deatail
/// </summary>
/// <param name=nOrderID>OrderID</param>
/// <param name=nProductID>Product</param>
/// <param name=nQuantity>Quantity</param>
/// <returns> affected Rows</returns>
public int UpdateOrderDetail(int nOrderID,int nProductID,int nQuantity)
{
int nAffected = 0;
try
{
// lock the intrinsic DataAcces utilities
Monitor.Enter(this);
// intialize DataAcces utilities for the local SqlServer
Prepair_L(“SPUpOrderDetail”);
// Add parameters to the command
27
SqlParameter pmOrderID = pmFactory_In.GetPMInt4(“@OrderID”);
pmOrderID.Value = nOrderID;
AddParameter_L(pmOrderID);
}
catch(Exception oException)
{
string strError;
strError = An Error Occured in DAOrder:UpdateOrderDetail;
this.ErrorLog(strError,oException);
}
finally
{
this.Close_L();
Monitor.Exit(this);
}
return nAffected;
}
Figure 16
The store procedure “ SPUpOrderDetail” will act in the data tier (DT) to accomplish
the update task. The int DAOrder.UpdateOrderDetail method (See Fig 16)
will receive values from business logic tier (BLT) and it will create parameters
for them , then created parameters will be added to the SqlCommand object with the
help of inherited DABais.AddParameter_L method. Afterwards, we will use the
DABais.ExcecuteNonQuery() method to update the database directly.
In the third example, we will use a SqlDataReader object to fetch data , which is
an alternative to using DataSet objects. The functionality of this example is same
as in example 1. The method public SqlDataReader
DAOrder.GetOrderDetail(int nOrderID) retrieves order details of an Order and
uses the store procedure “SpSelOrderDetail “ (Figure 13) in the data tier(DT).
28
/// <summary>
/// retrieves order details for an OrderID
/// </summary>
/// <param name=nOrderID>OrderID</param>
/// <returns>SqlDataReader</returns>
public SqlDataReader GetOrderDetail(int nOrderID)
{
SqlDataReader oReader = null;
try
{
// lock the intrinsic DataAcces utilities
Monitor.Enter(this);
// intialize DataAcces utilities for the local SqlServer
Prepair_L(“SPSelOrderDetail”);
// Add parameters to the command
SqlParameter pmOrderID = pmFactory_In.GetPMInt4(“@OrderID”);
pmOrderID.Value = nOrderID;
AddParameter_L(pmOrderID);
}
catch(Exception oException)
{
this.Close_L();
Monitor.Exit(this);
string strError;
strError = An Error Occured in DAOrder:GetOrderDetailOrders(DR);
this.ErrorLog(strError,oException);
}
return oReader;
Figure 17
Unlike DataSets, a SqlDataReader holds the database connection open and it will
retrieve data-rows one after another from the database in to the application. Afterwards,
you must close the database connection explicitly . You can use SqlDataReader,when
you need to read large amount of data quickly and there is no need for in-memory
database. I prefer to use DataSets in multi-tier applications than SqlDataReaders,
because there is always a need to calculate some business metrics in the middle tier
before the retried data visualized in the presentation layer(PL).
29
In this case, SqlDataReaders are not faster than Datasets. Let me confirm the previous
statement through a performance test.
/// <summary>
/// Retrieves order details for an OrderID
/// </summary>
/// <param name=nOrderID>OrderID</param>
/// <param name=dsOrderDetail>typed DataSet represent
OrderDetail</param>
/// <param name=dTotal>Total amount of an order</param>
public void GetOrderDetail(int nOrderID,out DSOrderDetail dsOrderDetail,
out decimal dTotal)
{
dsOrderDetail = null;
dTotal = 0;
try
{
DAOrder daOrder = new DAOrder();
daOrder.GetOrderDetail(nOrderID,out dsOrderDetail);
30
object oSum = dsOrderDetail.OrderDetails.Compute(Sum(Price),Quantity>0);
decimal dSum = Convert.ToDecimal(oSum);
dTotal = dSum*dConsumeTaxRate;
}
catch(Exception oException)
{
string strMessage = Error in BLOrderDetail:GetOrderDetail;
ErrorLog(strMessage,oException);
In the BLOrderDetail. GetOrderDetail method (Figure 18) , we will first retrieve the
typed DataSet “ DSOrderDetail” from database via data access tier, afterwards we
will use the expression “Quantity* UnitPrice” to calculate the Price column. Now,
we can determine the total of the invoice list by summing the Price column and
multiply this sum with the factor dConsumeTaxRate . We will then expose our
invoice list on the DAPrototype/DSTest.aspx page.
/// <summary>
/// Retrieves order details for an OrderID
/// </summary>
/// <param name=nOrderID>Id of an Order </param>
/// <param name=dTotal> Total amount of invoice list </param>
/// <returns> SqlDataReader </returns>
public SqlDataReader GetOrderDetail(int nOrderID,out decimal dTotal)
{
dTotal = 0;
SqlDataReader oReader = null;
try
{
DAOrder daOrder = new DAOrder();
// compute the total amount of order
oReader = daOrder.GetOrderDetail(nOrderID);
31
while(oReader.Read())
{
decimal dUnitPrice = Convert.ToDecimal(oReader[UnitPrice]);
int nQuantity = Convert.ToInt16(oReader[Quantity]);
dTotal += dUnitPrice * nQuantity;
}
dTotal*= dConsumeTaxRate;
// Retrive for the presentation layer
oReader = daOrder.GetOrderDetail(nOrderID);
}
catch(Exception oException)
{
string strMessage = Error in BLOrderDetail:GetOrderDetail(DR);
ErrorLog(strMessage,oException);
}
return oReader;
Figure 19
2.2.2
We can now start the performance test. I am using my home computer for this test
and it uses the Windows 2000 OS and has a single 800 MHz processor and 130 MB
RAM. Sql Server 2000 ,.Net Framework and Web stress tool are installed in the same
machine. We will keep the request stress level constant during the test , which will be
200 simultaneous browser connection. Web Stress Tool will send request for the
Dataset implementation(DAPrototype/DSTest.aspx.) and the SqlDataReader
implemetation (DAPrototype/DRTest.aspx.) for five minutes. Here are the results:
DataSet implemetation
Total number of requests: 42.093
Average requests per second: 140,31
Average database connection per second:31,90
SqlDataReader implemetation
Total number of requests: 35.663
Average requests per second: 118,88
Average database connection per second:173,27
Conclusion:
Dataset implementation is not only slightly faster than SqlDataReader implementation ,
but also it uses database connections efficiently. Furthermore , DataSets enable us to
implement business-logic efficiently than DataReaders.
Now, let us discuss remaining aspects in the question and answer form.
32
2.3 How can I extend this data access tier (DAT) to access multi-databases.
It is very easy to extend it. Say, you want extend DAT, so that it can work with the
database X. An extending workflow is given as follows.
a) Create protected attributes in the super class DABasis , in order to represent data
access classes from the name space System.Data.SqlClient;
b) Create methods which wraps these intrinsic data access objects. These methods can
be listed as follows
PrePair_X,Open_X,BegeinTransaction_X,ExcequteScalar_X,ExcequteNonQuery_X,
GetDataSet_X,ReuseCommand_X. In order to establish database connection, you must
add a key on the web.config file under the section <appSettings> and read and assign
it to a protected variable in the super class DABasis. Folks, that’s all, now
our data access tier is ready to work with database X.
2.4 How can I apply database transactions with different isolation level?
.The DABasis class supports database transactions with the
methods: protected void BeginTransaction_L(IsolationLevel iLevel),
protected void Rollback_L(),protected void Commit_L()(Figure 20).
/// <summary>
/// Starts a transaction to the local server
/// with isolationlevel
/// </summary>
protected void BeginTransaction_L(IsolationLevel iLevel)
33
{
try
{
// assign the OleDbtransaction to the SqlCommand
this.dbTransaction_L = dbConnection_L.BeginTransaction(iLevel);
this.dbCommand_L.Transaction = this.dbTransaction_L;
}
catch(Exception oException)
{
string strMessage = "Occured in BeginTransaction_L()";
ErrorLog(strMessage,oException);
}
}
/// <summary>
/// Rollbacks the transaction to the local server
/// </summary>
protected void Rollback_L()
{
try
{
this.dbTransaction_L.Rollback();
}
catch(Exception oException)
{
string strMessage = "Occured in RollBack_L()";
ErrorLog(strMessage,oException);
}
}
/// <summary>
/// Commits the local server
/// </summary>
protected void Commit_L()
{
try
{
this.dbTransaction_L.Commit();
}
catch(Exception oException)
{
string strMessage = "Occured in Commit_L()";
ErrorLog(strMessage,oException);
}
}
Figure 20
Let us explain , how these methods work together through an example. In this example
we going to delete an order and it’s details. Because of master-detail relationship
constraints , we must delete first the order details and then the order itself. It is very
34
efficient using a single store procedure “SPDelOrders” ( see Figure 21 and invoke it in
an ExecutenonQuery method.
/* Used in DAPrototype */
CREATE PROCEDURE SPDelOrders
(
@OrderID int
)
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
IF @@error > 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
GO
Figure 21
But in sake of demonstration, we delete an order and its order details in the data access
tier. We can achieve this goal by modifying ExecuteNonQuery pattern. We will use
store procedures “SPDelODDemo” and “SPDelOrdersDemo” to delete order details
and the order respectively(Figure 22).
/* Used in DAPrototype*/
CREATE PROCEDURE SPDelOrdersDemo
(
@OrderID int
)
AS
DELETE Orders WHERE OrderID = @OrderID
35
/// Deletes an order and its corresponding entries in order details .
/// Its is used to demonstrate transaction
/// </summary>
/// <param name=nOrderID></param>
public bool DeleteOrder(int nOrderID)
{
// At first, we will delete Order Details with the
// foreign key value nOrderID and after we
// will delete the corresponding Order. If this transaction fails,
// then we will roll back first transaction
bool bSuccess=false;
try
{
Monitor.Enter(this);
}
catch(Exception oException)
{
// Rollback the transaction
bSuccess=false;
this.Rollback_L();
string strMessage = An error occured in DAOrderDetail:DeleteOrder ;
ErrorLog(strMessage,oException);
36
}
finally
{
Close_L();
Monitor.Exit(this);
}
return bSuccess;
}
Figure 22
Monitor.Enter(this);
// Delete Order Detials with the OrderID
Prepair_L(“SPDelODDemo”);
// Add the parameters
SqlParameter pmOrderID = pmFactory_In.GetPMInt4(@OrderID);
pmOrderID.Value = nOrderID;
AddParameter_L(pmOrderID);
Step 2.
Open the database connection ,start the transaction using the
BeginTransaction_L(System.Data.IsolationLevel.Serializable) method
and execute the store procedure “ SPDelODDemo” .
// open the connection to the local server
this.Open_L();
this.BeginTransaction_L(System.Data.IsolationLevel.Serializable);
int nOD = ExecuteNonQuery_L();
// write on the WaringLog
StringBuilder strBuilder = new StringBuilder();
strBuilder.Append(nOD);
strBuilder.Append( Rows are deleted in [Order Details] with the
OrderID );
strBuilder.Append(nOrderID);
this.WarningLog(strBuilder.ToString());
Step 3
Reinitialize the SqlCommand object by clearing previous query parameters, set the
store procedure “SPDelOrdersDemo” and execcute it.
37
AddParameter_L(pmOrderID);
nOD = ExecuteNonQuery_L();
// write on the WaringLogstrBuilder = new StringBuilder();
strBuilder.Append(an order was deleted with ID );
strBuilder.Append(nOrderID);
WarningLog(strBuilder.ToString());
Step 4
If there is no error exception in the try block, then the transaction will be committed,
otherwise it rolled back in the exception block.
}
catch(Exception oException)
{
// Rollback the transaction
bSuccess=false;
this.Rollback_L();
string strMessage = An error occured in DAOrderDetail:DeleteOrder ;
ErrorLog(strMessage,oException);
}
finally
{
Close_L();
Monitor.Exit(this);
}
PId char(5)
Picture image
We can now use the store procedures “SPInImage” and “SPSelImage” to insert and
retrieve images from the database.
CREATE PROCEDURE SPInImage
(
@PId char(5),
@Picture image
)
AS
IF NOT EXISTS(SELECT * From Images WHERE PId=@PId)
BEGIN
38
INSERT INTO Images
(PId,
Picture
)
VALUES
(
@PId,
@Picture
)
END
GO
Now , we can use the methods bool InsertImage(string strPId,byte[] imPicture) and
byte[] GetImage(string strPId) to invoke these store procedures(See Figure 24)
/// <summary>
/// Inserts a picture to the table
/// </summary>
/// <param name="strPId"></param>
/// <param name="imPicture"></param>
/// <returns></returns>
public bool InsertImage(string strPId,byte[] imPicture)
{
bool bInsert = false;
try
{
// lock the intrinsic DataAccess utilities
Monitor.Enter(this);
// Initialise DataAccess utilities for the local server
this.Prepair_L("SPInImage");
39
pmPId.Value = strPId;
this.AddParameter_L(pmPId);
if(nExcequte>0)
{
bInsert = true;
}
catch(Exception oException)
{
string strError = "An Error Occured in :InsertImage";
ErrorLog(strError,oException);
}
finally
{
this.Close_L();
Monitor.Exit(this);
}
return bInsert;
/// <summary>
/// retrieves an image from the table “images”
/// </summary>
/// <param name="strPId"></param>
public byte[] GetImage(string strPId)
{
try
{
// lock the intrinsic DataAccess utilities
Monitor.Enter(this);
// Initialise DataAccess utilities for the local server
this.Prepair_L("SPSelImage");
40
pmPId.Value = strPId;
this.AddParameter_L(pmPId);
}
}
catch(Exception oException)
{
string strError = "An Error Occured in :GetImage";
ErrorLog(strError,oException);
}
finally
{
this.Close_L();
Monitor.Exit(this);
}
return imPicture;
}
Figure 24
2.6 Does this Data Access Tier supports presentation tier such like custom
paging?
Yes, of course. ASP.Net offers you sophisticated GUI controls such like DataGrids that
are able to page the retrieved records automatically, but in other hand, they are not
flexible enough. So, other alternatives are DataList or Repeaters. In this case, you are
responsible that these template controls show only the records which is needed on the
display screen . In this case, you need an access method in the DAT, which is able to
retrieve certain number of records step by step.
The void DABasis.GetDataSet_L(DataSet dsOut,int nStartRecord,int
nMaxRecord,string strTable) method (See Figure 25) enables you to retrieve fixed
amount of records page by page
41
/// <summary>
/// fills the Dataset.must be called after calling Prepair_L
/// </summary>
/// <param name="dsOut">Dataset which is to be filled</param>
/// <param name="nStartRecord">The zero-based record number to start
with.
///</param>
/// <param name="nMaxRecord">Maximum number of reocords to
retrieve</param>
/// <param name="strTable">Name of the Table</param>
protected virtual void GetDataSet_L(DataSet dsOut,int nStartRecord,int
nMaxRecord,string strTable)
{
try
{
dbAdapter_L.Fill(dsOut,nStartRecord,nMaxRecord,strTable);
}
catch(Exception oException)
{
string strMessage = "Occured in GetDataSet_L ";
strMessage += strTable+ oException.Message;
ErrorLog(strMessage);
}
}
Figure 25
Paul Abraham is a software developer who designs and develops multi-shop systems.
He has received his M.Sc in Mathematics and Computer Science from the
FernUniversität Hagen(http://www.fernuni-hagen.de Germany) and his main interests
are neural networks and bayesian statistics He lives in Rosenheim (South Germany
http://www.rosenheim.de). You can reach him at admin@paul-abraham.com
42