DNN 数据访问策略(一)


经过几天断断续续的努力,这篇文章终于翻译结束,文章主要讲了DNN的数据访问策略,对于了解系统整体上是如何工作的有一定的帮助,希望能给dnn的初学者一些有用的信息。由于翻译的匆忙+水平有限,错误或不当之处在所难免,欢迎大家讨论、指正。

  原作者:

  Shaun Walker – Perpetual Motion Interactive Systems Inc.

  http://www.perpetualmotion.ca

  目录

  简介... 2

  策略... 2

  需求... 3

  配置... 4

  数据访问层 ( DAL ). 8

  数据库脚本... 11

  数据库对象命名... 12

  应用程序块... 12

  数据传输... 12

  业务逻辑层 ( BLL ). 13

  自定义业务对象助手 ( CBO ). 14

  空处理机制... 16

  实现细节... 19

  缓存... 25

  性能... 26

  开发... 26

  自定义模块... 27

  改进核心模块... 29

  sql命令发生器... 30

  参考... 30

  简介

  DotNetNuke(以下简称DNN)的最终目的是创建一个门户的框架平台,这个平台可以为开发者增添模块搭建应用程序提供坚实的可靠的支持。应用程序的一个关键的功能就是数据存取。.NET Framework提供了多种数据存取的方法,从架构的角度来看从这么多方法中选出适合自己的需求的最佳的解决方案很难。本白皮书将尝试着在DNN应用程序的实现中提供最合适的数据存取策略。

  策略

  在很多资料中有各种关于.NET Framework数据存取方法的介绍,然而他们大多脱离了现实的实际应用。虽然大家常常讨论的是这些方法的优点和缺点,但是现在仍有很多开发者不知道如何选择自己的最佳策略。事实上每种方法都有适合它的不同的用例,理论上这是对的,这也是难以选择的原因,然而在实践中,每一个开发者都在寻找一个适合所有企业应用的存取策略。

  一致的数据存取策略有许多好处,有了统一定义好的数据存取策略,开发者就无需浪费时间来为每个任务选择数据存取方法,这种模式提高了代码的维护性,在所有的应用程序范围内实现了一致性。通过数据存取组件的集中处理使得数据存取策略风险降低了,也使得代码的完整性增强了。

  一致的兼容的数据存取策略的概念的确跟每个需求应用其最佳的数据存取策略相悖。为每个应用程序选择相应的最佳的数据存取策略能够获得最好的性能(假定你能够从所有的用例中筛选出最适合的方案)。可是这样可能导致团队在开发实践中难以协调的合作。

  DotNetNuke抛弃了众所周知的传统的80/20原则,它把精力集中在提供一致的兼容的数据存取策略,这个策略理想的目标是把80%的精力放在应用程序用例上,剩下的20%用来考虑跟用其他的数据存取方法相比是否性能要求是必须的,同时也采用了上面所述的策略。

  需求

  DNN的一个重要的需求就是要提供一个能够支持多种数据存储应用程序的实现方法。

  由于对外部数据存储通信的灵活性和性能的要求,我们选择放弃一般的数据存取方法而打造一个新的应用,这个新的应用主要利用了数据库本地化特征集(也就是用.NET管理提供者、所有的SQL语言、存储过程等等)。在选择特殊的数据库访问类时我们做了权衡,我们需要为我们想要支持的每个数据库平台写一个特殊的数据访问层,因此应用程序也就包含了更多的代码。数据访问层共享了大量共同的代码,每一个访问层都明确的处理了特殊数据库应用。

  为了简便的应用数据库访问类我们选择了提供者模式(也就是GOF描述的工厂设计模式),这种模式是通过反射的在应用程序运行时动态的加载正确的(适合的)数据访问对象。工厂是这样实现的:先创建一个抽象类,这个类声明了一个方法,这个方法是每一个数据访问类都必须继承实现的。对每一个我们支持的数据库,我们创建了一个具体的实现类,这个类实现了抽象类或“协议”中定义的各种数据库操作的代码。为了支持在运行时动态加载具体的操作类,我们在工厂里实现了一个Instance()方法,这个方法依赖于提供者类从配置文件读取并反射过来的值来决定需要加载哪个程序集。由于反射在应用程序性能方面非常耗费资源,我们把数据库提供者类的构造器储存到缓存里。

  那么为什么用抽象类而不用接口呢?这是因为接口是(不可变的)静态的,而且因此接口也不能将其自身复制(翻译)。由于接口不支持实现方法继续继承,所以这些类的模式不采用接口。为接口增加一个方法跟为基类增加一个抽象方式是等效的;任何类实现了接口它也就终止了,因为这些类不能再实现新的方法(确切的说应该是接口定义以外的方法接口无法使用)。

  下面的图表展示了业务逻辑、工厂以及数据库存取类是如何相互联系的。这个解决方案的关键优势是只要数据访问类实现了DataProvider抽象类的方法,数据库访问类就能够在业务逻辑类之后编译。这就意味着在我们想要创建另一个数据库的实现方法时我们无需改变业务逻辑层(或用户界面层),创建另一个实现方法的步骤是:

  1、为新的数据库创建数据库访问类,这些类实现了DataProvider的抽象类。

  2、将这些类类编译成一个程序集。

  3、测试并配置这个新的数据访问程序集到正在运行的服务器。

  4、修改配置文件,来指定新的数据库访问类。

  5、无需对业务逻辑组件进行任何改变也无需重新编译它。

  配置

  Web.config文件包含了许多配置节来使DataProvider模式起作用。第一个配置节注册了这些提供者(Providers)和他们相应的配置节处理方法(ConfigurationSectionHandlers)。尽管在这个例子中我们仅仅展示了DotNetNuke配置节组中的一个,我们可以通过类似的方法配置其它的提供者(也就是抽象验证提供者等等)。但有一点是必须保证的,那就是web.config文件中必须实现这些配置节。

  <configSections>
    <sectionGroup name="dotnetnuke">
      <section name="data" type="DotNetNuke.ProviderConfigurationHandler, DotNetNuke" />
    </sectionGroup>
  </configSections>

  下面的配置节是为老式的数据访问方法的模块而保留的:

  <appSettings>
    <add key="connectionString" value="Server=localhost;Database=DotNetNuke;uid=sa;pwd=;" />
  </appSettings>

  如上最终实现了大量的提供者模块。<dotnetnuke>配置组中的<data>配置节名称需要有一个默认的提供者(defaultProvider)属性,这个属性依赖于下面的<providers>集合中的特定的实例。在从一个provider转变为另一个provider的时候defaultProvider被用来作为单一的转换器。如果没有指定默认的provider,这个配置集合中的第一项就被认为是默认的。

  <data>配置节也包含了一个<providers>集合说明,确定了所有的<data>实现都是唯一的。每一个provider都必须至少包含name、type和providerPath属性(name不是特定的但是通常相应的类名,type指定了provider相关的强类名,providerPath指定了provider的特殊资源例如脚本在哪里)。每一个提供者同样可以有多个自定义属性。

  <dotnetnuke>
    <data defaultProvider="SqlDataProvider" >
      <providers>
     <clear/>
        <add name = "SqlDataProvider"
            type = "DotNetNuke.Data.SqlDataProvider, DotNetNuke.SqlDataProvider"
            connectionString = "Server=localhost;Database=DotNetNuke;uid=sa;pwd=;"
            providerPath = "~databaseSqlDataProvider"
            objectQualifier = "DotNetNuke"
            databaseOwner = "dbo"
        />
        <add name = "AccessDataProvider"
            type = "DotNetNuke.Data.AccessDataProvider, DotNetNuke.AccessDataProvider"
            connectionString = "PROVIDER=Microsoft.Jet.OLEDB.4.0;"
            providerPath = "~databaseAccessDataProvider"
            objectQualifier = "DotNetNuke"
            databaseFilename = "DotNetNuke.mdb"
        />
      </providers>
    </data>
  </dotnetnuke>

  下面是对providers集合中的节点作用的详细说明。

  <providers>配置节包含了一个或多个<add>、<remove>、<clear>元素。下面的规则应用在处理这些元素的时候:

  1、 声明一个空的<providers />不是错误。

  2、 Providers继承了父配置中<add>声明的项。

  3、 如果某项已经存在了或被继承了再用<add>重新定义那么这是错误的。

  4、 <remove>一个不存在的项是错误的。

  5、 如果一个项被<add>后又被<remove>了,然后再<add>这个完全相同的项是可以的(不是错误)。

  6、 如果一个项<add>, <clear>,然后再<add>是可以的(不是错误的)。

  7、 <clear>会清除所有在先前定义的和继承的项。例如:先用<add>声明再用<clear>清除那么项就不存在了,而在<clear>后再<add>声明的项是不会被清除的。

  

  <add>

描述 增加一个数据提供者(data provider)。
属性 Name——provider的友好的名称。

  Type——一个实现了provider接口的类。这个值是一个程序集的完整的关联。

  providerPath——查找provider的特殊资源(如脚本)的路径。

  其它name/value对——也许还有一些附加的名称/值对,所有的名称/值对都是provider能够理解的(处理的)。

  

  <remove>

描述 清除一个指定的数据提供者
属性 Name——要清除的provider的友好名称。

  

  <clear>

描述 清除所有的继承的提供者。

  ComponentsProvider.vb

  Provider.vb类提供了所有的实现细节,这些实现包括从web.config文件加载provider的信息以及应用<add>, <remove>, <clear>处理的规则(标准)。这是一个通用的(generic)类,它不仅仅适用于数据访问。

  ComponentsDataProvider.vb

  DataProvider.vb是一个抽象类,这个类包含了DNN的所有的数据访问方法。它包含了一个工厂本身的实例方法(Instance()),它负责在运行时动态加载web.config中描述的合适的程序集。

    ' unique provider name used for caching
    Private Const ProviderType As String = "data"
    Private Const ProviderName As String = ""
  
    Public Shared Function Instance() As DataProvider
  
      ' Use the cache because the reflection used later is expensive
      Dim cache As System.Web.Caching.Cache = System.Web.HttpContext.Current.Cache
  
      If cache(ProviderName & ProviderType & "provider") Is Nothing Then
  
        ' Get the name of the provider
        Dim objProviderConfiguration As ProviderConfiguration = ProviderConfiguration.GetProviderConfiguration(ProviderType)
  
        ' The assembly should be in bin or GAC, so we simply need to get an instance of the type
        Try
  
          ' Get the typename of the DataProvider ( ie. DotNetNuke.Data.SqlDataProvider, DotNetNuke.SqlDataProvider )
          Dim strTypeName As String = CType(objProviderConfiguration.Providers(objProviderConfiguration.DefaultProvider), Provider).Type
          ' Override the typename if a ProviderName is specified ( this allows the application to load a different DataProvider assembly for custom modules )
          strTypeName.Replace(objProviderConfiguration.DefaultProvider, ProviderName & objProviderConfiguration.DefaultProvider)
          ' Use reflection to store the constructor of the class that implements DataProvider
          Dim t As Type = Type.GetType(strTypeName, True)
  
          ' Insert the type into the cache
          cache.Insert(ProviderName & ProviderType & "provider", t.GetConstructor(System.Type.EmptyTypes))
  
        Catch e As Exception
  
        End Try
      End If
  
      Return CType(CType(cache(ProviderName & ProviderType & "provider"), ConstructorInfo).Invoke(Nothing), DataProvider)
  
    End Function

  所有的数据访问方法都被定义成必须重写的(MustOverride)。也就是说所有的从词类派生的数据提供者类都必须提供这些方法的实现。这些定义了业务逻辑层和数据存取层之间联系的抽象类协议。

    ' links module
    Public MustOverride Function GetLinks(ByVal ModuleId As Integer) As IDataReader
    Public MustOverride Function GetLink(ByVal ItemID As Integer, ByVal ModuleId As Integer) As IDataReader
    Public MustOverride Sub DeleteLink(ByVal ItemID As Integer)
    Public MustOverride Sub AddLink(ByVal ModuleId As Integer, ByVal UserName As String, ByVal Title As String, ByVal Url As String, ByVal MobileUrl As String, ByVal ViewOrder As String, ByVal Description As String, ByVal NewWindow As Boolean)
    Public MustOverride Sub UpdateLink(ByVal ItemId As Integer, ByVal UserName As String, ByVal Title As String, ByVal Url As String, ByVal MobileUrl As String, ByVal ViewOrder As String, ByVal Description As String, ByVal NewWindow As Boolean)

  数据访问层( DAL )

  数据访问层(DAL)必须实现数据提供者抽象类中声明的方法。然而,每一个DAL提供者在实现这些方法时也许很不相同。这种处理允许提供者灵活的选择他们自己的数据库访问协议(也就是说:.NET管理的OleDB, ODBC等)。同样也允许提供者处理数据库平台之间的所有不同之处(例如:存储过程,sql语言语法,@@IDENTITY)。

  每一个数据提供者必须为其在web.config中的自定义属性指定一个实现方法。

Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports Microsoft.ApplicationBlocks.Data
Imports System.IO
Imports System.Web
Imports DotNetNuke
  
Namespace DotNetNuke.Data
  
  Public Class SqlDataProvider
  
    Inherits DataProvider
  
    Private Const ProviderType As String = "data"
  
    Private _providerConfiguration As ProviderConfiguration = ProviderConfiguration.GetProviderConfiguration(ProviderType)
    Private _connectionString As String
    Private _providerPath As String
    Private _objectQualifier As String
    Private _databaseOwner As String
  
    Public Sub New()
  
      ' Read the configuration specific information for this provider
      Dim objProvider As Provider = CType(_providerConfiguration.Providers(_providerConfiguration.DefaultProvider), Provider)
  
      ' Read the attributes for this provider
      _connectionString = objProvider.Attributes("connectionString")
  
      _providerPath = objProvider.Attributes("providerPath")
  
      _objectQualifier = objProvider.Attributes("objectQualifier")
      If _objectQualifier <> "" And _objectQualifier.EndsWith("_") = False Then
        _objectQualifier += "_"
      End If
  
      _databaseOwner = objProvider.Attributes("databaseOwner")
      If _databaseOwner <> "" And _databaseOwner.EndsWith(".") = False Then
        _databaseOwner += "."
      End If
  
    End Sub
  
    Public ReadOnly Property ConnectionString() As String
      Get
        Return _connectionString
      End Get
    End Property
  
    Public ReadOnly Property ProviderPath() As String
      Get
        Return _providerPath
      End Get
    End Property
  
    Public ReadOnly Property ObjectQualifier() As String
      Get
        Return _objectQualifier
      End Get
    End Property
  
    Public ReadOnly Property DatabaseOwner() As String
      Get
        Return _databaseOwner
      End Get
    End Property

  数据访问方法必须涉及成简单的查询(例如 单一的select,insert,update,delete),以便于在所有的数据库平台上他们都能被实现。业务逻辑(例如条件分支,计算或局部变量)应该在业务逻辑层实现,这样才能从数据库抽象出来并集中到一个应用程序(模块)里处理。如果你经常用那些使你可以在数据库层实现程序逻辑的富sql语言变量工作的话,这种数据库访问是相当简单的。

  DNN中的Sql server/msde 数据提供者用了存储过程作为最好的数据访问技术。

    ' links module
    Public Overrides Function GetLinks(ByVal ModuleId As Integer) As IDataReader
      Return CType(SqlHelper.ExecuteReader(ConnectionString, DatabaseOwner & ObjectQualifier & "GetLinks", ModuleId), IDataReader)
    End Function
    Public Overrides Function GetLink(ByVal ItemId As Integer, ByVal ModuleId As Integer) As IDataReader
      Return CType(SqlHelper.ExecuteReader(ConnectionString, DatabaseOwner & ObjectQualifier & "GetLink", ItemId, ModuleId), IDataReader)
    End Function
    Public Overrides Sub DeleteLink(ByVal ItemId As Integer)
      SqlHelper.ExecuteNonQuery(ConnectionString, DatabaseOwner & ObjectQualifier & "DeleteLink", ItemId)
    End Sub
    Public Overrides Sub AddLink(ByVal ModuleId As Integer, ByVal UserName As String, ByVal Title As String, ByVal Url As String, ByVal MobileUrl As String, ByVal ViewOrder As String, ByVal Description As String, ByVal NewWindow As Boolean)
      SqlHelper.ExecuteNonQuery(ConnectionString, DatabaseOwner & ObjectQualifier & "AddLink", ModuleId, UserName, Title, Url, MobileUrl, IIf(ViewOrder <> "", ViewOrder, DBNull.Value), Description, NewWindow)
    End Sub
    Public Overrides Sub UpdateLink(ByVal ItemId As Integer, ByVal UserName As String, ByVal Title As String, ByVal Url As String, ByVal MobileUrl As String, ByVal ViewOrder As String, ByVal Description As String, ByVal NewWindow As Boolean)
      SqlHelper.ExecuteNonQuery(ConnectionString, DatabaseOwner & ObjectQualifier & "UpdateLink", ItemId, UserName, Title, Url, MobileUrl, IIf(ViewOrder <> "", ViewOrder, DBNull.Value), Description, NewWindow)
    End Sub

  DNN用到存储过程(存储过程查询)但是没有用参数自动查找的特性( CommandBuilder.DeriveParameters ),因此参数必须明确的定义。

    ' links module
    Public Overrides Function GetLinks(ByVal ModuleId As Integer) As IDataReader
      Return CType(OleDBHelper.ExecuteReader(ConnectionString, CommandType.StoredProcedure, ObjectQualifier & "GetLinks", _
        New OleDbParameter("@ModuleId", ModuleId)), IDataReader)
    End Function
    Public Overrides Function GetLink(ByVal ItemId As Integer, ByVal ModuleId As Integer) As IDataReader
      Return CType(OleDBHelper.ExecuteReader(ConnectionString, CommandType.StoredProcedure, ObjectQualifier & "GetLink", _
        New OleDbParameter("@ItemId", ItemId), _
        New OleDbParameter("@ModuleId", ModuleId)), IDataReader)
    End Function
    Public Overrides Sub DeleteLink(ByVal ItemId As Integer)
      OleDBHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, ObjectQualifier & "DeleteLink", _
        New OleDbParameter("@ItemId", ItemId))
    End Sub
    Public Overrides Sub AddLink(ByVal ModuleId As Integer, ByVal UserName As String, ByVal Title As String, ByVal Url As String, ByVal MobileUrl As String, ByVal ViewOrder As String, ByVal Description As String, ByVal NewWindow As Boolean)
      OleDBHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, ObjectQualifier & "AddLink", _
        New OleDbParameter("@ModuleId", ModuleId), _
        New OleDbParameter("@UserName", UserName), _
        New OleDbParameter("@Title", Title), _
        New OleDbParameter("@Url", Url), _
        New OleDbParameter("@MobileUrl", MobileUrl), _
        New OleDbParameter("@ViewOrder", IIf(ViewOrder <> "", ViewOrder, DBNull.Value)), _
        New OleDbParameter("@Description", Description), _
        New OleDbParameter("@NewWindow", NewWindow))
    End Sub
    Public Overrides Sub UpdateLink(ByVal ItemId As Integer, ByVal UserName As String, ByVal Title As String, ByVal Url As String, ByVal MobileUrl As String, ByVal ViewOrder As String, ByVal Description As String, ByVal NewWindow As Boolean)
      OleDBHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, ObjectQualifier & "UpdateLink", _
        New OleDbParameter("@ItemId", ItemId), _
        New OleDbParameter("@UserName", UserName), _
        New OleDbParameter("@Title", Title), _
        New OleDbParameter("@Url", Url), _
        New OleDbParameter("@MobileUrl", MobileUrl), _
        New OleDbParameter("@ViewOrder", IIf(ViewOrder <> "", ViewOrder, DBNull.Value)), _
        New OleDbParameter("@Description", Description), _
        New OleDbParameter("@NewWindow", NewWindow))
    End Sub

  数据库脚本

  DNN包含了一个自动升级的特性,也就是当有了一个新的应用程序版本发布后,应用程序能够自动更新数据库。不过脚本必须根据版本号和数据提供者命名(例如02.00.00.SqlDataProvider),并且必须放在web.config文件中providerPath属性所指定的目录下。动态升级的实现需要脚本中重写provider所实现的ExecuteScript的方法。这对安全规范和对象命名很有用。

  create procedure {databaseOwner}{objectQualifier}GetLinks

  @ModuleId int

as
  
select ItemId,
    CreatedByUser,
    CreatedDate,
   Title,
    Url,
    ViewOrder,
    Description,
    NewWindow
from {objectQualifier}Links
where ModuleId = @ModuleId
order by ViewOrder, Title

  GO

  数据库对象命名 

  web.config文件包含了一个名字叫objectQualifer的属性,它允许你为数据库对象指定一个前缀(例如DNN_)。Web主机往往只提供一个数据库服务器,因此必须跟其它web应用程序共享一个账号。如果你没有指定前缀也许会跟已经存在的其它应用程序的数据库对象名称冲突。指定前缀的另一个好处就是这些数据库对象在SQL Server企业管理器等管理工具里当按字母排序的时候他们会分组显示,这就方便了管理。

  如果你升级的是DotNetNuke2.0以前版本的数据库,你需要设置objectQualifier为“”。这是因为你也许用了第三方模块,而这些模块不用新的DAL体系架构而是用特定的对象名称。升级设置objectQualifier时会将你所有的核心的数据库对象重命名,这可能会使你的自定义的模块出错。

  应用程序块

  微软数据访问应用程序块(MSDAAB)是一个.NET组件,它包含了最优化的数据访问代码,它能帮助你依靠SQL Server数据库调用存储过程、发布sql文本命令。我们借用这些方法作为dnn的一个建造模块,来减少创建、测试和维护大量自定义的数据访问方法的代码。我们也为基于MSDAAB代码的微软Access数据提供者创建了一个OleDB.ApplicationBlocks.Data程序集。

  比起在我们的DAL实现方法里包含真正的资源代码来说,我们选择用MSDAAB作为一个黑箱组件的实现方法能够帮助我们防止修改MSDAAB代码,能够使我们完美的升级组件,这个新特征是可以实现的。

  数据传输

  DotNetNuke用DataReader把从数据访问层(DAL)那里读取的数据集合传递给业务逻辑层(BLL)。选用DataReader是因为它是ADO.NET提供的租块的数据传输机制(一个只向前的只读的数据流)。IDataReader是所有.NET兼容的数据读取器(DataReaders)的基本接口。这个抽象的IDataReader接口使我们能够在各层之间传输数据,而无需考虑数据访问协议在实际的数据提供者实现中可不可用(例如SqlClient, OleDB, ODBC等)。

  业务逻辑层 ( BLL )

  好的面向对象设计推荐我们将数据存储从应用程序中提取(抽象)出来。通过提取可以在应用程序上建立一个独立的业务逻辑接口集;因此减少了对下面的数据库物理实现的依赖。

  DNN的业务逻辑层被有效的定义在Components的子文件夹下。业务逻辑层包含了表述层调用的各种应用程序服务的抽象类。在数据访问方面,业务逻辑层向前调用应用程序接口(API)到适当的数据提供者,这个过程就文档前面介绍的数据提供者工厂机制。

  自定义业务对象是一个用自定义的数据结构封装数据的面向对象的技术。自定义业务对象需要一些自定义的代码,这就在类型安全设计模型、分离数据存储和序列化方面增加了开销。自定义业务对象提供了最大程度的灵活性,他们使应用程序可以在它的抽象范围内定义自己的数据结构;也消除了对所有数据容器的依赖(例如RecordSet, DataSet )。

  什么是类型安全的设计模型呢?考虑一下下面的数据访问代码例子:变量= DataReader(“字段名”)。这里将数据库字段的值赋给了一个变量,这段代码的问题在于无法保证数据库字段的数据类型和变量的数据类型相匹配;并且这个过程中的任何错误都将提交给运行时(run-time)来处理。采用自定义业务对象那么代码将是这样:变量=对象.属性(variable = Object.Property)。这样的话编译器就会在数据不匹配的时候迅速的告知我们。类型安全的设计还可以智能感知和改进代码的易读性。

  一组对象我们成为一个集合。在DNN里我们用了标准的动态数组(ArrayList)来描述一组自定义业务对象。ArrayLists是ASP.NET内部对象,它包含了基本集合所需要的所有特征(add,remove,find,iterate)。在ArrayList的特征里我们最重要的特征是它实现了IEnumerable接口,它可以被数据绑定(databound)给ASP.NET web控件(web control)。

  DNN的数据访问层是以DataReader的形式传递信息给业务逻辑层的。关于这种实现的一个问题就是为什么DNN用DataReader作为数据传输容器(container)来传输数据而不直接从DAL层传递数据给自定义业务对象。这是因为虽然这两种方法都是可行的,但我们相信使DAL层从BLL层完全独立出来是由一些优点的。例如:我们要为自定义业务对象增加一个附加属性,这种情况下这个属性仅仅是表述层用到而数据库中根本不需要,用DNN的方法,DAL层实现方法不需要做任何改动,因为他们对上面的BLL层没有任何依赖;但是如果DAL直接提供数据给自定义业务对象,所有的DAL层实现都需要重新编译来符合BLL层结构的需要。

  自定义业务对象助手 ( CBO )

  为了最小化移植从数据层传输来到自定义业务逻辑对象信息的代码工作量,创建了一个通用的utility类。这个类包含了两个公共的方法(函数)——一个是返回一个单独对象的实例,一个是返回一个集合对象(arraylist)。一般来说这个类里定义的每个属性在Datareader里都有相应的字段对应。这些影射的信息的名称和数据类型都必须是唯一的。下面的代码展示了如何用反射将datareader里的数据填充给自定义业务对象然后再关闭datareader。

  Public Class CBO
  
    Private Shared Function GetPropertyInfo(ByVal objType As Type) As ArrayList
  
      ' Use the cache because the reflection used later is expensive
      Dim objCache As System.Web.Caching.Cache = System.Web.HttpContext.Current.Cache
  
      If objCache(objType.Name) Is Nothing Then
        Dim objProperties As New ArrayList()
        Dim objProperty As PropertyInfo
        For Each objProperty In objType.GetProperties()
          objProperties.Add(objProperty)
        Next
        objCache.Insert(objType.Name, objProperties)
      End If
  
      Return CType(objCache(objType.Name), ArrayList)
  
    End Function
  
    Private Shared Function GetOrdinals(ByVal objProperties As ArrayList, ByVal dr As IDataReader) As Integer()
  
      Dim arrOrdinals(objProperties.Count) As Integer
      Dim intProperty As Integer
  
      If Not dr Is Nothing Then
        For intProperty = 0 To objProperties.Count - 1
          arrOrdinals(intProperty) = -1
          If CType(objProperties(intProperty), PropertyInfo).CanWrite Then
            Try
              arrOrdinals(intProperty) = dr.GetOrdinal(objProperties(intProperty).Name)
            Catch
              ' property does not exist in datareader
            End Try
          End If
        Next intProperty
      End If
  
      Return arrOrdinals
  
    End Function
  
    Private Shared Function CreateObject(ByVal objType As Type, ByVal dr As IDataReader, ByVal objProperties As ArrayList, ByVal arrOrdinals As Integer()) As Object
  
      Dim objObject As Object = Activator.CreateInstance(objType)
      Dim intProperty As Integer
  
      ' fill object with values from datareader
      For intProperty = 0 To objProperties.Count - 1
        If arrOrdinals(intProperty) <> -1 Then
          If IsDBNull(dr.GetValue(arrOrdinals(intProperty))) Then
            ' translate Null value
            objProperties(intProperty).SetValue(objObject, Null.SetNull(CType(objProperties(intProperty), PropertyInfo)), Nothing)
          Else
            Try
              ' try implicit conversion first
              objProperties(intProperty).SetValue(objObject, dr.GetValue(arrOrdinals(intProperty)), Nothing)
            Catch ' data types do not match
              Try
                ' try explicit conversion
                objProperties(intProperty).SetValue(objObject, Convert.ChangeType(dr.GetValue(arrOrdinals(intProperty)), CType(objProperties(intProperty), PropertyInfo).PropertyType), Nothing)
              Catch
                ' error assigning a datareader value to a property
              End Try
            End Try
          End If
        End If
      Next intProperty
  
      Return objObject
  
    End Function
  
    Public Shared Function FillObject(ByVal dr As IDataReader, ByVal objType As Type) As Object
  
      Dim objFillObject As Object
      Dim intProperty As Integer
  
      ' get properties for type
      Dim objProperties As ArrayList = GetPropertyInfo(objType)
  
      ' get ordinal positions in datareader
      Dim arrOrdinals As Integer() = GetOrdinals(objProperties, dr)
  
      ' read datareader
      If dr.Read Then
        ' fill business object
        objFillObject = CreateObject(objType, dr, objProperties, arrOrdinals)
      Else
        objFillObject = Nothing
      End If
  
      ' close datareader
      If Not dr Is Nothing Then
        dr.Close()
      End If
  
      Return objFillObject
  
    End Function
  
    Public Shared Function FillCollection(ByVal dr As IDataReader, ByVal objType As Type) As ArrayList
  
      Dim objFillCollection As New ArrayList()
      Dim objFillObject As Object
      Dim intProperty As Integer
  
      ' get properties for type
      Dim objProperties As ArrayList = GetPropertyInfo(objType)
  
      ' get ordinal positions in datareader
      Dim arrOrdinals As Integer() = GetOrdinals(objProperties, dr)
  
      ' iterate datareader
      While dr.Read
        ' fill business object
        objFillObject = CreateObject(objType, dr, objProperties, arrOrdinals)
        ' add to collection
        objFillCollection.Add(objFillObject)
      End While
  
      ' close datareader
      If Not dr Is Nothing Then
        dr.Close()
      End If
  
      Return objFillCollection
  
    End Function
  
  End Class


« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3