知识大全 设计.NET应用程序数据访问层五大原则

Posted

篇首语:家资是何物,积帙列梁梠。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 设计.NET应用程序数据访问层五大原则相关的知识,希望对你有一定的参考价值。

设计.NET应用程序数据访问层五大原则  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  摘要 大多数使用 NET框架组件工作的开发人员的一个核心工作是实现数据访问功能 他们建立的数据访问层(data access layer)是应用程序的精华部分 本文概述了使用Visual Studio NET和 NET框架组件建立数据访问层需要考虑的五个想法 这些技巧包括通过使用基类(base class)利用面相对象技术和 NET框架组件基础结构 使类容易继承 在决定显示方法和外部界面前仔细地检验需求

  如果你正在建立以数据为中心(data centric)的 NET框架组件应用程序 你最终必须建立数据访问层 也许你知道在 NET框架组件中建立自己的代码有很多好处 因为它支持实现和接口(interface)继承 你的代码更容易重复使用 特别是被使用不同的框架组件兼容(Framework pliant)语言的开发人员使用 本文我将概述为基于 NET框架组件的应用程序建立数据访问层的五条规则

  开始前 我必须提醒你建立的任何基于本文讨论的规则的数据访问层必须与传统Windows平台上开发人员喜欢的多层或者n层应用程序兼容 在这种结构中 表现层包含Web窗体 Windows窗体 调用与数据访问层的工作相应的事务层的XML服务代码 该层由多个数据访问类(data access classe)组成 换句话说 在事务处理协调不是必要的情况下 表现层将直接调用数据访问层 这种结构是传统的模型 视列表 控制程序(Model View Controller MVC)模式的变体 在多种情况下被Visual Studio NET和它暴露的控件采用

  规则 使用面向对象特性

  最基本的面向对象事务是建立一个使用实现继承的抽象类 这个基类可以包括你的所有数据访问类通过继承能够使用的服务 如果那些服务足够了 它们就能通过在整个组织的基类分布实现重复使用 例如最简单的情况是基类能够为衍生类处理连接的建立过程 如列表 所示

  Imports System Data SqlClient

  Namespace ACME DataPublic MustInherit Class DALBase : Implements IDisposablePrivate _connection As SqlConnection

  Protected Sub New(ByVal connect As String)_connection = New SqlConnection(connect)End Sub

  Protected ReadOnly Property Connection() As SqlConnectionGetReturn _connectionEnd GetEnd Property

  Public Sub Dispose() Implements IDisposable Dispose_connection Dispose()End Sub

  End ClassEnd Namespace

  列表 简单基类

  在列表中可以看到 对DALBase类作了MustInherit标记(C#中的抽象) 以确保它在继承关系中使用 接着该类在公共构造函数中包括了一个实例化的私有SqlConnection对象 它接收连接字符串作为一个参数 当来自IDisposable接口的Dispose方法确保连接对象已经被配置了的时候 受保护的(protected)Connection属性允许衍生类访问该连接对象

  即使在下面简化的例子中你也能开始看到抽象基类的用处

  Public Class WebData : Inherits DALBasePublic Sub New()MyBase New(ConfigurationSettings AppSettings( ConnectString ))End Sub

  Public Function GetOrders() As DataSetDim da As New SqlDataAdapter( usp_GetOrders Me Connection)da SelectCommand CommandType = CommandType StoredProcedureDim ds As New DataSet()

  da Fill(ds)Return dsEnd FunctionEnd Class

  在这种情况下 WebData类继承自DALBase 结果就是不必担心实例化SqlConnection对象 而是通过MyBase关键字(或者C#中的基关键字)简单地把连接字符串传递给基类 WebData类的GetOrders方法能使用Me Connection(在C#中是this Connection)访问受保护的属性 虽然这个例子相对简单 但是你将在规则 和 中看到基类也提供了其它的服务

  当数据访问层必须在+环境中运行时抽象的基类很有用 在这种情况下 因为允许组件使用+的必要代码复杂得多 所以更好的方式是建立一个如列表 所示的服务组件(serviced ponent)基类

  Transaction(TransactionOption Supported) _EventTrackingEnabled(True)> _Public MustInherit Class DALServicedBase : Inherits ServicedComponent

  Private _connection As SqlConnection

  Protected Overrides Sub Construct(ByVal s As String)_connection = New SqlConnection(s)End Sub

  Protected ReadOnly Property Connection() As SqlConnectionGetReturn _connectionEnd GetEnd PropertyEnd Class

  列表 服务组件基类

  在这段代码中 DALServicedBase类包含的基本功能与列表 中的相同 但是加上了从System EnterpriseServices名字空间的ServicedComponent的继承 并且包括了一些属性 指明组件支持对象构造 事务和静态跟踪 接着该基类仔细地捕捉组件服务管理器(Component Services Manager)中的构造字符串并且再次建立和暴露SqlConnection对象 我们要注意的是当一个类继承自DALServicedBase时 它也继承了属性的设置 换句话说 一个衍生类的事务选项也设置为Supported 如果衍生类想重载这种行为 它能在类的层次重新定义该属性

  此外 衍生类在适当情况下应该有利于自身重载和共享方法 使用重载的方法(一个方法有多个调用信号)在本质上有两种情况 首先 它们在一个方法需要接受多种类型的参数时使用 框架组件中的典型例子是System Convert类的方法 例如ToString方法包含 个接受一个参数的重载方法 每个重载方法的类型不同 其次 重载的方法用于暴露参数数量不断增长的信号 而不是不同类型的必要参数 在数据访问层中这类重载变得效率很高 因为它能用于为数据检索和修改暴露交替的信号 例如GetOrders方法可以重载 这样一个信号不接受参数并返回所有订单 但是附加的信号接受参数以表明调用程序希望检索特定的顾客订单 代码如下

  Public Overloads Function GetOrders() As DataSetPublic Overloads Function GetOrders(ByVal customerId As Integer) As DataSet

  这种情况下的一个好的实现技巧是抽象GetOrders方法的功能到一个能被每个重载信号调用的私有的或者受保护的方法中

  共享方法(C#中的静态方法)也能用于暴露数据访问类的所有实例能够访问的字段 属性和方法 尽管共享成员不能与使用组件服务(Component Services)的类一起使用 但是对于在数据访问类的共享构造函数中检索并被所有实例读取的只读数据是有用的 使用共享成员读/写数据时要小心 因为为了访问该共享数据 执行的多个线程可能会竞争

  规则 坚持设计指导

  随Visual Studio NET一起发布的在线文档中有一个叫 类库开发人员的设计指导(Design Guidelines for Class Library Developers) 的主题 它覆蓋了类 属性和方法的名字转换 是重载的成员 构造函数和事件的补充模式 你必须遵循名字转换的主要原因之一是 NET框架组件提供的跨语言(cross language)继承 如果你在Visual Basic NET中建立一个数据访问层基类 你想确保使用 NET框架组件兼容的其它语言的开发人员能继承它并容易理解它怎样工作 通过坚持我概述的指导方针 你的名字转换和构造就不会是语言特定的(language specific) 例如 你可能注意到在本文例子的代码中第一个词小写 并加上intercaps是用于方法的参数的 每个词大写是用于方法的 基类使用Base标志来标识它是一个抽象类

  可以推测 NET框架组件设计指导都是普通设计模式 像Gang of Four (Addison Wesley )写的Design Patterns记载的一样 例如 NET框架组件使用了Observer模式的一个变体 叫做Event模式 在类中暴露事件时你必须遵循它

  规则 利用基础结构(Infrastructure)

   NET框架组件包括一些类和构造 它们能辅助处理通常的与基础结构相关的事务 例如装置和异常处理 通过基类把这些概念与继承组合起来将非常强大 例如 你能考虑一下System Diagnostics名字空间中暴露的跟踪功能 除了提供Trace和Debug类外 该名字空间还包括衍生自Switch和TraceListener的类 Switch类的BooleanSwitch和TraceSwitch能被配置用于打开和关闭应用程序和配置文件 在TraceSwitch中可以暴露多层次跟踪 TraceListener类的TextWriterTraceListener和EventLogTraceListener分别将Trace和Debug方法的输入定位到文本文件和事件日志

  这样作的结果是给基类添加了跟踪功能 使衍生类记录消息日志更简单 接着应用程序能使用配置文件控制是否允许跟踪 你能包括一个BooleanSwitch类型的私有变量并在构造函数中实例化它来给列表 中的DALBase添加这个功能

  Public Sub New(ByVal connect As String)_connection = New SqlConnection(connect)_dalSwitch = New BooleanSwitch( DAL Data Access Code )End Sub

  传递给BooleanSwitch的参数包括名字和描述 接着你能添加一个受保护的属性打开和关闭开关 也能添加一个属性使用Trace对象的WriteLineIf方法格式化并写入跟踪消息

  Protected Property TracingEnabled() As BooleanGetReturn _dalSwitch EnabledEnd GetSet(ByVal Value As Boolean)_dalSwitch Enabled = ValueEnd SetEnd Property

  Protected Sub WriteTrace(ByVal message As String)Trace WriteLineIf(Me TracingEnabled Now & : & message)End Sub

  通过这种途径 衍生类自己并不知道开关(switch)和监听(listener)类 当数据访问类产生一个有意义的信号时能够简单地调用WriteTrace方法

  type= System Diagnostics TextWriterTraceListener initializeData= DALLog txt />

  列表 跟踪的配置文件

  为了建立一个监听器并打开它 需要使用应用程序配置文件 列表 显示了一个简单的配置文件 它能够打开刚才显示的数据访问类开关 并通过myListener调用TextWriterTraceListener把输出定位到文件DALLog txt中 当然 你能通过从TraceListener类衍生程序化地建立监听器并把该监听器直接包含在数据访问类中

  Public Class DALException : Inherits ApplicationExceptionPublic 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 AsException)MyBase New(message innerException)End Sub 在这儿添加自定义成员Public ConnectString As StringEnd Class

  列表 自定义异常类

  你从中收益的第二个基础结构是结构化异常处理(SEH) 在最基本的层次 数据访问类能够暴露它的衍生自System ApplicationException 的Exception(异常)对象并能进一步暴露自定义成员 例如 列表 中显示的DALException对象能用于包装数据访问类中的代码产生的异常 接着基类能暴露一个受保护的方法包装该异常 组装自定义成员 并把它发回给调用程序 如下所示

  Protected Sub ThrowDALException(ByVal message As String _ByVal innerException As Exception)Dim newMine As New DALException(message innerException)

  newMine ConnectString = Me Connection ConnectionStringMe WriteTrace(message & & innerException Message & )Throw newMineEnd Sub

  使用这种方法 衍生类能简单地调用受保护的方法 传递进去一个特定的数据异常(典型的有SqlException或者 OleDbException) 该异常被截取并添加了从属于特定数据域的消息 基类在DALException中包装该异常并把它发回到调用程序 这就允许调用程序用一个Catch语句轻易地捕捉所有来自数据访问类的异常

  作为选择之一 你可以看一看MSDN上发布的 Exception Management Application Block Overview 该框架组件通过一系列对象结合了异常和应用程序日志记录 实际上 通过从 NET 框架组件提供的BaseApplicationException类衍生的自定义异常类能够简单地插入该框架组件

  规则 仔细选择外部界面

  在你设计数据访问类的方法时 需要考虑它们怎样接受和返回数据 对大多数开发人员来说 主要有三个选择 直接使用ADO NET对象 使用XML 使用自定义类

  如果直接暴露ADO NET对象 你能使用一到两个编程模型 第一个包括数据集和数据表对象 它们对不连接数据访问很有用 有很多关于数据集和与它关联的数据表的文章 但是当你必须使用从下层数据存储断开的数据时它才最有用处 换句话说 数据集能在应用程序各层之间传递 即使那些层在物理上是分布式的 当业务和数据服务层放置在同一群服务器上并且与表现服务分开时也能使用 此外 数据集对象是通过基于XML的Web服务返回数据的理想方法 因为它们是可串行化的 因此能在SOAP回应消息中返回

  这与使用实现IDataReader接口的类(例如SqlDataReader 和OleDbDataReader)访问数据不同 数据阅读器(data reader)用只向前的 只读的方式访问数据 两者之间最大的不同是数据集和数据表对象能在应用程序域之间传递 通过传递值(by value)实现 然而数据阅读器能在各处传递 但是一般通过引用(by reference)实现 在列表 中 Read和GetValues在服务器过程中执行并且它们的返回值复制到客户端

  该图显示了数据阅读器怎样存活在应用程序域中 它在那儿它被建立 并且对它的所有访问结果都在客户端和服务器应用程序域之间的循环之中 这意味着当数据访问方法在相同的应用程序域运行时 应该返回数据阅读器作为调用者

  使用数据阅读器时有两个问题需要考虑 首先 当你从数据访问类的一个方法返回数据阅读器时 你必须考虑与数据阅读器关联的连接对象的生存期 默认情况是当调用程序通过数据阅读器重复时连接仍然是忙的 不幸的是当调用程序结束后 连接仍然打开 因此它不返回到连接池(如果允许连接池) 但是 当通过传递CommandBehavior CloseConnection 枚举给mand对象的ExecuteReader方法 连接的Close方法被调用时 你能命令数据阅读器关闭它的连接

  其次 为了把表现层从特定的框架组件数据提供程序(例如SqlClient或者OleDb)中分离出来 调用代码应该使用IDataReader接口(例如SqlDataReader)而不是具体类型来引用返回值 通过这种方法 如果应用程序后端从Oracle移植到 SQL Server 或者数据访问类的一个方法的返回类型改变了 表现层也不需要更改

  如果你希望数据访问类返回XML 你可以从System Xml名字空间中的XmlDocument和XmlReader中选择一个 它与数据集和IDataReader类似 换句话说 当数据从数据源断开时你的方法应该返回一个XmlDocument(或者XmlDataDocument) 然而XmlReader可用于访问XML数据的流

  最后 你也能决定与公共属性一起返回自定义类 这些类可以使用Serialization(串行化)属性标记 这样它们就能跨越应用程序域复制 另外 如果你从方法中返回多个对象 就需要强化类型(strongly typed)的集合类

  Imports System Xml Serialization

  _Public Class Book : Implements IComparablePublic ProductID As IntegerPublic ISBN As StringPublic Title As StringPublic Author As StringPublic UnitCost As DecimalPublic Description As StringPublic PubDate As Date

  Public Function CompareTo(ByVal o As Object) As Integer _Implements IComparable CompareToDim b As Book = CType(o Book)Return Me Title CompareTo(b Title)End FunctionEnd Class

  Public NotInheritable Class BookCollection : Inherits ArrayListDefault Public Shadows Property Item(ByVal productId As Integer) _As BookGetReturn Me(IndexOf(productId))End GetSet(ByVal Value As Book)Me(IndexOf(productId)) = ValueEnd SetEnd Property

  Public Overloads Function Contains(ByVal productId As Integer) As _BooleanReturn ( <> IndexOf(productId))End Function

  Public Overloads Function IndexOf(ByVal productId As Integer) As _IntegerDim index As Integer = Dim item As Book

  For Each item In MeIf item ProductID = productId ThenReturn indexEnd Ifindex = index + NextReturn End Function

  Public Overloads Sub RemoveAt(ByVal productId As Integer)RemoveAt(IndexOf(productId))End Sub

  Public Shadows Function Add(ByVal value As Book) As IntegerReturn MyBase Add(value)End FunctionEnd Class

  列表 使用自定义类

  上列表(列表 )包含了一个简单的Book类和与它关联的集合类的例子 你能注意到Book类用Serializable做了标记 使它跨越应用程序域能使用 by value 语法 该类实现了IComparable接口 因此当它包含在一个集合类中的时候 默认情况下它将按Title排序 BookCollection类从System Collections名字空间的ArrayList衍生 并且为了将该集合限制到Book对象而隐藏了Item属性和ADD方法

  通过使用自定义类你完全地控制了数据的表现 开发人员的效率并且没有依赖ADO NET的调用 但是这种途径需要更多的代码 因为 NET框架组件没有包含任何与对象相关的技术映射 在这种情况下 你应该在数据访问类中建立一个数据读取器并使用它来组合自定义类

  规则 抽象 NET框架组件数据提供程序

  最后一条规则说明了为什么和怎样抽象数据访问类内部使用的 NET框架组件数据提供程序(data provider) 先前我说过ADO NET编程模型暴露了特定的 NET框架组件数据提供程序 包括SqlClient OleDb和其它MSDN Online Web站点上可用的 但是这种设计的结果是提高性能 为数据提供程序暴露特定数据源功能的能力 它强迫你决定使用那种数据提供程序编码 换句话说 开发人员典型地会选择使用SqlClient或OleDb 接着在各自的名字空间直接对它们的类进行编程

  如果你想改变 NET框架组件数据提供程序 你必须重新编写数据访问方法 为了避免这种情况发生 你可以使用Abstract Factory设计模式 使用这种模式 你能建立一个简单的类 它暴露方法来建立主要的 NET框架组件数据提供程序对象(mand connection data adapter和parameter) 而那些对象基于传递给构造函数的 NET框架组件数据提供程序的信息 列表 中的代码就是这样一个简单的类

  public enum ProviderType :int SqlClient = OLEDB =

  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 _Type _parmType _daType;

  private void _initClass() _conType = new Type[ ];_Type = new Type[ ];_parmType = new Type[ ];_daType = new Type[ ];

  // 为提供程序初始化类型_conType[(int)ProviderType SqlClient] = typeof(SqlConnection);_conType[(int)ProviderType OLEDB] = typeof(OleDbConnection);_Type[(int)ProviderType SqlClient] = typeof(SqlCommand);_Type[(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 mandText IDbConnectionconnection) IDataAdapter d;IDbDataAdapter da;

  d = (IDataAdapter)Activator CreateInstance(_daType[(int)_pType] false);da = (IDbDataAdapter)d;da SelectCommand = this CreateCommand(mandText connection);return d;

  public IDataParameter CreateParameter(string paramName DbTypeparamType) IDataParameter p;p = (IDataParameter)Activator CreateInstance(_parmType[(int)_pType] false);p ParameterName = paramName;p DbType = paramType;return p;

  public IDataParameter CreateParameter(string paramName DbTypeparamType 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 IDbConnectionconnection) IDbCommand c;c = (IDbCommand)Activator CreateInstance(_Type[(int)_pType] false);c CommandText = cmdText;c Connection = connection;return c;

  列表 ProviderFactory

  为了使用该类 数据访问类的代码必须对多个 NET框架组件数据提供程序实现的接口(包括IDbCommand IDbConnection IDataAdapter和IDataParameter)进行编程 例如 为了使用一个参数化存储过程的返回值来填充数据集 必须在数据访问类的某个方法中有下面的代码

  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 StoredProceduredb SelectCommand Parameters Add(_pf CreateParameter( @productId DbType Int id))

  Dim ds As New DataSet( Books )da Fill(ds)

  典型的情况是你在类的层次声明ProviderFactory变量并在数据访问类的构造函数中实例化它 另外 它的构造函数与从配置文件中读取的提供程序一起组装 而不应该是硬代码 你可以想象 ProviderFactory是数据访问类的一个重大的补充 并且能被包括进部件 分发给其它的开发人员

  结论

cha138/Article/program/net/201311/11869

相关参考

知识大全 数据层组件设计与数据传递一

  摘要  学习向应用程序公开数据的最佳方式以及如何实现一个有效的策略以便在分布式应用程序的层间传递数据  简介  在设计分布式应用程序时需要确定如何访问和表示与该应用程序相关联的业务数据本文提供一些

知识大全 剖析.Net下的数据访问层技术(1)

剖析.Net下的数据访问层技术(1)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  引言  自从

知识大全 剖析.Net下的数据访问层技术(4)

剖析.Net下的数据访问层技术(4)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Micros

知识大全 剖析.Net下的数据访问层技术(2)

剖析.Net下的数据访问层技术(2)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  其它  结束

知识大全 剖析.Net下的数据访问层技术(3)

剖析.Net下的数据访问层技术(3)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  O/RMap

知识大全 ASP.NET2.0—— 实现数据访问层

ASP.NET2.0——实现数据访问层  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  在文章重点

知识大全 从 .NET 应用程序访问 Microsoft Office 数据

从.NET应用程序访问MicrosoftOffice数据  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下

知识大全 数据访问与ADO.NET

数据访问与ADO.NET  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!ADONET的设计目标  A

知识大全 数据层组件设计与数据传递二

  实现业务实体  业务实体具有以下特点业务实体提供对业务数据及相关功能(在某些设计中)的状态编程访问业务实体可以使用具有复杂架构的数据来构建这种数据通常来自数据库中的多个相关表业务实体数据可以作为业

知识大全 使用ADO.NET设计数据库应用程序

使用ADO.NET设计数据库应用程序  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  认识ADO和