DNN 数据访问策略 (转)
?
經(jīng)過幾天斷斷續(xù)續(xù)的努力,這篇文章終于翻譯結(jié)束,文章主要講了DNN的數(shù)據(jù)訪問策略,對(duì)于了解系統(tǒng)整體上是如何工作的有一定的幫助,希望能給dnn的初學(xué)者一些有用的信息。由于翻譯的匆忙+水平有限,錯(cuò)誤或不當(dāng)之處在所難免,歡迎大家討論、指正。
原作者:
Shaun Walker – Perpetual Motion Interactive Systems Inc.
http://www.perpetualmotion.ca
?
?
目錄
?
?
簡介... 2
策略... 2
需求... 3
配置... 4
數(shù)據(jù)訪問層 ( DAL ). 8
數(shù)據(jù)庫腳本... 11
數(shù)據(jù)庫對(duì)象命名... 12
應(yīng)用程序塊... 12
數(shù)據(jù)傳輸... 12
業(yè)務(wù)邏輯層 ( BLL ). 13
自定義業(yè)務(wù)對(duì)象助手 ( CBO ). 14
空處理機(jī)制... 16
實(shí)現(xiàn)細(xì)節(jié)... 19
緩存... 25
性能... 26
開發(fā)... 26
自定義模塊... 27
改進(jìn)核心模塊... 29
sql命令發(fā)生器... 30
參考... 30
?
?
?
?
?
?
?
簡介
?
DotNetNuke(以下簡稱DNN)的最終目的是創(chuàng)建一個(gè)門戶的框架平臺(tái),這個(gè)平臺(tái)可以為開發(fā)者增添模塊搭建應(yīng)用程序提供堅(jiān)實(shí)的可靠的支持。應(yīng)用程序的一個(gè)關(guān)鍵的功能就是數(shù)據(jù)存取。.NET Framework提供了多種數(shù)據(jù)存取的方法,從架構(gòu)的角度來看從這么多方法中選出適合自己的需求的最佳的解決方案很難。本白皮書將嘗試著在DNN應(yīng)用程序的實(shí)現(xiàn)中提供最合適的數(shù)據(jù)存取策略。
策略
?
?
在很多資料中有各種關(guān)于.NET Framework數(shù)據(jù)存取方法的介紹,然而他們大多脫離了現(xiàn)實(shí)的實(shí)際應(yīng)用。雖然大家常常討論的是這些方法的優(yōu)點(diǎn)和缺點(diǎn),但是現(xiàn)在仍有很多開發(fā)者不知道如何選擇自己的最佳策略。事實(shí)上每種方法都有適合它的不同的用例,理論上這是對(duì)的,這也是難以選擇的原因,然而在實(shí)踐中,每一個(gè)開發(fā)者都在尋找一個(gè)適合所有企業(yè)應(yīng)用的存取策略。
一致的數(shù)據(jù)存取策略有許多好處,有了統(tǒng)一定義好的數(shù)據(jù)存取策略,開發(fā)者就無需浪費(fèi)時(shí)間來為每個(gè)任務(wù)選擇數(shù)據(jù)存取方法,這種模式提高了代碼的維護(hù)性,在所有的應(yīng)用程序范圍內(nèi)實(shí)現(xiàn)了一致性。通過數(shù)據(jù)存取組件的集中處理使得數(shù)據(jù)存取策略風(fēng)險(xiǎn)降低了,也使得代碼的完整性增強(qiáng)了。
一致的兼容的數(shù)據(jù)存取策略的概念的確跟每個(gè)需求應(yīng)用其最佳的數(shù)據(jù)存取策略相悖。為每個(gè)應(yīng)用程序選擇相應(yīng)的最佳的數(shù)據(jù)存取策略能夠獲得最好的性能(假定你能夠從所有的用例中篩選出最適合的方案)??墒沁@樣可能導(dǎo)致團(tuán)隊(duì)在開發(fā)實(shí)踐中難以協(xié)調(diào)的合作。
DotNetNuke拋棄了眾所周知的傳統(tǒng)的80/20原則,它把精力集中在提供一致的兼容的數(shù)據(jù)存取策略,這個(gè)策略理想的目標(biāo)是把80%的精力放在應(yīng)用程序用例上,剩下的20%用來考慮跟用其他的數(shù)據(jù)存取方法相比是否性能要求是必須的,同時(shí)也采用了上面所述的策略。
?
?
?
?
需求
?
DNN的一個(gè)重要的需求就是要提供一個(gè)能夠支持多種數(shù)據(jù)存儲(chǔ)應(yīng)用程序的實(shí)現(xiàn)方法。
????? 由于對(duì)外部數(shù)據(jù)存儲(chǔ)通信的靈活性和性能的要求,我們選擇放棄一般的數(shù)據(jù)存取方法而打造一個(gè)新的應(yīng)用,這個(gè)新的應(yīng)用主要利用了數(shù)據(jù)庫本地化特征集(也就是用.NET管理提供者、所有的SQL語言、存儲(chǔ)過程等等)。在選擇特殊的數(shù)據(jù)庫訪問類時(shí)我們做了權(quán)衡,我們需要為我們想要支持的每個(gè)數(shù)據(jù)庫平臺(tái)寫一個(gè)特殊的數(shù)據(jù)訪問層,因此應(yīng)用程序也就包含了更多的代碼。數(shù)據(jù)訪問層共享了大量共同的代碼,每一個(gè)訪問層都明確的處理了特殊數(shù)據(jù)庫應(yīng)用。
????? 為了簡便的應(yīng)用數(shù)據(jù)庫訪問類我們選擇了提供者模式(也就是GOF描述的工廠設(shè)計(jì)模式),這種模式是通過反射的在應(yīng)用程序運(yùn)行時(shí)動(dòng)態(tài)的加載正確的(適合的)數(shù)據(jù)訪問對(duì)象。工廠是這樣實(shí)現(xiàn)的:先創(chuàng)建一個(gè)抽象類,這個(gè)類聲明了一個(gè)方法,這個(gè)方法是每一個(gè)數(shù)據(jù)訪問類都必須繼承實(shí)現(xiàn)的。對(duì)每一個(gè)我們支持的數(shù)據(jù)庫,我們創(chuàng)建了一個(gè)具體的實(shí)現(xiàn)類,這個(gè)類實(shí)現(xiàn)了抽象類或“協(xié)議”中定義的各種數(shù)據(jù)庫操作的代碼。為了支持在運(yùn)行時(shí)動(dòng)態(tài)加載具體的操作類,我們?cè)诠S里實(shí)現(xiàn)了一個(gè)Instance()方法,這個(gè)方法依賴于提供者類從配置文件讀取并反射過來的值來決定需要加載哪個(gè)程序集。由于反射在應(yīng)用程序性能方面非常耗費(fèi)資源,我們把數(shù)據(jù)庫提供者類的構(gòu)造器儲(chǔ)存到緩存里。
????? ?那么為什么用抽象類而不用接口呢?這是因?yàn)榻涌谑?#xff08;不可變的)靜態(tài)的,而且因此接口也不能將其自身復(fù)制(翻譯)。由于接口不支持實(shí)現(xiàn)方法繼續(xù)繼承,所以這些類的模式不采用接口。為接口增加一個(gè)方法跟為基類增加一個(gè)抽象方式是等效的;任何類實(shí)現(xiàn)了接口它也就終止了,因?yàn)檫@些類不能再實(shí)現(xiàn)新的方法(確切的說應(yīng)該是接口定義以外的方法接口無法使用)。
????? 下面的圖表展示了業(yè)務(wù)邏輯、工廠以及數(shù)據(jù)庫存取類是如何相互聯(lián)系的。這個(gè)解決方案的關(guān)鍵優(yōu)勢(shì)是只要數(shù)據(jù)訪問類實(shí)現(xiàn)了DataProvider抽象類的方法,數(shù)據(jù)庫訪問類就能夠在業(yè)務(wù)邏輯類之后編譯。這就意味著在我們想要?jiǎng)?chuàng)建另一個(gè)數(shù)據(jù)庫的實(shí)現(xiàn)方法時(shí)我們無需改變業(yè)務(wù)邏輯層(或用戶界面層),創(chuàng)建另一個(gè)實(shí)現(xiàn)方法的步驟是:
1、? 為新的數(shù)據(jù)庫創(chuàng)建數(shù)據(jù)庫訪問類,這些類實(shí)現(xiàn)了DataProvider的抽象類。
2、? 將這些類類編譯成一個(gè)程序集。
3、? 測(cè)試并配置這個(gè)新的數(shù)據(jù)訪問程序集到正在運(yùn)行的服務(wù)器。
4、? 修改配置文件,來指定新的數(shù)據(jù)庫訪問類。
5、? 無需對(duì)業(yè)務(wù)邏輯組件進(jìn)行任何改變也無需重新編譯它。
?
?
?
?
?
配置
?
?????? Web.config文件包含了許多配置節(jié)來使DataProvider模式起作用。第一個(gè)配置節(jié)注冊(cè)了這些提供者(Providers)和他們相應(yīng)的配置節(jié)處理方法(ConfigurationSectionHandlers)。盡管在這個(gè)例子中我們僅僅展示了DotNetNuke配置節(jié)組中的一個(gè),我們可以通過類似的方法配置其它的提供者(也就是抽象驗(yàn)證提供者等等)。但有一點(diǎn)是必須保證的,那就是web.config文件中必須實(shí)現(xiàn)這些配置節(jié)。
?
??? <configSections>
??????? <sectionGroup name="dotnetnuke">
??????????? <section name="data" type="DotNetNuke.ProviderConfigurationHandler, DotNetNuke" />
??????? </sectionGroup>
??? </configSections>
?
下面的配置節(jié)是為老式的數(shù)據(jù)訪問方法的模塊而保留的:
??? <appSettings>
??????? <add key="connectionString" value="Server=localhost;Database=DotNetNuke;uid=sa;pwd=;" />
??? </appSettings>
?
如上最終實(shí)現(xiàn)了大量的提供者模塊。<dotnetnuke>配置組中的<data>配置節(jié)名稱需要有一個(gè)默認(rèn)的提供者(defaultProvider)屬性,這個(gè)屬性依賴于下面的<providers>集合中的特定的實(shí)例。在從一個(gè)provider轉(zhuǎn)變?yōu)榱硪粋€(gè)provider的時(shí)候defaultProvider被用來作為單一的轉(zhuǎn)換器。如果沒有指定默認(rèn)的provider,這個(gè)配置集合中的第一項(xiàng)就被認(rèn)為是默認(rèn)的。
?????? <data>配置節(jié)也包含了一個(gè)<providers>集合說明,確定了所有的<data>實(shí)現(xiàn)都是唯一的。每一個(gè)provider都必須至少包含name、type和providerPath屬性(name不是特定的但是通常相應(yīng)的類名,type指定了provider相關(guān)的強(qiáng)類名,providerPath指定了provider的特殊資源例如腳本在哪里)。每一個(gè)提供者同樣可以有多個(gè)自定義屬性。
?
?
??? <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>
?
下面是對(duì)providers集合中的節(jié)點(diǎn)作用的詳細(xì)說明。
<providers>配置節(jié)包含了一個(gè)或多個(gè)<add>、<remove>、<clear>元素。下面的規(guī)則應(yīng)用在處理這些元素的時(shí)候:
?
?
1、? 聲明一個(gè)空的<providers />不是錯(cuò)誤。
2、? Providers繼承了父配置中<add>聲明的項(xiàng)。
3、? 如果某項(xiàng)已經(jīng)存在了或被繼承了再用<add>重新定義那么這是錯(cuò)誤的。
4、? <remove>一個(gè)不存在的項(xiàng)是錯(cuò)誤的。
5、? 如果一個(gè)項(xiàng)被<add>后又被<remove>了,然后再<add>這個(gè)完全相同的項(xiàng)是可以的(不是錯(cuò)誤)。
6、? 如果一個(gè)項(xiàng)<add>, <clear>,然后再<add>是可以的(不是錯(cuò)誤的)。
7、? <clear>會(huì)清除所有在先前定義的和繼承的項(xiàng)。例如:先用<add>聲明再用<clear>清除那么項(xiàng)就不存在了,而在<clear>后再<add>聲明的項(xiàng)是不會(huì)被清除的。
?
| <add> | |
| 描述 | 增加一個(gè)數(shù)據(jù)提供者(data provider)。 |
| 屬性 | Name——provider的友好的名稱。 Type——一個(gè)實(shí)現(xiàn)了provider接口的類。這個(gè)值是一個(gè)程序集的完整的關(guān)聯(lián)。 providerPath——查找provider的特殊資源(如腳本)的路徑。 其它name/value對(duì)——也許還有一些附加的名稱/值對(duì),所有的名稱/值對(duì)都是provider能夠理解的(處理的)。 |
?
| <remove> | |
| 描述 | 清除一個(gè)指定的數(shù)據(jù)提供者 |
| 屬性 | Name——要清除的provider的友好名稱。 |
?
?
| <clear> | |
| 描述 | 清除所有的繼承的提供者。 |
?
\Components\Provider.vb
?
Provider.vb類提供了所有的實(shí)現(xiàn)細(xì)節(jié),這些實(shí)現(xiàn)包括從web.config文件加載provider的信息以及應(yīng)用<add>, <remove>, <clear>處理的規(guī)則(標(biāo)準(zhǔn))。這是一個(gè)通用的(generic)類,它不僅僅適用于數(shù)據(jù)訪問。
?
\Components\DataProvider.vb
?
DataProvider.vb是一個(gè)抽象類,這個(gè)類包含了DNN的所有的數(shù)據(jù)訪問方法。它包含了一個(gè)工廠本身的實(shí)例方法(Instance()),它負(fù)責(zé)在運(yùn)行時(shí)動(dòng)態(tài)加載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
?
?
?????? 所有的數(shù)據(jù)訪問方法都被定義成必須重寫的(MustOverride)。也就是說所有的從詞類派生的數(shù)據(jù)提供者類都必須提供這些方法的實(shí)現(xiàn)。這些定義了業(yè)務(wù)邏輯層和數(shù)據(jù)存取層之間聯(lián)系的抽象類協(xié)議。
?
??????? ' 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)
數(shù)據(jù)訪問層( DAL )
?
數(shù)據(jù)訪問層(DAL)必須實(shí)現(xiàn)數(shù)據(jù)提供者抽象類中聲明的方法。然而,每一個(gè)DAL提供者在實(shí)現(xiàn)這些方法時(shí)也許很不相同。這種處理允許提供者靈活的選擇他們自己的數(shù)據(jù)庫訪問協(xié)議(也就是說:.NET管理的OleDB, ODBC等)。同樣也允許提供者處理數(shù)據(jù)庫平臺(tái)之間的所有不同之處(例如:存儲(chǔ)過程,sql語言語法,@@IDENTITY)。
每一個(gè)數(shù)據(jù)提供者必須為其在web.config中的自定義屬性指定一個(gè)實(shí)現(xiàn)方法。
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
?
數(shù)據(jù)訪問方法必須涉及成簡單的查詢(例如 單一的select,insert,update,delete),以便于在所有的數(shù)據(jù)庫平臺(tái)上他們都能被實(shí)現(xiàn)。業(yè)務(wù)邏輯(例如條件分支,計(jì)算或局部變量)應(yīng)該在業(yè)務(wù)邏輯層實(shí)現(xiàn),這樣才能從數(shù)據(jù)庫抽象出來并集中到一個(gè)應(yīng)用程序(模塊)里處理。如果你經(jīng)常用那些使你可以在數(shù)據(jù)庫層實(shí)現(xiàn)程序邏輯的富sql語言變量工作的話,這種數(shù)據(jù)庫訪問是相當(dāng)簡單的。
DNN中的Sql server/msde 數(shù)據(jù)提供者用了存儲(chǔ)過程作為最好的數(shù)據(jù)訪問技術(shù)。
?
??????? ' 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用到存儲(chǔ)過程(存儲(chǔ)過程查詢)但是沒有用參數(shù)自動(dòng)查找的特性( CommandBuilder.DeriveParameters ),因此參數(shù)必須明確的定義。
?
??????? ' 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
?
數(shù)據(jù)庫腳本
?
?????? DNN包含了一個(gè)自動(dòng)升級(jí)的特性,也就是當(dāng)有了一個(gè)新的應(yīng)用程序版本發(fā)布后,應(yīng)用程序能夠自動(dòng)更新數(shù)據(jù)庫。不過腳本必須根據(jù)版本號(hào)和數(shù)據(jù)提供者命名(例如02.00.00.SqlDataProvider),并且必須放在web.config文件中providerPath屬性所指定的目錄下。動(dòng)態(tài)升級(jí)的實(shí)現(xiàn)需要腳本中重寫provider所實(shí)現(xiàn)的ExecuteScript的方法。這對(duì)安全規(guī)范和對(duì)象命名很有用。
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
?
數(shù)據(jù)庫對(duì)象命名??
?????? web.config文件包含了一個(gè)名字叫objectQualifer的屬性,它允許你為數(shù)據(jù)庫對(duì)象指定一個(gè)前綴(例如DNN_)。Web主機(jī)往往只提供一個(gè)數(shù)據(jù)庫服務(wù)器,因此必須跟其它web應(yīng)用程序共享一個(gè)賬號(hào)。如果你沒有指定前綴也許會(huì)跟已經(jīng)存在的其它應(yīng)用程序的數(shù)據(jù)庫對(duì)象名稱沖突。指定前綴的另一個(gè)好處就是這些數(shù)據(jù)庫對(duì)象在SQL Server企業(yè)管理器等管理工具里當(dāng)按字母排序的時(shí)候他們會(huì)分組顯示,這就方便了管理。
?????? 如果你升級(jí)的是DotNetNuke2.0以前版本的數(shù)據(jù)庫,你需要設(shè)置objectQualifier為“”。這是因?yàn)槟阋苍S用了第三方模塊,而這些模塊不用新的DAL體系架構(gòu)而是用特定的對(duì)象名稱。升級(jí)設(shè)置objectQualifier時(shí)會(huì)將你所有的核心的數(shù)據(jù)庫對(duì)象重命名,這可能會(huì)使你的自定義的模塊出錯(cuò)。
?
應(yīng)用程序塊
?
?????? 微軟數(shù)據(jù)訪問應(yīng)用程序塊(MSDAAB)是一個(gè).NET組件,它包含了最優(yōu)化的數(shù)據(jù)訪問代碼,它能幫助你依靠SQL Server數(shù)據(jù)庫調(diào)用存儲(chǔ)過程、發(fā)布sql文本命令。我們借用這些方法作為dnn的一個(gè)建造模塊,來減少創(chuàng)建、測(cè)試和維護(hù)大量自定義的數(shù)據(jù)訪問方法的代碼。我們也為基于MSDAAB代碼的微軟Access數(shù)據(jù)提供者創(chuàng)建了一個(gè)OleDB.ApplicationBlocks.Data程序集。
?????? 比起在我們的DAL實(shí)現(xiàn)方法里包含真正的資源代碼來說,我們選擇用MSDAAB作為一個(gè)黑箱組件的實(shí)現(xiàn)方法能夠幫助我們防止修改MSDAAB代碼,能夠使我們完美的升級(jí)組件,這個(gè)新特征是可以實(shí)現(xiàn)的。
?
數(shù)據(jù)傳輸
?????? DotNetNuke用DataReader把從數(shù)據(jù)訪問層(DAL)那里讀取的數(shù)據(jù)集合傳遞給業(yè)務(wù)邏輯層(BLL)。選用DataReader是因?yàn)樗茿DO.NET提供的租塊的數(shù)據(jù)傳輸機(jī)制(一個(gè)只向前的只讀的數(shù)據(jù)流)。IDataReader是所有.NET兼容的數(shù)據(jù)讀取器(DataReaders)的基本接口。這個(gè)抽象的IDataReader接口使我們能夠在各層之間傳輸數(shù)據(jù),而無需考慮數(shù)據(jù)訪問協(xié)議在實(shí)際的數(shù)據(jù)提供者實(shí)現(xiàn)中可不可用(例如SqlClient, OleDB, ODBC等)。
?
業(yè)務(wù)邏輯層 ( BLL )
?????? 好的面向?qū)ο笤O(shè)計(jì)推薦我們將數(shù)據(jù)存儲(chǔ)從應(yīng)用程序中提取(抽象)出來。通過提取可以在應(yīng)用程序上建立一個(gè)獨(dú)立的業(yè)務(wù)邏輯接口集;因此減少了對(duì)下面的數(shù)據(jù)庫物理實(shí)現(xiàn)的依賴。
?????? DNN的業(yè)務(wù)邏輯層被有效的定義在\Components的子文件夾下。業(yè)務(wù)邏輯層包含了表述層調(diào)用的各種應(yīng)用程序服務(wù)的抽象類。在數(shù)據(jù)訪問方面,業(yè)務(wù)邏輯層向前調(diào)用應(yīng)用程序接口(API)到適當(dāng)?shù)臄?shù)據(jù)提供者,這個(gè)過程就文檔前面介紹的數(shù)據(jù)提供者工廠機(jī)制。
?????? 自定義業(yè)務(wù)對(duì)象是一個(gè)用自定義的數(shù)據(jù)結(jié)構(gòu)封裝數(shù)據(jù)的面向?qū)ο蟮募夹g(shù)。自定義業(yè)務(wù)對(duì)象需要一些自定義的代碼,這就在類型安全設(shè)計(jì)模型、分離數(shù)據(jù)存儲(chǔ)和序列化方面增加了開銷。自定義業(yè)務(wù)對(duì)象提供了最大程度的靈活性,他們使應(yīng)用程序可以在它的抽象范圍內(nèi)定義自己的數(shù)據(jù)結(jié)構(gòu);也消除了對(duì)所有數(shù)據(jù)容器的依賴(例如RecordSet, DataSet )。
?????? 什么是類型安全的設(shè)計(jì)模型呢?考慮一下下面的數(shù)據(jù)訪問代碼例子:變量= DataReader(“字段名”)。這里將數(shù)據(jù)庫字段的值賦給了一個(gè)變量,這段代碼的問題在于無法保證數(shù)據(jù)庫字段的數(shù)據(jù)類型和變量的數(shù)據(jù)類型相匹配;并且這個(gè)過程中的任何錯(cuò)誤都將提交給運(yùn)行時(shí)(run-time)來處理。采用自定義業(yè)務(wù)對(duì)象那么代碼將是這樣:變量=對(duì)象.屬性(variable = Object.Property)。這樣的話編譯器就會(huì)在數(shù)據(jù)不匹配的時(shí)候迅速的告知我們。類型安全的設(shè)計(jì)還可以智能感知和改進(jìn)代碼的易讀性。
?????? 一組對(duì)象我們成為一個(gè)集合。在DNN里我們用了標(biāo)準(zhǔn)的動(dòng)態(tài)數(shù)組(ArrayList)來描述一組自定義業(yè)務(wù)對(duì)象。ArrayLists是ASP.NET內(nèi)部對(duì)象,它包含了基本集合所需要的所有特征(add,remove,find,iterate)。在ArrayList的特征里我們最重要的特征是它實(shí)現(xiàn)了IEnumerable接口,它可以被數(shù)據(jù)綁定(databound)給ASP.NET web控件(web control)。
?????? DNN的數(shù)據(jù)訪問層是以DataReader的形式傳遞信息給業(yè)務(wù)邏輯層的。關(guān)于這種實(shí)現(xiàn)的一個(gè)問題就是為什么DNN用DataReader作為數(shù)據(jù)傳輸容器(container)來傳輸數(shù)據(jù)而不直接從DAL層傳遞數(shù)據(jù)給自定義業(yè)務(wù)對(duì)象。這是因?yàn)殡m然這兩種方法都是可行的,但我們相信使DAL層從BLL層完全獨(dú)立出來是由一些優(yōu)點(diǎn)的。例如:我們要為自定義業(yè)務(wù)對(duì)象增加一個(gè)附加屬性,這種情況下這個(gè)屬性僅僅是表述層用到而數(shù)據(jù)庫中根本不需要,用DNN的方法,DAL層實(shí)現(xiàn)方法不需要做任何改動(dòng),因?yàn)樗麄儗?duì)上面的BLL層沒有任何依賴;但是如果DAL直接提供數(shù)據(jù)給自定義業(yè)務(wù)對(duì)象,所有的DAL層實(shí)現(xiàn)都需要重新編譯來符合BLL層結(jié)構(gòu)的需要。
?
?
?
?
自定義業(yè)務(wù)對(duì)象助手 ( CBO )
?????? 為了最小化移植從數(shù)據(jù)層傳輸來到自定義業(yè)務(wù)邏輯對(duì)象信息的代碼工作量,創(chuàng)建了一個(gè)通用的utility類。這個(gè)類包含了兩個(gè)公共的方法(函數(shù))——一個(gè)是返回一個(gè)單獨(dú)對(duì)象的實(shí)例,一個(gè)是返回一個(gè)集合對(duì)象(arraylist)。一般來說這個(gè)類里定義的每個(gè)屬性在Datareader里都有相應(yīng)的字段對(duì)應(yīng)。這些影射的信息的名稱和數(shù)據(jù)類型都必須是唯一的。下面的代碼展示了如何用反射將datareader里的數(shù)據(jù)填充給自定義業(yè)務(wù)對(duì)象然后再關(guān)閉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
空處理
?
?????? 每一個(gè)數(shù)據(jù)存取系統(tǒng)都有一個(gè)特殊的構(gòu)造來處理那些沒有明確指定的字段值。
在大多數(shù)關(guān)系數(shù)據(jù)庫管理系統(tǒng)中,這個(gè)構(gòu)造就是眾所周知的null值。從應(yīng)用程序的角度看,在表述層和數(shù)據(jù)存取層傳遞null值是一個(gè)架構(gòu)上的挑戰(zhàn)。這是因?yàn)楸硎鰧颖仨殢臄?shù)據(jù)庫的特定信息抽象出來;而且,當(dāng)一個(gè)屬性值沒有明確指定的時(shí)候表述層也必須能夠表達(dá)說明。事實(shí)上這相當(dāng)復(fù)雜,.NET Framework的本身的數(shù)據(jù)類型不能自動(dòng)的轉(zhuǎn)換從數(shù)據(jù)庫返回的null值(如果你試圖直接那樣賦值的話將會(huì)拋出一個(gè)異常)。另外,每一個(gè)數(shù)據(jù)存儲(chǔ)都有它自己的屬性來實(shí)現(xiàn)null。唯一合理的解決方案就是創(chuàng)建一個(gè)抽象的傳輸服務(wù),來編碼/解碼應(yīng)用程序各層之間的null值。
?
?????? 乍一看,你也許會(huì)想到用vb.net中的“nothing”關(guān)鍵字可以很好的擔(dān)負(fù)起這個(gè)傳輸服務(wù)的任務(wù)。不幸的是,調(diào)查顯示,.NET Framework本身的數(shù)據(jù)類型處理“nothing”的時(shí)候沒有預(yù)想的那么好。盡管分配為nothing的屬性不會(huì)拋出異常,實(shí)際上這個(gè)屬性的值將非常依賴于它的數(shù)據(jù)類型(String = Nothing, Date = Date.MinValue, Integer = 0, Boolean = False, 等等)并且自帶的IsNothing()函數(shù)的結(jié)果還不是一致的(兼容的)結(jié)果。
?
?????? 在DNN里,我們創(chuàng)建了一個(gè)通用的類來處理null的問題,它統(tǒng)一管理應(yīng)用程序各層的null問題。在應(yīng)用程序中用一個(gè)常量來描述每種數(shù)據(jù)類型的null情況,再把這個(gè)常量轉(zhuǎn)化成各種數(shù)據(jù)存儲(chǔ)實(shí)現(xiàn)里的實(shí)際的null值。這個(gè)類包含的各種方法將null轉(zhuǎn)換服務(wù)的物理細(xì)節(jié)從應(yīng)用程序中抽象出來了。
?????? * 記住,這個(gè)類僅僅用在數(shù)據(jù)庫字段允許有null值的情況下。還要記住,這個(gè)類要求DAL和BLL層之間的數(shù)據(jù)類型一致(例如:一個(gè)BLL信息類里的屬性字段的數(shù)據(jù)類型必須跟DAL 數(shù)據(jù)提供者傳遞過來的參數(shù)的數(shù)據(jù)類型一致)。
?
?
??? 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
實(shí)現(xiàn)細(xì)節(jié)
?????? 下面的一段代碼例子示范了應(yīng)用程序的各層之間是如何完成數(shù)據(jù)訪問的。
?
表述層( UI )
?????? 表述層依賴于它上面的業(yè)務(wù)邏輯層。自定義業(yè)務(wù)邏輯對(duì)象的屬性和方法建立了這兩個(gè)曾之間的基礎(chǔ)接口(表述層不要直接調(diào)用數(shù)據(jù)訪問層的方法)。
?
?
獲取
?
?????? ' 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)
?
業(yè)務(wù)邏輯層( BLL )
?
?????? 每一個(gè)應(yīng)用程序業(yè)務(wù)方法都有跟它相對(duì)應(yīng)的多關(guān)系業(yè)務(wù)對(duì)象組成的物理文件。每一個(gè)業(yè)務(wù)對(duì)象定義都有一個(gè)定義它的屬性的信息類和定義它的方法的控制類。
?
??? 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
?
每一個(gè)數(shù)據(jù)庫的字段在信息類里都有相對(duì)應(yīng)的屬性。為了使通用的自定義業(yè)務(wù)對(duì)象助手(CBO Helper)類能自動(dòng)的把從IDataReader接口獲取的數(shù)據(jù)轉(zhuǎn)換成自定義的業(yè)務(wù)對(duì)象,數(shù)據(jù)庫字段和與它直接關(guān)聯(lián)的屬性在名稱和數(shù)據(jù)類型方面都必須是唯一的。
?
?
??? 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
?
???? 你可能注意到了傳遞信息到數(shù)據(jù)庫的控制方法(例如:增加和更新)是傳遞一個(gè)自定義業(yè)務(wù)對(duì)象實(shí)例作為一個(gè)參數(shù)的。這樣做的優(yōu)點(diǎn)是:對(duì)象定義從BLL層獨(dú)立出來,這樣就減少了類定義改變后相應(yīng)的修改。個(gè)別的對(duì)象屬性被提取出來作為數(shù)量值傳遞給數(shù)據(jù)訪問層(這是因?yàn)镈AL不關(guān)心BLL對(duì)象的結(jié)構(gòu))。
?
?
數(shù)據(jù)訪問層 ( DAL )
?
?????? 采用本文上述介紹的提供者技術(shù)DNN可以支持多種數(shù)據(jù)存儲(chǔ)。實(shí)際上它包含一個(gè)基類,這個(gè)基類在運(yùn)行時(shí)決定哪個(gè)具體的數(shù)據(jù)訪問類適合當(dāng)前的數(shù)據(jù)訪問請(qǐng)求。
?
?
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 (具體實(shí)現(xiàn)類)
?
在具體類中包含了下面的幫助方法,這個(gè)方法用來獨(dú)立數(shù)據(jù)庫null的實(shí)現(xiàn)(這個(gè)例子中DBNull.Value 是針對(duì)SQL Server而言的)并且提供一個(gè)簡單的接口。
?
?
??? ' general
??? Private Function GetNull(ByVal Field As Object) As Object
??????? Return Null.GetNull(Field, DBNull.Value)
??? End Function
?
每一個(gè)在基類里表明必須繼承的方法在具體類里都必須實(shí)現(xiàn)。注意上面的add/update方法里描述的GetNull()函數(shù)的使用。
?
??? ' 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
緩存
?
?????? 很多數(shù)據(jù)訪問頻繁的方法采用了web緩存技術(shù)通過減少后臺(tái)數(shù)據(jù)庫請(qǐng)求次數(shù)的方法來提高性能。System.Web.Caching.Cache命名空間提供一些往緩存中增加內(nèi)容或從緩存中重新獲取內(nèi)容的工具。它包含一個(gè)字典接口,憑借一個(gè)字符串關(guān)鍵字來查找對(duì)應(yīng)的對(duì)象。這個(gè)對(duì)象將持續(xù)在應(yīng)用程序的整個(gè)生命周期。當(dāng)應(yīng)用程序重起后這個(gè)緩存才被重新創(chuàng)建。注意,只有序列化的對(duì)象才能被加入到緩存里。
?
?
??????????? 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對(duì)象還支持緩存管理的一些特征。為防止緩存因?yàn)榇罅康膬?nèi)容項(xiàng)而臃腫不堪,DNN采用一個(gè)動(dòng)態(tài)調(diào)整的方法來刪除那些超過60秒沒有被訪問的對(duì)象。這個(gè)特征主要用在有緩存設(shè)置的地方。
?
?
??????????? 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)
?
當(dāng)應(yīng)用程序的操作會(huì)影響緩存內(nèi)容時(shí),每一個(gè)受影響的緩存內(nèi)容項(xiàng)都會(huì)被刪除,這樣新的內(nèi)容項(xiàng)才能加進(jìn)來。
?
?
??????????? If Not objCache("GetHostSettings") Is Nothing Then
??????????????? objCache.Remove("GetHostSettings")
??????????? End If
?
性能
?????? 為了評(píng)測(cè)系統(tǒng)的性能我們用了微軟應(yīng)用程序評(píng)測(cè)中心(Microsoft Application Center Test)工具,這個(gè)工具可以模擬大量的用戶打開很多服務(wù)器連接并且快速的提交http請(qǐng)求。為了對(duì)比,我們分析了DotNetNuke1.0.10( 采用SqlCommandGenerator )跟DotNetNuke 2.0 ( 采用新的抽象DAL )。下面是測(cè)試結(jié)果(特別感謝Kenny Rice提供測(cè)試援助)。
?
?
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
開發(fā)
?????? DNN提供了靈活的門戶軟件架構(gòu)。這個(gè)應(yīng)用程序核心提供了大量的服務(wù)作為通用的方法,例如會(huì)員,角色安全,個(gè)性化,管理,站點(diǎn)log,導(dǎo)航和數(shù)據(jù)存取(訪問)。它還提供了靈活的擴(kuò)展應(yīng)用程序增加特殊業(yè)務(wù)功能的能力。大多數(shù)情況下建議將特殊的業(yè)務(wù)功能從框架核心抽象出來并用自定義模塊實(shí)現(xiàn)。這保持了核心的完成性并且為將來升級(jí)提供了最好的選擇。但是,如果你絕對(duì)必須修改核心實(shí)體的話,你也不會(huì)受到你的需求變化的限制。
?
自定義模塊
?????? DNN允許將自定義模塊打包成私有的程序集發(fā)布到門戶安裝。只要較小的修改,自定義模塊在數(shù)據(jù)存取方面可以采用相同的技術(shù)作為核心。這種方式的另一個(gè)優(yōu)點(diǎn)是能夠?yàn)槊恳粋€(gè)支持的數(shù)據(jù)庫平臺(tái)提供自定義模塊的不同版本。
?????? 在你采用下面論述的數(shù)據(jù)訪問技術(shù)之前,你需要先考慮一下你的組件實(shí)際上是否需要支持多種數(shù)據(jù)庫。DNN不強(qiáng)制你采用提供者模式創(chuàng)建自定義模塊。事實(shí)上,如果你清楚你的組件僅僅用在單一的數(shù)據(jù)庫平臺(tái),那么不需要增加額外的開發(fā)努力。開發(fā)者作這些決定(判斷)是一種基本的職責(zé)(原則)。
?
?
\PrivateAssemblies\Survey
?????? 概觀自定義模塊,它的架構(gòu)其實(shí)跟DNN核心的架構(gòu)是同樣的方法。它包含了一個(gè)叫做SurveyDB.vb的業(yè)務(wù)邏輯層的類,這個(gè)類包含了業(yè)務(wù)邏輯層的方法。它還包含了它自己的SurveyDataProvider.vb的類(文件名/類名很重要,因?yàn)槟悴幌胨鶧NN里的DataProvider類沖突)。這種情況下ProviderName常量被設(shè)置成上述的類名+ DataProvider字符串值的形式。這對(duì)于工廠方法里使用通過相同的配置集中得到的基于相同的數(shù)據(jù)庫的數(shù)據(jù)提供者很重要(例如:SurveySqlDataProvider 將會(huì)跟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類一樣,它包含了必須的數(shù)據(jù)存取方法。
?
?????? ?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
?
?????? 自定義模塊包含數(shù)據(jù)提供者的實(shí)現(xiàn),我們必須再一次在web.config文件里指定一個(gè)自定義屬性定義的實(shí)現(xiàn)。
?
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 )
?
?????? 為了發(fā)布私有的自定義模塊,DNN用到了一個(gè)清單文件。增加多個(gè)提供者的時(shí)候這個(gè)文件的結(jié)構(gòu)有些微的變化。
?
?
<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>
?
改進(jìn)核心模塊
?????? 自定義模塊是為門戶架構(gòu)增加附加功能的首選方法。當(dāng)然,有時(shí)候修改核心功能來滿足你特定的需求也是必需的。為了能修改核心模塊的數(shù)據(jù)訪問方法,你必須對(duì)控制管理提供者模型的面向?qū)ο笤瓌t有最基本的了解。
?????? 理論上,這個(gè)提供者模型采用一個(gè)基類派生出實(shí)例化的子類的工廠設(shè)計(jì)模式。事實(shí)上,DataProvider類就起這個(gè)基類的作用,它定義了所有應(yīng)用程序的核心數(shù)據(jù)存取方法。所有的方法被定義成公共的必須繼承的,也就是說它們?cè)诨愔兄皇菍?shí)簡單的定義而且都沒有實(shí)現(xiàn)。
?????? DataProvider類起到一個(gè)契約的作用,這個(gè)契約要求它的子類都必須完全實(shí)現(xiàn),否則對(duì)象的實(shí)例化將會(huì)失敗。這個(gè)意思也就是說如果一個(gè)基類要求必須被繼承的方法的參數(shù)列表或返回值被修改,那么所有子類的實(shí)現(xiàn)也都必須作相應(yīng)的修改,否則它們不會(huì)被正確的上載。不能正確的上載不僅意味著某個(gè)特殊方法的調(diào)用會(huì)失敗,實(shí)際上,子類的正確實(shí)例化也將徹底失敗。這個(gè)契約機(jī)制雖然給應(yīng)用程序帶來一定的弱點(diǎn),但是它保證了每個(gè)子類實(shí)現(xiàn)有了最小的標(biāo)準(zhǔn)。
?
?????? 下面的例子示范了有關(guān)擴(kuò)展核心的步驟,假定我們?cè)黾右粋€(gè)新的字段到核心表中:
?
1、? 如果需要,修改表述層來顯示和修改新的字段。
2、? 修改相關(guān)聯(lián)的業(yè)務(wù)邏輯層的類,在相關(guān)方法中添加這個(gè)字段(例如AddTable, UpdateTable)。
3、? 修改DataProvider基類因步驟2中變化而帶來的必須的變化,并重新編譯應(yīng)用程序。
4、? 為每一個(gè)DataProvider的子類實(shí)現(xiàn)(例如:SqlDataProvider,AccessDataProvider)做必要的修改,重新編譯會(huì)顯示基類跟其實(shí)現(xiàn)類之間的差異。需要修改的實(shí)現(xiàn)類的數(shù)量依賴于你的應(yīng)用支持的不同數(shù)據(jù)庫的數(shù)量。
5、用特殊的數(shù)據(jù)庫更新命令(如:ALTER TABLE)修改每一個(gè)DataProvider子類實(shí)現(xiàn)的腳本。如果數(shù)據(jù)庫提供者用了存儲(chǔ)過程,那么同樣必須寫新版本的存儲(chǔ)過程(用相關(guān)的DROP 和CREATE 命令)。
?
SQL命令發(fā)生器(SqlCommandGenerator)
?????? 早期版本的DNN包含一個(gè)叫做SqlCommandGenerator的類,這個(gè)類能夠簡單的調(diào)用SQL Server / MSDE數(shù)據(jù)庫。然而它是反射每一個(gè)數(shù)據(jù)庫調(diào)用的,這帶來很嚴(yán)重的性能影響。這個(gè)類雖然仍然還保留著,但很明顯的我們鼓勵(lì)開發(fā)者采用DataProvider的模式。
?
?
參考
?????? 微軟ASP.NET 組的Rob Howard提供了大量的指導(dǎo)和DataProvider模型實(shí)現(xiàn)的示例代碼,.NET Pet Shop 3.0也提供了很多優(yōu)秀的參考資料。Microsoft Data Access Application Block一個(gè)快速開發(fā)SQL Server Provider實(shí)現(xiàn)的一個(gè)有效的工具。
轉(zhuǎn)載于:https://www.cnblogs.com/Alaric/archive/2011/12/23/2299787.html
總結(jié)
以上是生活随笔為你收集整理的DNN 数据访问策略 (转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 缝纫机多少钱一台啊?
- 下一篇: [转]Android横竖屏切换解决方案