Download Abstract Factory DAAB

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Entity–attribute–value model wikipedia , lookup

Extensible Storage Engine wikipedia , lookup

Microsoft Jet Database Engine wikipedia , lookup

SQL wikipedia , lookup

Microsoft SQL Server wikipedia , lookup

Clusterpoint wikipedia , lookup

Relational model wikipedia , lookup

Database model wikipedia , lookup

Open Database Connectivity wikipedia , lookup

PL/SQL wikipedia , lookup

Transcript
Abstract Factory DAAB
Versions of this Document
3.0
Author
Diego Gonzalez (Lagash Systems).
3.1
Chris J. Breisch (Quest Information
Systems, Inc.)
Introduction
The idea behind this version of the DAAB is to provide an easy support to make the
data access independent of the ADO.NET provider. The DAAB is a great tool that
allows very easy access to the Sql Server ADO.NET driver using single line methods
for example ExecuteDataset, ExecuteReader, etc.
The problem behind this implementation is that every method was implemented as a
static method of a single class named SqlHelper. This class uses the SqlServer
ADO.NET provider only and it's not easy to extend this class using OOP techniques,
the only way to extend this class in order to use a different ADO.NET driver is
rewriting the code whole.
In ADO.NET some interfaces are provided to create the ADO.NET providers. Those
interfaces are: IDbConnection, IDbTransaction, IDbCommand, IDataParameter,
IDataReader, IDataRecord, etc. Every ADO.NET provider must implement those
interfaces on its implementation classes, so there's an open door to write code that
doesn't knows which ADO.NET provider it is using if the code is using interfaces
instead of concrete classes. The ADO.NET interfaces define a common base of
functionality supported by almost any database. Some drivers may define new
methods that aren't defined on the interface. For example, the SQL Server managed
provider defines a method named ExecuteXmlReader that uses a SQL Server 2000
feature to return XML data from the database.
To get data from a table using ADO.NET without the DAAB, your code might look
something like this:
using( SqlConnection conn = new SqlConnection( /* conn string */ ))
{
conn.Open();
using( SqlCommand cmd = conn.CreateCommand() )
{
cmd.CommandText = “SELECT * FROM titles;”;
using( SqlDataReader reader = cmd.ExecuteReader() )
{
while( reader.Read() )
{
// process the result set.
}
}
}
}
The following code demonstrates how to create code that uses only the ADO.NET
interfaces to access the database:
using( IDbConnection conn = GetConnection( /* ... */ ) )
{
conn.Open();
using( IDbCommand cmd = conn.CreateCommand() )
{
cmd.CommandText = “SELECT * FROM titles;”;
using( IDataReader reader = cmd.ExecuteReader() )
{
while( reader.Read() )
{
// process the result set.
}
}
}
}
In the previous code the GetConnection method is supposed to return any instance
of the IDbConnection interface. The following code uses the interface methods to
interact with the database.
In this version of the DAAB the main helper class is named AdoHelper and it defines
the same methods that were defined on SqlHelper, but using the ADO.NET interfaces
instead using the SqlServer implementations. For example, the method
ExecuteDataset is implemented using only ADO.NET interfaces so it can be executed
using any ADO.NET provider implementation.
Goals and Non-Goals
Goals


Complete provide a complete interface support for ADO.NET. Some
important classes on ADO.NET do not define common interfaces or there’s
no way to get an instance of the following classes: XxxCommandBuilder,
XxxDataAdapter.
Use the same base code on AdoHelper on any ADO.NET provider
supporting the common interfaces.
Out of Scope

Provide a single SQL language for every provider. Some ADO.NET
providers use different SQL syntax, if an application wants to use any
database it should use any provider, AND the same SQL syntax for all of
them. It’s not a goal for this implementation to solve this problem. The
application must use ANSI SQL when possible, and should be aware for
some differences between providers.
Using the Abstract DAAB
The AdoHelper is an abstract class, so it can’t be instantiated, this class can only be
used by derived classes. For example, the classes named SqlServer, Odbc, OleDb,
and Oracle extend AdoHelper.
The AdoHelper class defines some static methods that can be used to create
AdoHelper derived classes, using the Abstract Factory design pattern. The method
used to create an instance of the AdoHelper derived class is CreateHelper, this
method is defined with two overloads:
 Using the assembly and type name of a DAAB provider. The overload
receiving two string parameters uses the assembly name and the type name
to load the type and creating an instance using reflection.

Using a configuration section. This overload receives a single string with the
name of an “alias”. The aliases are defined on the default configuration file for
the application domain. Usually the file is named using the EXE file name with
a “.config” extension, for example “Notepad.exe.config”. Please refer to the
“Configuring Applications” section on the Framework SDK documentation.
If you chose the configuration section overload you must have a .config file with your
application with the following structure:
<configuration>
<configSections>
<section
name="daabProviders"
type="GotDotNet.ApplicationBlocks.Data.DAABSectionHandler,
GotDotNet.ApplicationBlocks.Data">
</section>
</configSections>
<daabProviders>
<daabProvider
alias="accounts"
assembly="GotDotNet.ApplicationBlocks.Data"
type="GotDotNet.ApplicationBlocks.Data.SqlServer" />
<daabProvider
alias="credits"
assembly="GotDotNet.ApplicationBlocks.Data"
type="GotDotNet.ApplicationBlocks.Data.OleDb" />
</daabProviders>
</configuration>
You must have a <configSections/> defined exactly as above, although you can
choose whatever name you wish. The name “daabProviders” was chosen because it
will hold the “daabProvider” elements.
This configuration file defines two different providers using the aliases “accounts”
and “credits”. The “accounts” database uses the SqlServer DAAB provider and the
“credits” uses the OleDb provider. The CreateHelper overload with a single string
parameter uses the configuration file to get the assembly and type name for the
alias provided as a parameter and creates an instance using reflection.
The following code demonstrates how to use the CreateHelper method overloads:
using GotDotNet.ApplicationBlocks.Data;
// ...
AdoHelper helper = AdoHelper.CreateHelper(
“GotDotNet.ApplicationBlocks.Data”,
“GotDotNet.ApplicationBlocks.Data.SqlServer” );
AdoHelper otherHelper = AdoHelper.CreateHelper( “credits” );
Using the Online Help
This version of the DAAB comes with online documentation that can be integrated
with Visual Studio 2003 (integration with Visual Studio 2005 does not work at this
time). The on-line help is not installed integrated into Visual Studio by default.
There is a shortcut labeled ‘Install On-line Help’ to do this located in the Start Menu
folder for the DAAB, usually ‘Start/All Programs/GotDotNet/Application Blocks/Data
3.1’. An uninstall shortcut has been provided in this same folder, should you later
decide you prefer not to have the help integrated.
Also, the help may be access directly through a help file. The shortcut to this is in
the same folder and labeled ‘Documentation’.
Note: The on-line help is automatically removed from the Visual Studio help upon
uninstallation of the DAAB.
Creating a provider for the Abstract DAAB
To create an Abstract DAAB helper a class must be created extending the AdoHelper
abstract class. Any AdoHelper derived class must implement the following methods:
Method
Description
IDbConnection GetConnection(
string connectionString )
This method must return an instance of an
IDbConnection implementation for the
ADO.NET provider.
IDbDataAdapter GetDataAdapter()
Must return an instance of a class
implementing IDbDataParameter for the
ADO.NET provider.
void DeriveParameters(
IDbCommand cmd )
This method must call the DerivedParameters
on the XxxCommandBuilder for the ADO.NET
provider specifying the “cmd” parameter. If
the ADO.NET provider does not provide such
implementation, the AdoHelper derived class
must implement similar functionality. Check
the ADO.NET documentation on the
Framework SDK.
IDataParameter GetParameter()
Must return an IDataParameter derived class
which is not connected to any connection.
XmlReader ExecuteXmlReader(
IDbCommand cmd )
Must return an XmlReader containing the
resultset generated by the command. One
way to do this might be to fill a data set and
then use DataSet.GetXml to return an
XmlReader object. See the implementations
in the provided helpers for examples.
Provider specific code to set up the
updating/ed event handlers used by
UpdateDataset. This code is fairly generic
void AddUpdateEventHandlers
(IDbDataAdapter dataAdapter,
RowUpdatingHandler
rowUpdatingHandler,
RowUpdatedHandler
rowUpdatedHandler)
and can be copied nearly verbatim from any
of the existing helpers.
IDataParameter[]
GetDataParameters(int size)
Must return an array of IDataParameters of
the specified size.
IDataParameter
GetBlobParameter(IDbConnection
connection, IDataParameter p)
Must return an IDataParameter with BLOB
data. Generally this is just a pass-thru
method. See the SqlServer.[cs, vb] file for
details on the pass-thru, and Oracle.[cs, vb]
for an example of this method that actually
does something.
In addition, the helper may override any or all of the following methods. The base
class implementations are generally all that’s required, however special cases may
exist that need to be handled in the derived class.
IDataParameter GetParameter( string
name, object value )
Return an IDataParameter with
ParameterName = name and Value =
value
IDataParameter GetParameter (
string name, DbType dbType, int
size, ParameterDirection direction
)
Return an IDataParameter with
ParameterName = name, DbType =
type, Size = size, and
ParameterDirection = direction
IDataParameter GetParameter (
string name, DbType dbType, int
size, string sourceColumn,
DataRowVersion sourceVersion )
Return an IDataParameter with
ParameterName = name, DbType =
type, Size = size, SourceColumn =
sourceColumn, SourceVersion =
sourceVersion.
void AttachParameters(IDbCommand
command, IDataParameter[]
commandParameters)
Essentially takes the array of
IDataParameters and attaches them to
the IDbCommand object via it’s
Parameters collection. Typically this
might get overridden if null values need
to be handled in some special way.
void
CleanParameterSyntax(IDbCommand
command)
This cleans up the parameter syntax for
the call. ODBC in particular takes a
much different syntax than the other
ADO.NET providers. This is a way to
have a more “generalized” SQL code
across providers. This method is not
designed to be perfect and handle all
issues. See the “Out of Scope” section
above.
void PrepareCommand(IDbCommand
command, IDbConnection connection,
IDbTransaction transaction,
CommandType commandType, string
This method sets up everything for the
IDbCommand. It associates it with any
existing transactions, opens a connection
commandText, IDataParameter[]
commandParameters, out bool
mustCloseConnection )
if necessary, sets up the command text
and any parameters, and returns a bool
indicating whether it opened a
connection or not.
void ClearCommand(IDbCommand
command )
This method clears the IDbCommand
objects parameter collection. It’s use is
considered deprecated due to too many
inconsistencies between providers and
situations.
All of the Execute…,Fill…,Update…
methods
Generally the only methods that might
need to be overridden in a helper
subclass are the ones that take only a
command object, such as
ExecuteXmlReader(IDbCommand cmd).
All of the other methods for a given block
eventually call this method.
IDbCommand CreateCommand(…)
Must return an IDbCommand object. The
base class uses
Connection.CreateCommand(). This is
usually sufficient.
IDataParameter[]
GetSpParameterSet(…)
Must return an IDataParameter array
containing parameter information for the
stored procedure, spName. If the given
database does not have stored
procedures, obviously this method is not
necessary. First, this method looks in
the ParameterCache for the information.
If not found, the base class method uses
the
XxxCommandBuilder.DeriveParameters
method from the appropriate provider.
(note: the SQL Server implementation,
SqlCommandBuilder.DeriveParameters is
buggy, so the SqlServer helper provides
its own implementation of
DeriveParameters). This method also
stores the retrieved information in the
ParameterCache for later use.
IDataParameter[]
GetCachedParameterSet(string
connectionString, string
commandText)
Must return an IDataParameter array
containing parameter information for the
stored procedure specified in
commandText. This method uses the
ParameterCache to quickly retrieve the
parameter information.
Every method defined on the AdoHelper base class is defined virtual, it means any
method could be overridden on the derived class if the use of the interfaces within
the AdoHelper class is not supported by the interfaces implementation on the
ADO.NET provider.
Only a summary of
methods is shown...
AdoHelper
+ GetConnection ( )
# GetDataAdapter ( )
# DeriveParameters ( )
+ GetParameter ( )
+ CreateHelper ( )
+ CreateHelper ( )
+ GetParameter ( )
# AttachParameters ( )
# AssignParameterValues ( )
# AssignParameterValues ( )
# PrepareCommand ( )
# ClearCommand ( )
+ ExecuteDataset ( )
+ ExecuteDataset ( )
+ ExecuteDataset ...
( )
ADOHelperParameterCache
~ CloneParameters ( )
+ CacheParameterSet ( )
+ GetCachedParameterSet ( )
DAABSectionHandler
+ Create ( )
ProviderAlias
+ «property» AssemblyName : string
+ «property» TypeName : string
- _assemblyName : string
- _typeName : string
+ ProviderAlias ( )
+ «get» AssemblyName ( )
+ «get» TypeName ( )
Odbc
SqlServer
OleDb
+ Odbc ( )
+ GetConnection ( )
# GetDataAdapter ( )
# DeriveParameters ( )
+ GetParameter ( )
# PrepareCommand ( )
+ SqlServer ( )
+ GetConnection ( )
# GetDataAdapter ( )
# DeriveParameters ( )
+ GetParameter ( )
# ClearCommand ( )
+ ExecuteXmlReader ( )
+ ExecuteXmlReader ( )
+ ExecuteXmlReader ( )
+ ExecuteXmlReader ( )
+ ExecuteXmlReader ( )
+ ExecuteXmlReader ( )
+ ExecuteXmlReaderTypedParams ( )
+ ExecuteXmlReaderTypedParams ( )
+ OleDb ( )
+ GetConnection ( )
# GetDataAdapter ( )
# DeriveParameters ( )
+ GetParameter ( )
Figure 1 – Class diagram
Note: the above diagram is out of date.
The PetShop DAL vs Abstract DAAB
The PetShop sample application available on MSDN is a 3-tier application, it means
the application is divided in Presentation Layer (PL), Business Logic Layer (BLL) and
Data Access Layer (DAL). The PetShop application implements a Data Access Layer
that can be used with Oracle and SQL Server ADO.NET providers. The PetShop
developers decide to implement the DAL using interfaces; those interfaces are used
by the BLL to access the database to get and update the information.
There’s an interface for each entity on the system with the methods needed by the
BLL.:
Interface
Methods
IAccount
SignIn, GetAddress, Insert, Update
IInventory
CurrentQtyInStock, TakeStock
IItem
GetItemsByProduct, GetItem
IOrder
Insert, GetOrder
IProduct
GetProductsByCategory, GetProductsBySearch
IProfile
GetBannerPath
There are two implementations of each interface, for each supported ADO.NET
driver. The BLL uses a factory class to get an instance for the DAL which is
configured on the .config file. Each implementation uses driver specific helper classes
to perform the operations defined by the interface.
The helper classes defined OraHelper and SqlHelper define a subset of the operations
available on DAAB, the OraHelper was completely rewritten using driver specific
classes for each ADO.NET provider.
So the DAL is divided on two classes: The interface implementation for each entity
and the XxxHelper and the provider specific helpers named OraHelper and SqlHelper.
Comparing the OraHelper and the SqlHelper code there are no many differences
between them, the Abstract DAAB could be used here in fact (if an OracleHelper is
developed). So the entity specific data access code, only provides the correct SQL
statements and parameter values.
The reason to rewrite the whole data access code for each entity and each ADO.NET
providers is because some issues can’t be performed with generic code, for example:

SQL language. There are many differences on the SQL statements supported
by each ADO.NET provider.

Parameter markers. Using the SqlCommand class parameters can be placed
on the CommandText using a “@” preceding the parameter name, and the
Parameters collection can be filled with the parameter names and values. The
SqlCommand class then executes the SQL statement using the correct data
types. The same feature is available in Oracle using “:”
AdoHelper Members
Public Static (Shared) Methods
CreateHelper
Overloaded. Create an AdoHelper for
working with a specific provider (i.e. Sql,
Odbc, OleDb, Oracle)
Public Instance Methods
CacheParameterSet
CleanParameterSyntax
CreateCommand
DeriveParameters
Overloaded. Add parameter array to the
cache
This method cleans up the parameter
syntax for the provider
Overloaded. Simplify the creation of a
IDbCommand object by allowing a stored
procedure and optional parameters to be
provided
Calls the
CommandBuilder.DeriveParameters
method for the specified provider, doing
Equals (inherited from Object)
ExecuteDataset
ExecuteDatasetTypedParams
ExecuteNonQuery
ExecuteNonQueryTypedParams
ExecuteReader
any setup and cleanup necessary
Determines whether the specified Object
is equal to the current Object.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a
resultset) against the specified
IDbTransaction using the provided
parameter values. This method will query
the database to discover the parameters
for the stored procedure (the first time
each stored procedure is called), and
assign the values based on parameter
order.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a
resultset) against the specified
IDbTransaction using the dataRow
column values as the stored procedure's
parameters values. This method will
query the database to discover the
parameters for the stored procedure (the
first time each stored procedure is
called), and assign the values based on
row values.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns no
resultset) against the specified
IDbTransaction using the provided
parameter values. This method will query
the database to discover the parameters
for the stored procedure (the first time
each stored procedure is called), and
assign the values based on parameter
order.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns no
resultset) against the specified
IDbTransaction using the dataRow
column values as the stored procedure's
parameters values. This method will
query the database to discover the
parameters for the stored procedure (the
first time each stored procedure is
called), and assign the values based on
row values.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a
resultset) against the specified
IDbTransaction using the provided
parameter values. This method will query
the database to discover the parameters
for the stored procedure (the first time
ExecuteReaderTypedParams
ExecuteScalar
ExecuteScalarTypedParams
ExecuteXmlReader
ExecuteXmlReaderTypedParams
FillDataset
each stored procedure is called), and
assign the values based on parameter
order.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a
resultset) against the specified
IDbTransaction using the dataRow
column values as the stored procedure's
parameters values. This method will
query the database to discover the
parameters for the stored procedure (the
first time each stored procedure is
called), and assign the values based on
parameter order.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a 1x1
resultset) against the specified
IDbTransaction using the provided
parameter values. This method will query
the database to discover the parameters
for the stored procedure (the first time
each stored procedure is called), and
assign the values based on parameter
order.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a 1x1
resultset) against the specified
IDbTransaction using the dataRow
column values as the stored procedure's
parameters values. This method will
query the database to discover the
parameters for the stored procedure (the
first time each stored procedure is
called), and assign the values based on
parameter order.
Overloaded. Execute an IDbCommand
(that returns a resultset) against the
provided IDbConnection.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a
resultset) against the specified
IDbConnection using the dataRow
column values as the stored procedure's
parameters values. This method will
assign the parameter values based on
parameter order.
Overloaded. Execute a stored procedure
via a IDbCommand (that returns a
resultset) against the specified
IDbTransaction using the provided
parameter values. This method will query
the database to discover the parameters
GetCachedParameterSet
GetConnection
GetDataAdapter
GetParameter
GetSpParameterSet
UpdateDataset
for the stored procedure (the first time
each stored procedure is called), and
assign the values based on parameter
order.
Overloaded. Retrieve a parameter array
from the cache
Returns an IDbConnection object for the
given connection string
Returns an IDbDataAdapter object
Overloaded. Get an IDataParameter for
use in a SQL command
Overloaded. Retrieves the set of
IDataParameterParameters appropriate
for the stored procedure
Overloaded. Executes the respective
command for each inserted, updated, or
deleted row in the DataSet.
Protected Instance Constructors
AdoHelper Constructor
Initializes a new instance of the
AdoHelper class.
Protected Instance Fields
m_rowUpdated
m_rowUpdating
Internal handler used for bubbling up the
event to the user
Internal handler used for bubbling up the
event to the user
Protected Instance Methods
AddUpdateEventHandlers
AssignParameterValues
AttachParameters
ClearCommand
Provider specific code to set up the
updating/ed event handlers used by
UpdateDataset
Overloaded. This method assigns
dataRow column values to an
IDataParameterCollection
This method is used to attach array of
IDataParameters to a IDbCommand. This
method will assign a value of DbNull to
any parameter with a direction of
InputOutput and a value of null. This
behavior will prevent default values from
being used, but this will be the less
common case than an intended pure
output parameter (derived as
InputOutput) where the user provided no
input value.
This method clears (if necessary) the
connection, transaction, command type
and parameters from the provided
command
ExecuteReader
GetBlobParameter
GetDataParameters
PrepareCommand
RowUpdated
RowUpdating
SetCommand
Overloaded. Execute a IDbCommand
(that returns a resultset) against the
database specified in the connection
string.
Handle any provider-specific issues with
BLOBs here by "washing" the
IDataParameter and returning a new one
that is set up appropriately for the
provider.
Returns an array of IDataParameters of
the specified size
This method opens (if necessary) and
assigns a connection, transaction,
command type and parameters to the
provided command
This method consumes the
RowUpdatedEvent and passes it on to the
consumer specifed in the call to
UpdateDataset
This method consumes the
RowUpdatingEvent and passes it on to
the consumer specifed in the call to
UpdateDataset
Set up a command for updating a
dataset.
Diego González is an MVP for .NET/C# and a co-founder of Lagash, a high-tech company based in
Buenos Aires, Argentina. Diego was recently working close to PAG (msdn.microsoft.com/practices) in the
development of the Application Blocks as an Architect and Lead Developer. He’s an active member in the
.NET community and he’s a common speaker in Microsoft events in Argentina, you can get in touch with
Diego at [email protected].
Chris J. Breisch is an MCSD and MCDBA and works for Quest Information Systems, Inc., a consulting
company based in Indianapolis, Indiana, USA. You can get to know more about him and his thoughts on
.NET by reading his blog at http://geekswithblogs.net/cbreisch