ADO.NET实现应用程序数据访问层
【IT168 技术文档】
最基础的面向对象的任务是使用继承的实现来创建抽象基类,该基类可以包含所有数据访问类可以通过继承使用的服务。如果这些服务足够通用,则可以通过在整个组织中分发基类来对它们进行重用。例如,在最简单的情况下,基类可以为派生类完成连接对象的创建,如下:
正如您看到的那样,DALBase类被标记为MustInherit(在C#中为abstract),以确保它用于继承关系。该类随后会包含一个在公共构造函数(它接受连接字符串作为参数)中实例化的私有SqlConnection对象。然后,受保护的Connection属性允许派生类访问该连接对象,而IDisposable接口中的Dispose方法则确保该连接对象得以被处理。即使是在下面这个简化的示例中,您也可以从中注意到抽象基类的用处:
规则2:遵守设计准则
.NET Framework包含一些可以帮助处理与基础结构相关的一般性任务(例如,检测和异常处理)的类和结构,通过基类将这些概念与继承相结合可能十分有用。例如,请考虑在System.Diagnostics命名空间中公开的跟踪功能。除了Trace和Debug类,该命名空间还包括从Switch和TraceListener派生的类。Switch类—BooleanSwitch和TraceSwitch,可以通过编程方式以及通过应用程序的配置文件而被配置为打开和关闭。就TraceSwitch而言,可以公开多个级别的跟踪。TraceListener类—TextWriterTraceListener和EventLogTraceListener将Trace和Debug方法的输出分别定向到文本文件和事件日志。因此,您可以将跟踪功能添加到基类中,以便派生类可以轻松地记录消息。继而,应用程序可以使用应用程序配置文件来控制是否启用跟踪。您可以通过包含一个BooleanSwitch类型的私有变量并在构造函数中将其实例化,以将该功能添加到DALBase类中:
BooleanSwitch的参数包括它的名称和说明。您随后可以添加一个受保护属性,以便将开关打开和关闭,并添加另一个受保护属性,以便使用Trace对象的WriteLineIf方法来格式化和写入跟踪消息:Public Sub New(ByVal connect As String) _connection = New SqlConnection(connect) _dalSwitch = New BooleanSwitch("DAL", "Data Access Code") End Sub
这样,派生类无须自己了解开关类和侦听器类,就可以在数据访问类中发生重大事件时轻松地调用WriteTrace方法。Protected Property TracingEnabled() As Boolean Get Return _dalSwitch.Enabled End Get Set(ByVal Value As Boolean) _dalSwitch.Enabled = Value End Set End Property Protected Sub WriteTrace(ByVal message As String) Trace.WriteLineIf(Me.TracingEnabled, Now & ": " & message) End Sub
<?XML version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <switches> <add name="DAL" value="1" /> </switches> <trace autoflush="true" indentsize="4"> <listeners> <add name="myListener" type="System.Diagnostics. TextWriterTraceListener" initializeData="DALLog.txt"/> </listeners> </trace> </system.diagnostics> </configuration>
毫无疑问,您应该利用的另一个基础结构是结构化异常处理(SEH)。在最基本的级别,DAL可以公开它自己从System.ApplicationException继承的Exception对象,并且还可以公开自定义成员。例如,下面所示的DALException对象可以用来包装由数据访问类中的代码引发的异常。然后,基类可以公开一个受保护的方法以包装异常、填充自定义成员并将其传回调用方,如下所示:Public Class DALException : Inherits ApplicationException Public Sub New() MyBase.New() End Sub Public Sub New(ByVal message As String) MyBase.New(message) End Sub Public Sub New(ByVal message As String,_ ByVal innerException As Exception) MyBase.New(message, innerException) End Sub ' Add custom members here Public ConnectString As String End Class
这样,派生类可以轻松地调用受保护的方法,传入所截获的特定于数据的异常(通常为SqlException或OleDbException),并添加一个与特定数据域有关的消息。基类在DALException中包装该异常,并将其传回调用方。这使得调用方可以使用单个Catch语句轻松地捕获来自DAL的所有异常。该框架通过一组对象将异常的发布与应用程序日志耦合在一起。实际上,您可以通过从.NET Framework提供的BaseApplicationException类继承自己的自定义异常类,来将它们插入到该框架中。Protected Sub ThrowDALException(ByVal message As String, _ ByVal innerException As Exception) Dim newMine As New DALException(message, innerException) newMine.ConnectString = Me.Connection.ConnectionString Me.WriteTrace(message & "{" & innerException.Message & "}") Throw newMine End Sub
规则4:仔细选择外部接口
最后,您还可以决定用公共属性返回自定义类。这些类可以用Serialization属性标记,以便能够跨应用程序域进行复制。另外,如果您要从方法中返回多个对象,则可能需要强类型集合类。
Imports System.Xml.Serialization
![]()
<Serializable()> _
Public Class BookClass Book : Implements IComparable
![]()
<XmlAttributeAttribute()> Public ProductID As Integer
Public ISBN As String
Public Title As String
Public Author As String
Public UnitCost As Decimal
Public Description As String
Public PubDate As Date
![]()
Public Function CompareTo()Function CompareTo(ByVal o As Object) As Integer _
Implements IComparable.CompareTo
![]()
Dim b As Book = CType(o, Book)
Return Me.Title.CompareTo(b.Title)
End Function
![]()
End Class
![]()
Public NotInheritable Class BookCollectionClass BookCollection : Inherits ArrayList
![]()
Default Public Shadows Property Item_()Property Item_
(ByVal productId As Integer)As Book
![]()
Get
Return Me(IndexOf(productId))
End Get
Set(ByVal Value As Book)
Me(IndexOf(productId)) = Value
End Set
End Property
![]()
Public Overloads Function Contains()Function Contains(ByVal_
productId As Integer) As Boolean
Return (-1 <> IndexOf(productId))
End Function
![]()
Public Overloads Function IndexOf()Function IndexOf(ByVal_
productId As Integer) As Integer
Dim index As Integer = 0
Dim item As Book
![]()
For Each item In Me
If item.ProductID = productId Then
Return index
End If
index = index + 1
Next
Return -1
End Function
![]()
Public Overloads Sub RemoveAt()Sub RemoveAt(ByVal productId As Integer)
RemoveAt(IndexOf(productId))
End Sub
![]()
Public Shadows Function Add()Function Add(ByVal value As Book) As Integer
Return MyBase.Add(value)
End Function
![]()
End Class
规则5:抽象化.NET Framework data provider
最后一个规则指定,为什么应该对在DAL内部使用的.NET Framework data provider抽象化,以及应该如何进行抽象。正如我已经提到的那样,ADO.NET编程模型公开了独特的.NET Framework data provider,包括SqlClient、OleDb和其他可从 MSDN 在线Web站点上获得的data provider。尽管该设计能够提高性能,并且使provider能够公开特定于数据源的功能(例如SqlCommand对象的ExecuteXMLReader方法),但它会迫使开发人员决定针对哪个provider进行编码。换句话说,开发人员通常选择使用SqlClient或OleDb,然后直接针对各个命名空间中的类编写代码。
如果您要更改.NET Framework data provider,则需要重新编写数据访问方法的代码。为了避免这种情况,可以使用称为“抽象工厂(Abstrace Factory)”的设计模式。使用该模式,可以生成一个简单的类,该类将公开能够基于标识传入到构造函数的.NET Framework data provider的信息来创建主要的.NET Framework data provider对象(命令、连接、数据适配器和参数)的方法。图7中的代码显示了该类的一个简化的C#版本。为了使用该类,数据访问类中的代码需要针对.NET Framework data provider实现的各种接口(包括IDbCommand、IDbConnection、IDataAdapter和IDataParameter)进行编程。例如,为了用来自参数化存储过程的结果填充DataSet,您可以在数据访问类的方法内部使用以下代码:public enum ProviderType : int {SqlClient = 0, OLEDB = 1} public class ProviderFactory { public ProviderFactory(ProviderType provider) { _pType = provider; _initClass(); } public ProviderFactory() { _initClass(); } private ProviderType _pType = ProviderType.SqlClient; private bool _pTypeSet = false; private Type[] _conType, _comType, _parmType, _daType; private void _initClass() { _conType = new Type[2]; _comType = new Type[2]; _parmType = new Type[2]; _daType = new Type[2]; // Initialize the types for the providers _conType[(int)ProviderType.SqlClient] = typeof(SqlConnection); _conType[(int)ProviderType.OLEDB] = typeof(OleDbConnection); _comType[(int)ProviderType.SqlClient] = typeof(SqlCommand); _comType[(int)ProviderType.OLEDB] = typeof(OleDbCommand); _parmType[(int)ProviderType.SqlClient] = typeof(SqlParameter); _parmType[(int)ProviderType.OLEDB] = typeof(OleDbParameter); _daType[(int)ProviderType.SqlClient] = typeof(SqlDataAdapter); _daType[(int)ProviderType.OLEDB] = typeof(OleDbDataAdapter); } public ProviderType Provider { get { return _pType; } set { if (_pTypeSet) { throw new ReadOnlyException_ ("Provider already set to " + _pType.ToString()); } else { _pType = value; _pTypeSet = true; } } } public IDataAdapter CreateDataAdapter_ (string commandText,IDbConnection connection) { IDataAdapter d; IDbDataAdapter da; d = (IDataAdapter)Activator.CreateInstance_ (_daType[(int)_pType], false); da = (IDbDataAdapter)d; da.SelectCommand = this.CreateCommand(commandText, connection); return d; } public IDataParameter CreateParameter_ (string paramName, DbType paramType) { IDataParameter p; p = (IDataParameter)Activator.CreateInstance_ (_parmType[(int)_pType], false); p.ParameterName = paramName; p.DbType = paramType; return p; } public IDataParameter CreateParameter_ (string paramName, DbType paramType, Object value) { IDataParameter p; p = (IDataParameter)Activator.CreateInstance_ (_parmType[(int)_pType], false); p.ParameterName = paramName; p.DbType = paramType; p.Value = value; return p; } public IDbConnection CreateConnection(string connect) { IDbConnection c; c = (IDbConnection)Activator.CreateInstance_ (_conType[(int)_pType], false); c.ConnectionString = connect; return c; } public IDbCommand CreateCommand(string cmdText,_ IDbConnection connection) { IDbCommand c; c = (IDbCommand)Activator.CreateInstance_ (_comType[(int)_pType], false); c.CommandText = cmdText; c.Connection = connection; return c; } }
Dim _pf As New ProviderFactory(ProviderType.SqlClient) Dim cn As IDbConnection = _pf.CreateConnection(_connect) Dim da As IDataAdapter = _pf.CreateDataAdapter("usp_GetBook", cn) Dim db As IDbDataAdapter = CType(da, IDbDataAdapter) db.SelectCommand.CommandType = CommandType.StoredProcedure db.SelectCommand.Parameters.Add(_pf.CreateParameter("@productId", _ DbType.Int32, id)) Dim ds As New DataSet("Books") da.Fill(ds)
