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 = "~\database\SqlDataProvider\"
??????????????????????? objectQualifier = "DotNetNuke"
??????????????????????? databaseOwner = "dbo"
??????????????? />
??????????????? <add name = "AccessDataProvider"
??????????????????????? type = "DotNetNuke.Data.AccessDataProvider, DotNetNuke.AccessDataProvider"
??????????????????????? connectionString = "PROVIDER=Microsoft.Jet.OLEDB.4.0;"
??? ??????????????????? providerPath = "~\database\AccessDataProvider\"
??????????????????????? 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> | |
| 描述 | 清除所有的繼承的提供者。 |
?
\Components\Provider.vb
?
Provider.vb類提供了所有的實現細節,這些實現包括從web.config文件加載provider的信息以及應用<add>, <remove>, <clear>處理的規則(標準)。這是一個通用的(generic)類,它不僅僅適用于數據訪問。
?
\Components\DataProvider.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
空處理
?
?????? 每一個數據存取系統都有一個特殊的構造來處理那些沒有明確指定的字段值。
在大多數關系數據庫管理系統中,這個構造就是眾所周知的null值。從應用程序的角度看,在表述層和數據存取層傳遞null值是一個架構上的挑戰。這是因為表述層必須從數據庫的特定信息抽象出來;而且,當一個屬性值沒有明確指定的時候表述層也必須能夠表達說明。事實上這相當復雜,.NET Framework的本身的數據類型不能自動的轉換從數據庫返回的null值(如果你試圖直接那樣賦值的話將會拋出一個異常)。另外,每一個數據存儲都有它自己的屬性來實現null。唯一合理的解決方案就是創建一個抽象的傳輸服務,來編碼/解碼應用程序各層之間的null值。
?
?????? 乍一看,你也許會想到用vb.net中的“nothing”關鍵字可以很好的擔負起這個傳輸服務的任務。不幸的是,調查顯示,.NET Framework本身的數據類型處理“nothing”的時候沒有預想的那么好。盡管分配為nothing的屬性不會拋出異常,實際上這個屬性的值將非常依賴于它的數據類型(String = Nothing, Date = Date.MinValue, Integer = 0, Boolean = False, 等等)并且自帶的IsNothing()函數的結果還不是一致的(兼容的)結果。
?
?????? 在DNN里,我們創建了一個通用的類來處理null的問題,它統一管理應用程序各層的null問題。在應用程序中用一個常量來描述每種數據類型的null情況,再把這個常量轉化成各種數據存儲實現里的實際的null值。這個類包含的各種方法將null轉換服務的物理細節從應用程序中抽象出來了。
?????? * 記住,這個類僅僅用在數據庫字段允許有null值的情況下。還要記住,這個類要求DAL和BLL層之間的數據類型一致(例如:一個BLL信息類里的屬性字段的數據類型必須跟DAL 數據提供者傳遞過來的參數的數據類型一致)。
?
?
??? Public Class Null
?
??????? ' define application encoded null values
??????? Public Shared ReadOnly Property NullInteger() As Integer
??????????? Get
??????????????? Return -1
??????????? End Get
??? ????End Property
??????? Public Shared ReadOnly Property NullDate() As Date
??????????? Get
??????????????? Return Date.MinValue
??????????? End Get
??????? End Property
??????? Public Shared ReadOnly Property NullString() As String
??????????? Get
?????? ?????????Return ""
??????????? End Get
??????? End Property
??????? Public Shared ReadOnly Property NullBoolean() As Boolean
??????????? Get
??????????????? Return False
??????????? End Get
??????? End Property
?
??????? ' sets a field to an application encoded null value ( used in Presentation layer )
??????? Public Shared Function SetNull(ByVal objField As Object) As Object
??????????? If TypeOf objField Is Integer Then
??????????????? SetNull = NullInteger
??????????? ElseIf TypeOf objField Is Date Then
??????????????? SetNull = NullDate
??????????? ElseIf TypeOf objField Is String Then
??????????????? SetNull = NullString
??????????? ElseIf TypeOf objField Is Boolean Then
??????????????? SetNull = NullBoolean
??????????? Else
??????????????? Throw New NullReferenceException()
??????????? End If
??????? End Function
?
??????? ' sets a field to an application encoded null value ( used in BLL layer )
??????? Public Shared Function SetNull(ByVal objPropertyInfo As PropertyInfo) As Object
??????????? Select Case objPropertyInfo.PropertyType.ToString
??????????????? Case "System.Int16", "System.Int32", "System.Int64", "System.Single", "System.Double", "System.Decimal"
??????????????????? SetNull = NullInteger
??????????????? Case "System.DateTime"
??????????????? ????SetNull = NullDate
??????????????? Case "System.String", "System.Char"
??????????????????? SetNull = NullString
??????????????? Case "System.Boolean"
??????????????????? SetNull = NullBoolean
??????????????? Case Else
??????????????????? Throw New NullReferenceException()
??????????? End Select
??????? End Function
?
??????? ' convert an application encoded null value to a database null value ( used in DAL )
??????? Public Shared Function GetNull(ByVal objField As Object, ByVal objDBNull As Object) As Object
??????????? GetNull = objField
??????????? If TypeOf objField Is Integer Then
??????????????? If objField = NullInteger Then
??????????????????? GetNull = objDBNull
??????????????? End If
??????????? ElseIf TypeOf objField Is Date Then
?????????????? ?If objField = NullDate Then
??????????????????? GetNull = objDBNull
??????????????? End If
??????????? ElseIf TypeOf objField Is String Then
??????????????? If objField = NullString Then
??????????????????? GetNull = objDBNull
??????????????? End If
???? ???????ElseIf TypeOf objField Is Boolean Then
??????????????? If objField = NullBoolean Then
??????????????????? GetNull = objDBNull
??????????????? End If
??????????? Else
??????????????? Throw New NullReferenceException()
??????????? End If
??????? End Function
?
??????? ' checks if a field contains an application encoded null value
??????? Public Shared Function IsNull(ByVal objField As Object) As Boolean
??????????? If objField = SetNull(objField) Then
??????????????? IsNull = True
??????????? Else
???? ???????????IsNull = False
??????????? End If
??????? End Function
?
??? End Class
實現細節
?????? 下面的一段代碼例子示范了應用程序的各層之間是如何完成數據訪問的。
?
表述層( UI )
?????? 表述層依賴于它上面的業務邏輯層。自定義業務邏輯對象的屬性和方法建立了這兩個曾之間的基礎接口(表述層不要直接調用數據訪問層的方法)。
?
?
獲取
?
?????? ' create a Controller object
??? Dim objAnnouncements As New AnnouncementsController
?
??? ' get the collection
??? lstAnnouncements.DataSource = objAnnouncements.GetAnnouncements(ModuleId)
??? lstAnnouncements.DataBind()
?
增加/更新
?
??? ...
??? Private itemId As Integer
???
??? If Not (Request.Params("ItemId") Is Nothing) Then
??????? itemId = Int32.Parse(Request.Params("ItemId"))
??? Else
??????? itemId = Null.SetNull(itemId)
??? End If
??? ...
?
??? ' create an Info object
??? Dim objAnnouncement As New AnnouncementInfo
?
??? ' set the properties
??? objAnnouncement.ItemId = itemId
??? objAnnouncement.ModuleId = ModuleId
??? objAnnouncement.CreatedByUser = Context.User.Identity.Name
??? objAnnouncement.Title = txtTitle.Text
??? objAnnouncement.Description = txtDescription.Text
??? objAnnouncement.Url = txtExternal.Text
??? objAnnouncement.Syndicate = chkSyndicate.Checked
??? If txtViewOrder.Text <> "" Then
??????? objAnnouncement.ViewOrder = txtViewOrder.Text
??? Else
??????? objAnnouncement.ViewOrder = Null.SetNull(objAnnouncement.ViewOrder)
??? End If
??? If txtExpires.Text <> "" Then
??????? objAnnouncement.ExpireDate = txtExpires.Text
??? Else
??????? objAnnouncement.ExpireDate = Null.SetNull(objAnnouncement.ExpireDate)
??? End If
?
??? ' create a Controller object
??? Dim objAnnouncements As New AnnouncementsController
?
??? If Null.IsNull(itemId) Then
??????? ' add
??????? objAnnouncements.AddAnnouncement(objAnnouncement)
??? Else
??????? ' update
??????? objAnnouncements.UpdateAnnouncement(objAnnouncement)
??? End If
?
??? ** Notice the use of the Null.SetNull() and Null.IsNull() helper methods
?
刪除
?
?????? ' create a Controller object
??? Dim objAnnouncements As New AnnouncementsController
?
??? ' delete the record
??? objAnnouncements.DeleteAnnouncement(itemId)
?
業務邏輯層( BLL )
?
?????? 每一個應用程序業務方法都有跟它相對應的多關系業務對象組成的物理文件。每一個業務對象定義都有一個定義它的屬性的信息類和定義它的方法的控制類。
?
??? Public Class AnnouncementInfo
?
??????? ' local property declarations
??????? Private _ItemId As Integer
??????? Private _ModuleId As Integer
??????? Private _UserName As String
??????? Private _Title As String
??????? Private _Url As String
??????? Private _Syndicate As Boolean
??????? Private _ExpireDate As Date
??????? Private _Description As String
??????? Private _ViewOrder As Integer
??????? Private _CreatedByUser As String
??????? Private _CreatedDate As Date
??????? Private _Clicks As Integer
?
? ??????' constructor
??????? Public Sub New()
??????? ' custom initialization logic
??????? End Sub
?
??????? ' public properties
??????? Public Property ItemId() As Integer
??????????? Get
??????????????? Return _ItemId
??????????? End Get
??????????? Set(ByVal Value As Integer)
??????????????? _ItemId = Value
??????????? End Set
??????? End Property
?
??????? Public Property ModuleId() As Integer
??????????? Get
??????????????? Return _ModuleId
??????????? End Get
??????????? Set(ByVal Value As Integer)
???????? ???????_ModuleId = Value
??????????? End Set
??????? End Property
?
??????? Public Property Title() As String
??????????? Get
??????????????? Return _Title
??????????? End Get
??????????? Set(ByVal Value As String)
??????????????? _Title = Value
?????????? ?End Set
??????? End Property
?
??????? Public Property Url() As String
??????????? Get
??????????????? Return _Url
??????????? End Get
??????????? Set(ByVal Value As String)
??????????????? _Url = Value
??????????? End Set
??????? End Property
?
??????? Public Property Syndicate() As Boolean
??????????? Get
??????????????? Return _Syndicate
??????????? End Get
??????????? Set(ByVal Value As Boolean)
??????????????? _Syndicate = Value
??????????? End Set
??????? End Property
?
??????? Public Property ViewOrder() As Integer
??????????? Get
??????????????? Return _ViewOrder
??????????? End Get
??????????? Set(ByVal Value As Integer)
??????????????? _ViewOrder = Value
??????????? End Set
??????? End Property
?
??????? Public Property Description() As String
?????? ?????Get
??????????????? Return _Description
??????????? End Get
??????????? Set(ByVal Value As String)
??????????????? _Description = Value
??????????? End Set
??????? End Property
?
??????? Public Property ExpireDate() As Date
??????????? Get
??????????? ????Return _ExpireDate
??????????? End Get
??????????? Set(ByVal Value As Date)
??????????????? _ExpireDate = Value
??????????? End Set
??????? End Property
?
??????? Public Property CreatedByUser() As String
??????????? Get
??????????????? Return _CreatedByUser
??????????? End Get
??????????? Set(ByVal Value As String)
??????????????? _CreatedByUser = Value
??????????? End Set
??????? End Property
?
??????? Public Property CreatedDate() As Date
??????????? Get
??????????????? Return _CreatedDate
??????????? End Get
??????????? Set(ByVal Value As Date)
??????????????? _CreatedDate = Value
??????????? End Set
??????? End Property
?
??????? Public Property Clicks() As Integer
??????????? Get
??????????????? Return _Clicks
??????????? End Get
??????????? Set(ByVal Value As Integer)
??????????????? _Clicks = Value
??????????? End Set
??????? End Property
??? End Class
?
每一個數據庫的字段在信息類里都有相對應的屬性。為了使通用的自定義業務對象助手(CBO Helper)類能自動的把從IDataReader接口獲取的數據轉換成自定義的業務對象,數據庫字段和與它直接關聯的屬性在名稱和數據類型方面都必須是唯一的。
?
?
??? Public Class AnnouncementsController
?
??????? Public Function GetAnnouncements(ByVal ModuleId As Integer) As ArrayList
?
??????????? Return CBO.FillCollection(DataProvider.Instance().GetAnnouncements(ModuleId), GetType(AnnouncementInfo))
?
??????? End Function
?
?
??????? Public Function GetAnnouncement(ByVal ItemId As Integer, ByVal ModuleId As Integer) As AnnouncementInfo
?
??????????? Return CType(CBO.FillObject(DataProvider.Instance().GetAnnouncement(ItemId, ModuleId), GetType(AnnouncementInfo)), AnnouncementInfo)
?
??????? End Function
?
?
??????? Public Sub DeleteAnnouncement(ByVal ItemID As Integer)
?
??????????? DataProvider.Instance().DeleteAnnouncement(ItemID)
?
??????? End Sub
?
??????? Public Sub AddAnnouncement(ByVal objAnnouncement As AnnouncementInfo)
?
?
??????????? DataProvider.Instance().AddAnnouncement(objAnnouncement.ModuleId, objAnnouncement.CreatedByUser, objAnnouncement.Title, objAnnouncement.Url, objAnnouncement.Syndicate, objAnnouncement.ExpireDate, objAnnouncement.Description, objAnnouncement.ViewOrder)
?
??????? End Sub
?
??????? Public Sub UpdateAnnouncement(ByVal objAnnouncement As AnnouncementInfo)
?
??????????? DataProvider.Instance().UpdateAnnouncement(objAnnouncement.ItemId, objAnnouncement.CreatedByUser, objAnnouncement.Title, objAnnouncement.Url, objAnnouncement.Syndicate, objAnnouncement.ExpireDate, objAnnouncement.Description, objAnnouncement.ViewOrder)
?
??????? End Sub
?
??? End Class
?
???? 你可能注意到了傳遞信息到數據庫的控制方法(例如:增加和更新)是傳遞一個自定義業務對象實例作為一個參數的。這樣做的優點是:對象定義從BLL層獨立出來,這樣就減少了類定義改變后相應的修改。個別的對象屬性被提取出來作為數量值傳遞給數據訪問層(這是因為DAL不關心BLL對象的結構)。
?
?
數據訪問層 ( DAL )
?
?????? 采用本文上述介紹的提供者技術DNN可以支持多種數據存儲。實際上它包含一個基類,這個基類在運行時決定哪個具體的數據訪問類適合當前的數據訪問請求。
?
?
DataProvider ( 基類 )
?
?? ?' announcements module
??? Public MustOverride Function GetAnnouncements(ByVal ModuleId As Integer) As IDataReader
??? Public MustOverride Function GetAnnouncement(ByVal ItemId As Integer, ByVal ModuleId As Integer) As IDataReader
??? Public MustOverride Sub DeleteAnnouncement(ByVal ItemID As Integer)
??? Public MustOverride Sub AddAnnouncement(ByVal ModuleId As Integer, ByVal UserName As String, ByVal Title As String, ByVal URL As String, ByVal Syndicate As Boolean, ByVal ExpireDate As Date, ByVal Description As String, ByVal ViewOrder As Integer)
??? Public MustOverride Sub UpdateAnnouncement(ByVal ItemId As Integer, ByVal UserName As String, ByVal Title As String, ByVal URL As String, ByVal Syndicate As Boolean, ByVal ExpireDate As Date, ByVal Description As String, ByVal ViewOrder As Integer)
?
SqlDataProvider (具體實現類)
?
在具體類中包含了下面的幫助方法,這個方法用來獨立數據庫null的實現(這個例子中DBNull.Value 是針對SQL Server而言的)并且提供一個簡單的接口。
?
?
??? ' general
??? Private Function GetNull(ByVal Field As Object) As Object
??????? Return Null.GetNull(Field, DBNull.Value)
??? End Function
?
每一個在基類里表明必須繼承的方法在具體類里都必須實現。注意上面的add/update方法里描述的GetNull()函數的使用。
?
??? ' announcements module
??? Public Overrides Function GetAnnouncements(ByVal ModuleId As Integer) As IDataReader
??????? Return CType(SqlHelper.ExecuteReader(ConnectionString, DatabaseOwner & ObjectQualifier & "GetAnnouncements", ModuleId), IDataReader)
??? End Function
??? Public Overrides Function GetAnnouncement(ByVal ItemId As Integer, ByVal ModuleId As Integer) As IDataReader
??????? Return CType(SqlHelper.ExecuteReader(ConnectionString, DatabaseOwner & ObjectQualifier & "GetAnnouncement", ItemId, ModuleId), IDataReader)
??? End Function
??? Public Overrides Sub DeleteAnnouncement(ByVal ItemId As Integer)
??????? SqlHelper.ExecuteNonQuery(ConnectionString, DatabaseOwner & ObjectQualifier & "DeleteAnnouncement", ItemId)
??? End Sub
??? Public Overrides Sub AddAnnouncement(ByVal ModuleId As Integer, ByVal UserName As String, ByVal Title As String, ByVal URL As String, ByVal Syndicate As Boolean, ByVal ExpireDate As Date, ByVal Description As String, ByVal ViewOrder As Integer)
??????? SqlHelper.ExecuteNonQuery(ConnectionString, DatabaseOwner & ObjectQualifier & "AddAnnouncement", ModuleId, UserName, Title, URL, Syndicate, GetNull(ExpireDate), Description, GetNull(ViewOrder))
??? End Sub
??? Public Overrides Sub UpdateAnnouncement(ByVal ItemId As Integer, ByVal UserName As String, ByVal Title As String, ByVal URL As String, ByVal Syndicate As Boolean, ByVal ExpireDate As Date, ByVal Description As String, ByVal ViewOrder As Integer)
??????? SqlHelper.ExecuteNonQuery(ConnectionString, DatabaseOwner & ObjectQualifier & "UpdateAnnouncement", ItemId, UserName, Title, URL, Syndicate, GetNull(ExpireDate), Description, GetNull(ViewOrder))
??? End Sub
緩存
?
?????? 很多數據訪問頻繁的方法采用了web緩存技術通過減少后臺數據庫請求次數的方法來提高性能。System.Web.Caching.Cache命名空間提供一些往緩存中增加內容或從緩存中重新獲取內容的工具。它包含一個字典接口,憑借一個字符串關鍵字來查找對應的對象。這個對象將持續在應用程序的整個生命周期。當應用程序重起后這個緩存才被重新創建。注意,只有序列化的對象才能被加入到緩存里。
?
?
??????????? Dim objCache As System.Web.Caching.Cache = System.Web.HttpContext.Current.Cache
?
??????????? If objCache("GetHostSettings") Is Nothing Then
??????????????? objCache.Insert("GetHostSettings", GetHostSettings())
??????????? End If
??????????? Me.HostSettings = objCache("GetHostSettings")
?
System.Web.Caching.Cache對象還支持緩存管理的一些特征。為防止緩存因為大量的內容項而臃腫不堪,DNN采用一個動態調整的方法來刪除那些超過60秒沒有被訪問的對象。這個特征主要用在有緩存設置的地方。
?
?
??????????? If objCache("GetPortalTabModules" & intTabId.ToString) Is Nothing Then
??????????????? dr = DataProvider.Instance().GetPortalTabModules(Me.PortalId, Me.ActiveTab.TabId)
??????????????? objCache.Insert("GetPortalTabModules" & intTabId.ToString, ConvertDataReaderToDataSet(dr), Nothing, DateTime.MaxValue, TimeSpan.FromSeconds(60))
??????????? End If
?
??????????? ds = objCache("GetPortalTabModules" & intTabId.ToString)
?
當應用程序的操作會影響緩存內容時,每一個受影響的緩存內容項都會被刪除,這樣新的內容項才能加進來。
?
?
??????????? If Not objCache("GetHostSettings") Is Nothing Then
??????????????? objCache.Remove("GetHostSettings")
??????????? End If
?
性能
?????? 為了評測系統的性能我們用了微軟應用程序評測中心(Microsoft Application Center Test)工具,這個工具可以模擬大量的用戶打開很多服務器連接并且快速的提交http請求。為了對比,我們分析了DotNetNuke1.0.10( 采用SqlCommandGenerator )跟DotNetNuke 2.0 ( 采用新的抽象DAL )。下面是測試結果(特別感謝Kenny Rice提供測試援助)。
?
?
DotNetNuke 2.0 ( DAL enhancement )
?
Total number of requests: ??????????????????????????? 93,254
Total number of connections: ?????????????????????? 93,253
?
Average requests per second: ??????????????????????????? 310.85
Average time to first byte (msecs): ???????????????????? 2.37
Average time to last byte (msecs): ???????????????????? 2.46
Average time to last byte per iteration (msecs): 29.58
?
Number of unique requests made in test: ?????????? 12
Number of unique response codes: ??????????????????? 1
?
DotNetNuke 1.0.10 ( SqlCommandGenerator )
?
Total number of requests:????????????????????? 42,350
Total number of connections:??????????????????????? 42,350
?????????????
Average requests per second:????????????????????? 141.17
Average time to first byte (msecs):??????????????? 6.02
Average time to last byte (msecs):??????????????? 6.15
Average time to last byte per iteration (msecs):? 116.94
?????????????
Number of unique requests made in test:??????????? 17
Number of unique response codes:?????????????? 2
開發
?????? DNN提供了靈活的門戶軟件架構。這個應用程序核心提供了大量的服務作為通用的方法,例如會員,角色安全,個性化,管理,站點log,導航和數據存取(訪問)。它還提供了靈活的擴展應用程序增加特殊業務功能的能力。大多數情況下建議將特殊的業務功能從框架核心抽象出來并用自定義模塊實現。這保持了核心的完成性并且為將來升級提供了最好的選擇。但是,如果你絕對必須修改核心實體的話,你也不會受到你的需求變化的限制。
?
自定義模塊
?????? DNN允許將自定義模塊打包成私有的程序集發布到門戶安裝。只要較小的修改,自定義模塊在數據存取方面可以采用相同的技術作為核心。這種方式的另一個優點是能夠為每一個支持的數據庫平臺提供自定義模塊的不同版本。
?????? 在你采用下面論述的數據訪問技術之前,你需要先考慮一下你的組件實際上是否需要支持多種數據庫。DNN不強制你采用提供者模式創建自定義模塊。事實上,如果你清楚你的組件僅僅用在單一的數據庫平臺,那么不需要增加額外的開發努力。開發者作這些決定(判斷)是一種基本的職責(原則)。
?
?
\PrivateAssemblies\Survey
?????? 概觀自定義模塊,它的架構其實跟DNN核心的架構是同樣的方法。它包含了一個叫做SurveyDB.vb的業務邏輯層的類,這個類包含了業務邏輯層的方法。它還包含了它自己的SurveyDataProvider.vb的類(文件名/類名很重要,因為你不想它跟DNN里的DataProvider類沖突)。這種情況下ProviderName常量被設置成上述的類名+ DataProvider字符串值的形式。這對于工廠方法里使用通過相同的配置集中得到的基于相同的數據庫的數據提供者很重要(例如:SurveySqlDataProvider 將會跟SqlDataProvider 使用相同的web.config配置集)。
?
?
Imports System
Imports System.Web.Caching
Imports System.Reflection
?
Namespace DotNetNuke
?
??? Public MustInherit Class SurveyDataProvider
?
??????? ' unique provider name used for caching
??????? Private Const ProviderType As String = "data"
??????? Private Const ProviderName As String = "Survey"
?
而且象DNN里的DataProvider類一樣,它包含了必須的數據存取方法。
?
?????? ?Public MustOverride Function GetSurveys(ByVal ModuleId As Integer) As IDataReader
??????? Public MustOverride Function GetSurvey(ByVal SurveyID As Integer, ByVal ModuleId As Integer) As IDataReader
??????? Public MustOverride Sub AddSurvey(ByVal ModuleId As Integer, ByVal Question As String, ByVal ViewOrder As String, ByVal OptionType As String, ByVal UserName As String)
??????? Public MustOverride Sub UpdateSurvey(ByVal SurveyId As Integer, ByVal Question As String, ByVal ViewOrder As String, ByVal OptionType As String, ByVal UserName As String)
??????? Public MustOverride Sub DeleteSurvey(ByVal SurveyID As Integer)
??????? Public MustOverride Function GetSurveyOptions(ByVal SurveyId As Integer) As IDataReader
??????? Public MustOverride Sub AddSurveyOption(ByVal SurveyId As Integer, ByVal OptionName As String, ByVal ViewOrder As String)
??????? Public MustOverride Sub UpdateSurveyOption(ByVal SurveyOptionId As Integer, ByVal OptionName As String, ByVal ViewOrder As String)
??????? Public MustOverride Sub DeleteSurveyOption(ByVal SurveyOptionID As Integer)
??????? Public MustOverride Sub AddSurveyResult(ByVal SurveyOptionId As Integer)
?
\database
?
?????? 自定義模塊包含數據提供者的實現,我們必須再一次在web.config文件里指定一個自定義屬性定義的實現。
?
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports Microsoft.ApplicationBlocks.Data
?
Namespace DotNetNuke.Data
?
??? Public Class SurveySqlDataProvider
?
??????? Inherits SurveyDataProvider
?
??????? 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
?
Survey.dnn ( deployment )
?
?????? 為了發布私有的自定義模塊,DNN用到了一個清單文件。增加多個提供者的時候這個文件的結構有些微的變化。
?
?
<module>
? <folder>DesktopModules/Survey</folder>
? <friendlyname>Survey</friendlyname>
? <desktopsrc>Survey.ascx</desktopsrc>
? <mobilesrc></mobilesrc>
? <editsrc>EditSurvey.ascx</editsrc>
? <description>Survey allows you to create custom surveys to obtain public feedback</description>
? <editmoduleicon>icon_survey_32px.gif</editmoduleicon>?
? <uninstall></uninstall>?
? <files>
??? <file>
????? <name>Survey.ascx</name>
??? </file>
??? <file>
????? <name>EditSurvey.ascx</name>
??? </file>
??? <file>
????? <name>Survey.dll</name>
??? </file>
??? <file>
????? <name>DotNetNuke.SurveySqlDataProvider.dll</name>
??? </file>
??? <file>
????? <name>Survey01.00.00.SqlDataProvider</name>
??? </file>
??? <file>
????? <name>Survey Uninstall.SqlDataProvider</name>
??? </file>
??? <file>
????? <name>DotNetNuke.SurveyAccessDataProvider.dll</name>
??? </file>
??? <file>
????? <name>Survey01.00.00.AccessDataProvider</name>
??? </file>
??? <file>
?? ???<name>Survey Uninstall.AccessDataProvider</name>
??? </file>
? </files>
</module>
?
改進核心模塊
?????? 自定義模塊是為門戶架構增加附加功能的首選方法。當然,有時候修改核心功能來滿足你特定的需求也是必需的。為了能修改核心模塊的數據訪問方法,你必須對控制管理提供者模型的面向對象原則有最基本的了解。
?????? 理論上,這個提供者模型采用一個基類派生出實例化的子類的工廠設計模式。事實上,DataProvider類就起這個基類的作用,它定義了所有應用程序的核心數據存取方法。所有的方法被定義成公共的必須繼承的,也就是說它們在基類中只是實簡單的定義而且都沒有實現。
?????? DataProvider類起到一個契約的作用,這個契約要求它的子類都必須完全實現,否則對象的實例化將會失敗。這個意思也就是說如果一個基類要求必須被繼承的方法的參數列表或返回值被修改,那么所有子類的實現也都必須作相應的修改,否則它們不會被正確的上載。不能正確的上載不僅意味著某個特殊方法的調用會失敗,實際上,子類的正確實例化也將徹底失敗。這個契約機制雖然給應用程序帶來一定的弱點,但是它保證了每個子類實現有了最小的標準。
?
?????? 下面的例子示范了有關擴展核心的步驟,假定我們增加一個新的字段到核心表中:
?
1、? 如果需要,修改表述層來顯示和修改新的字段。
2、? 修改相關聯的業務邏輯層的類,在相關方法中添加這個字段(例如AddTable, UpdateTable)。
3、? 修改DataProvider基類因步驟2中變化而帶來的必須的變化,并重新編譯應用程序。
4、? 為每一個DataProvider的子類實現(例如:SqlDataProvider,AccessDataProvider)做必要的修改,重新編譯會顯示基類跟其實現類之間的差異。需要修改的實現類的數量依賴于你的應用支持的不同數據庫的數量。
5、用特殊的數據庫更新命令(如:ALTER TABLE)修改每一個DataProvider子類實現的腳本。如果數據庫提供者用了存儲過程,那么同樣必須寫新版本的存儲過程(用相關的DROP 和CREATE 命令)。
?
SQL命令發生器(SqlCommandGenerator)
?????? 早期版本的DNN包含一個叫做SqlCommandGenerator的類,這個類能夠簡單的調用SQL Server / MSDE數據庫。然而它是反射每一個數據庫調用的,這帶來很嚴重的性能影響。這個類雖然仍然還保留著,但很明顯的我們鼓勵開發者采用DataProvider的模式。
?
?
參考
?????? 微軟ASP.NET 組的Rob Howard提供了大量的指導和DataProvider模型實現的示例代碼,.NET Pet Shop 3.0也提供了很多優秀的參考資料。Microsoft Data Access Application Block一個快速開發SQL Server Provider實現的一個有效的工具。
轉載于:https://www.cnblogs.com/Alaric/archive/2011/12/23/2299787.html
總結
以上是生活随笔為你收集整理的DNN 数据访问策略 (转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 缝纫机多少钱一台啊?
- 下一篇: 空调氟利昂多少钱啊?