日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

掌握 ASP.NET 之路:自定义实体类简介 来源 :msdn

發(fā)布時(shí)間:2023/12/13 asp.net 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 掌握 ASP.NET 之路:自定义实体类简介 来源 :msdn 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

ADODB.RecordSet 和常常被遺忘的 MoveNext 的時(shí)代已經(jīng)過去,取而代之的是 Microsoft ADO.NET 強(qiáng)大而又靈活的功能。我們的新武器就是 System.Data 名稱空間,它的特點(diǎn)是具有速度極快的 DataReader 和功能豐富的 DataSet,而且打包在一個(gè)面向?qū)ο蟮膹?qiáng)大模型中。能夠使用這樣的工具一點(diǎn)都不奇怪。任何 3 層體系結(jié)構(gòu)都依靠可靠的數(shù)據(jù)訪問層 (DAL) 將數(shù)據(jù)層與業(yè)務(wù)層完美地連接起來(lái)。高質(zhì)量的 DAL 有助于改善代碼的重新使用,它是獲得高性能的關(guān)鍵,而且是完全透明的。

隨著工具的改進(jìn),我們的開發(fā)模式也發(fā)生了變化。告別 MoveNext 并不只是讓我們擺脫了繁瑣的語(yǔ)法,它還讓我們認(rèn)識(shí)了斷開連接的數(shù)據(jù),這種數(shù)據(jù)對(duì)我們開發(fā)應(yīng)用程序的方式產(chǎn)生了深刻的影響。

因?yàn)槲覀円呀?jīng)熟悉了 DataReader(其行為與 RecordSet 非常類似),所以沒花多長(zhǎng)時(shí)間就進(jìn)一步開發(fā)出 DataAdapterDataSetDataTableDataView。正是在開發(fā)這些新對(duì)象的過程中不斷得到磨煉的技能改變了我們的開發(fā)方式。斷開連接的數(shù)據(jù)使我們可以利用新的緩存技術(shù),從而大大提高了應(yīng)用程序的性能。這些類的功能使我們能夠編寫出更智能、更強(qiáng)大的函數(shù),同時(shí)還能減少(有時(shí)候甚至是大大減少)常見活動(dòng)所需的代碼數(shù)量。

有些情況下非常適合使用 DataSet,例如在設(shè)計(jì)原型、開發(fā)小型系統(tǒng)和支持實(shí)用程序時(shí)。但是,在企業(yè)系統(tǒng)中使用 DataSet 可能并不是最佳的解決方案,因?yàn)閷?duì)企業(yè)系統(tǒng)來(lái)說(shuō),易于維護(hù)要比投入市場(chǎng)的時(shí)間更重要。本指南的目的就是探討一種適合處理此類工作的 DataSet 的替代解決方案,即:自定義實(shí)體與集合。盡管還存在其他替代解決方案,但它們都無(wú)法提供相同的功能或無(wú)法獲得更多的支持。我們的首要任務(wù)是了解 DataSet 的缺點(diǎn),以便理解我們要解決的問題。

記住,每種解決方案都有優(yōu)缺點(diǎn),所以 DataSet 的缺點(diǎn)可能比自定義實(shí)體的缺點(diǎn)(我們也將進(jìn)行討論)更容易讓您接受。您和您的團(tuán)隊(duì)必須自己決定哪個(gè)解決方案更適合您的項(xiàng)目。記住要考慮解決方案的總成本,包括要求改變的實(shí)質(zhì)所在以及生產(chǎn)后所需的時(shí)間比實(shí)際開發(fā)代碼的時(shí)間更長(zhǎng)的可能性。最后請(qǐng)注意,我所說(shuō)的 DataSet 并不是類型化的 DataSet,但它確實(shí)可以彌補(bǔ)非類型化的 DataSet 的一些缺點(diǎn)。

返回頁(yè)首

DataSet 存在的問題

缺少抽象

尋找替代解決方案的第一個(gè)也是最明顯的原因就是 DataSet 無(wú)法從數(shù)據(jù)庫(kù)結(jié)構(gòu)中提取代碼。DataAdapter 可以很好地使您的代碼獨(dú)立于基礎(chǔ)數(shù)據(jù)庫(kù)供應(yīng)商(Microsoft、Oracle、IBM 等),但不能抽象出數(shù)據(jù)庫(kù)的核心組件:表、列和關(guān)系。這些核心數(shù)據(jù)庫(kù)組件也是 DataSet 的核心組件。DataSet 和數(shù)據(jù)庫(kù)不僅共享通用組件,不幸的是,它們還共享架構(gòu)。假定有下面這樣一個(gè) Select 語(yǔ)句:

SELECT UserId, FirstName, LastName FROM Users

我們知道這些值可以從 DataSet 中的 UserId、FirstNameLastName 這些 DataColumn 中獲得。

為什么會(huì)這么復(fù)雜?讓我們看一個(gè)基本的日常示例。首先我們有一個(gè)簡(jiǎn)單的 DAL 函數(shù):

'Visual Basic .NET Public Function GetAllUsers() As DataSet Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As SqlCommand = New SqlCommand("GetUsers", connection) command.CommandType = CommandType.StoredProcedure Dim da As SqlDataAdapter = New SqlDataAdapter(command) Try Dim ds As DataSet = New DataSet da.Fill(ds) Return ds Finally connection.Dispose() command.Dispose() da.Dispose() End Try End Function //C# public DataSet GetAllUsers() { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("GetUsers", connection); command.CommandType = CommandType.StoredProcedure; SqlDataAdapter da = new SqlDataAdapter(command); try { DataSet ds = new DataSet(); da.Fill(ds); return ds; }finally { connection.Dispose(); command.Dispose(); da.Dispose(); } }

然后我們有一個(gè)頁(yè)面,它使用重復(fù)器顯示所有用戶:

<HTML> <body> <form id="Form1" method="post" runat="server"> <asp:Repeater ID="users" Runat="server"> <ItemTemplate> <%# DataBinder.Eval(Container.DataItem, "FirstName") %> <br /> </ItemTemplate> </asp:Repeater> </form> </body> </HTML> <script runat="server"> public sub page_load users.DataSource = GetAllUsers() users.DataBind() end sub </script>

正如我們所看到的那樣,我們的 ASPX 頁(yè)面利用 DAL 函數(shù) GetAllUsers 作為重復(fù)器的 DataSource。如果由于某種原因(為了性能而降級(jí)、為清楚起見而進(jìn)行了標(biāo)準(zhǔn)化、要求發(fā)生了變化)導(dǎo)致數(shù)據(jù)庫(kù)架構(gòu)發(fā)生變化,變化就會(huì)一直影響 ASPX,即影響使用“FirstName”列名的 Databinder.Eval 行。這將立刻在您腦海中產(chǎn)生一個(gè)危險(xiǎn)信號(hào):數(shù)據(jù)庫(kù)架構(gòu)的變化會(huì)一直影響到 ASPX 代碼嗎?聽起來(lái)不太像 N 層,對(duì)嗎?

如果我們所要做的只是對(duì)列進(jìn)行簡(jiǎn)單的重命名,那么更改本例中的代碼并不復(fù)雜。但是,如果在許多地方都使用了 GetAllUsers,更糟糕的是,如果將其作為為無(wú)數(shù)用戶提供服務(wù)的 Web 服務(wù),那又會(huì)怎么樣呢?怎樣才能輕松或安全地傳播更改?對(duì)于這個(gè)基本示例而言,存儲(chǔ)過程本身作為抽象層可能已經(jīng)足夠;但是依賴存儲(chǔ)過程獲得除最基本的保護(hù)以外的功能則可能會(huì)在以后造成更大的問題??梢詫⒋艘暈橐环N硬編碼;實(shí)質(zhì)上,使用 DataSet 時(shí),您可能需要在數(shù)據(jù)庫(kù)架構(gòu)(不管使用列名稱還是序號(hào)位置)和應(yīng)用層/業(yè)務(wù)層之間建立一個(gè)嚴(yán)格的連接。但愿以前的經(jīng)驗(yàn)(或邏輯)已經(jīng)讓您了解到硬編碼對(duì)維護(hù)工作以及將來(lái)的開發(fā)產(chǎn)生的影響。

DataSet 無(wú)法提供適當(dāng)抽象的另一個(gè)原因是它要求開發(fā)人員必須了解基礎(chǔ)架構(gòu)。我們所說(shuō)的不是基礎(chǔ)知識(shí),而是關(guān)于列名稱、類型和關(guān)系的所有知識(shí)。去掉這個(gè)要求不僅使您的代碼不像我們看到的那樣容易中斷,還使代碼更易于編寫和維護(hù)。簡(jiǎn)單地說(shuō):

Convert.ToInt32(ds.Tables[0].Rows[i]["userId"]);

不僅難于閱讀,而且需要非常熟悉列名稱及其類型。理想情況下,您的業(yè)務(wù)層不需要知道有關(guān)基礎(chǔ)數(shù)據(jù)庫(kù)、數(shù)據(jù)庫(kù)架構(gòu)或 SQL 的任何內(nèi)容。如果您像上述代碼字符串中那樣使用 DataSet(使用 CodeBehind 并不會(huì)有任何改善),您的業(yè)務(wù)層可能會(huì)很薄。

弱類型

DataSet 屬于弱類型,因此容易出錯(cuò),還可能會(huì)影響您的開發(fā)工作。這意味著無(wú)論何時(shí)從 DataSet 中檢索值,值都以 System.Object 的形式返回,您需要對(duì)這種值進(jìn)行轉(zhuǎn)換。您面臨轉(zhuǎn)換可能會(huì)失敗的風(fēng)險(xiǎn)。不幸的是,失敗不是在編譯時(shí)發(fā)生,而是在運(yùn)行時(shí)發(fā)生。另外,在處理弱類型的對(duì)象時(shí),Microsoft Visual Studio.NET (VS.NET) 等工具對(duì)您的開發(fā)人員并沒有太大的幫助。前面我們說(shuō)過需要深入了解構(gòu)架的知識(shí),就是指這個(gè)意思。我們?cè)賮?lái)看一個(gè)非常常見的示例:

'Visual Basic.NET Dim userId As Integer = ? Convert.ToInt32(ds.Tables(0).Rows(0)("UserId")) Dim userId As Integer = CInt(ds.Tables(0).Rows(0)("UserId")) Dim userId As Integer = CInt(ds.Tables(0).Rows(0)(0)) //C# int userId = Convert.ToInt32(ds.Tables[0].Rows[0]("UserId"));

這段代碼顯示了從 DataSet 中檢索值的可能方法——可能您的代碼中到處都需要檢索值(如果不進(jìn)行轉(zhuǎn)換,而您使用的又是 Visual Basic .NET,您可能會(huì)使用 Option Strict Off 這樣的代碼,而這會(huì)給您帶來(lái)更大的麻煩。)

不幸的是,這些代碼中的每一行都可能會(huì)產(chǎn)生大量的運(yùn)行時(shí)錯(cuò)誤:

  • 轉(zhuǎn)換可能由于以下原因而失敗:

    • 值可能為空。

    • 開發(fā)人員可能對(duì)基礎(chǔ)數(shù)據(jù)類型判斷有誤(還是這個(gè)問題,即開發(fā)人員需要非常熟悉數(shù)據(jù)庫(kù)架構(gòu))。

    • 如果您使用序號(hào)值,誰(shuí)知道位置 X 處實(shí)際上是一個(gè)什么樣的列。

  • ds.Tables(0) 可能返回一個(gè)空引用(如果 DAL 方法或存儲(chǔ)過程中有任何部分失敗)。

  • “UserId”可能由于以下原因而是一個(gè)無(wú)效的列名稱:

    • 可能已經(jīng)更改了名稱。

    • 可能不是由存儲(chǔ)過程返回的。

    • 可能包含錯(cuò)別字。

  • 我們可以修改代碼并以更安全的方式編寫,即為 null/nothing 添加檢查,為轉(zhuǎn)換添加 try/catch,但這些對(duì)開發(fā)人員都沒有幫助。

    更糟糕的是,正如我們前面所說(shuō),這不是抽象的。這意味著,每次要從 DataSet 中檢索 userId 時(shí),您都將面臨上面提到的風(fēng)險(xiǎn),或者需要對(duì)相同的保護(hù)性步驟進(jìn)行重新編程(當(dāng)然,實(shí)用程序功能可能會(huì)有助于降低風(fēng)險(xiǎn))。弱類型對(duì)象將錯(cuò)誤從設(shè)計(jì)時(shí)或編譯時(shí)(這時(shí)總能夠自動(dòng)檢測(cè)并輕松修復(fù)錯(cuò)誤)轉(zhuǎn)移到運(yùn)行時(shí)(這時(shí)的錯(cuò)誤可能會(huì)出現(xiàn)在生產(chǎn)過程中,而且更難查明)。

    非面向?qū)ο?/strong>

    您不能僅僅因?yàn)?DataSet 是對(duì)象,而 C# 和 Visual Basic .NET 是面向?qū)ο?(OO) 的語(yǔ)言就能以面向?qū)ο蟮姆绞绞褂?DataSet。OO 編程的“hello world”是一個(gè)典型的 Person 類,該類又是 Employee 的子類。但 DataSet 并沒有使此類繼承或其他大多數(shù) OO 技術(shù)成為可能(或者至少使它們變得自然/直觀)。Scott Hanselman 是類實(shí)體的堅(jiān)決支持者,他做出了最好的解釋:

    “DataSet 是一個(gè)對(duì)象,對(duì)嗎?但它并不是域?qū)ο?#xff0c;它不是一個(gè)‘蘋果’或‘桔子’,而是一個(gè)‘DataSet’類型的對(duì)象。DataSet 是一只碗(它知道支持?jǐn)?shù)據(jù)存儲(chǔ))。DataSet 是一個(gè)知道如何保存行和列的對(duì)象,它非常了解數(shù)據(jù)庫(kù)。但是,我不希望返回碗,我希望返回域?qū)ο?#xff0c;例如‘蘋果’?!?sup>1

    DataSet 使數(shù)據(jù)之間保持一種關(guān)系,使它們更強(qiáng)大并且能夠在關(guān)系數(shù)據(jù)庫(kù)中方便地使用。不幸的是,這意味著您將失去 OO 的所有優(yōu)點(diǎn)。

    因?yàn)?DataSet 不能作為域?qū)ο?#xff0c;所以無(wú)法向它們添加功能。通常情況下,對(duì)象具有字段、屬性和方法,它們的行為針對(duì)的是類的實(shí)例。例如,您可能會(huì)將 PromoteCalcuateOvertimePay 函數(shù)與 User 對(duì)象相關(guān)聯(lián),該對(duì)象可以通過 someUser.Promote()someUser.CalculateOverTimePay() 安全地調(diào)用。因?yàn)闊o(wú)法向 DataSet 添加方法,所以您需要使用實(shí)用程序功能來(lái)處理弱類型對(duì)象,并且在整個(gè)代碼中包含硬編碼值的更多實(shí)例。您一般會(huì)以過程代碼結(jié)束,在過程代碼中,您要么不斷地從 DataSet 中獲取數(shù)據(jù),要么以繁瑣的方式將它們存儲(chǔ)在本地變量中并向其他位置傳遞。兩種方法都有缺點(diǎn),而且都沒有任何優(yōu)點(diǎn)。

    DataSet 相反的情況

    如果您認(rèn)為數(shù)據(jù)訪問層應(yīng)返回 DataSet,您可能會(huì)漏掉一些重要的優(yōu)點(diǎn)。其中一個(gè)原因是您可能正在使用一個(gè)較薄或不存在的業(yè)務(wù)層,除了其他問題外,它還限制了您進(jìn)行抽象的能力。另外,因?yàn)槟褂玫氖且话愕念A(yù)編譯解決方案,所以很難利用 OO 技術(shù)。最后,Visual Studio.NET 等工具使開發(fā)人員無(wú)法輕松地利用弱類型對(duì)象(例如 DataSet),因此降低了效率并且增加了出錯(cuò)的可能性。

    所有這些因素都以不同的方式對(duì)代碼的可維護(hù)性產(chǎn)生了直接的影響。缺乏抽象使功能改善和錯(cuò)誤修復(fù)變得更復(fù)雜、更危險(xiǎn)。您無(wú)法充分利用 OO 提供的代碼重新使用或可讀性方面的改進(jìn)。當(dāng)然還有一點(diǎn),無(wú)論您的開發(fā)人員處理的是業(yè)務(wù)邏輯還是表示邏輯,他們都必須非常了解您的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。

    返回頁(yè)首

    自定義實(shí)體類

    DataSet 有關(guān)的大多數(shù)問題都可以利用 OO 編程的豐富功能在定義明確的業(yè)務(wù)層中解決。實(shí)際上,我們希望獲得按照關(guān)系組織的數(shù)據(jù)(數(shù)據(jù)庫(kù)),并將數(shù)據(jù)作為對(duì)象(代碼)使用。這個(gè)概念就是,不是獲得保存汽車信息的 DataTable,而是獲得汽車對(duì)象(稱為自定義實(shí)體或域?qū)ο?#xff09;。

    在了解自定義實(shí)體之前,讓我們首先看一看我們將要面臨的挑戰(zhàn)。最明顯的挑戰(zhàn)就是所需代碼的數(shù)量。我們不是簡(jiǎn)單地獲取數(shù)據(jù)并自動(dòng)填充 DataSet,而是獲取數(shù)據(jù)并手動(dòng)將數(shù)據(jù)映射到自定義實(shí)體(必須先創(chuàng)建好)。由于這是一項(xiàng)重復(fù)性的任務(wù),我們可以使用代碼生成工具或 O/R 映射器(后文有詳細(xì)的介紹)來(lái)減輕工作量。更大的問題是將數(shù)據(jù)從關(guān)系世界映射到對(duì)象世界的具體過程。對(duì)于簡(jiǎn)單的系統(tǒng),映射通常是直接的,但是隨著復(fù)雜性的增加,這兩個(gè)世界之間的差異就會(huì)產(chǎn)生問題。例如,繼承在對(duì)象世界中是獲得代碼重新使用以及可維護(hù)性的重要技術(shù)。不幸的是,繼承對(duì)關(guān)系數(shù)據(jù)庫(kù)來(lái)說(shuō)卻是一個(gè)陌生的概念。另外一個(gè)例子就是處理關(guān)系的方式不同:對(duì)象世界依靠維護(hù)單個(gè)對(duì)象的引用,而關(guān)系世界則是利用外鍵。

    因?yàn)榇a的數(shù)量以及關(guān)系數(shù)據(jù)和對(duì)象之間的差異不斷增加,看起來(lái)這個(gè)方法并不太適合更復(fù)雜的系統(tǒng),但事實(shí)正好相反。通過將各種問題隔離到一個(gè)層中,即映射過程(同樣可以自動(dòng)化),復(fù)雜的系統(tǒng)也可以從此方法獲益。另外,此方法已經(jīng)很常用,這意味著可以通過幾種已有的設(shè)計(jì)模式徹底解決增加的復(fù)雜性。前面討論的 DataSet 的缺點(diǎn)在復(fù)雜系統(tǒng)中將成倍擴(kuò)大,最后您會(huì)得出這樣一個(gè)系統(tǒng),它欠缺靈活應(yīng)變能力的缺點(diǎn)恰好超出其構(gòu)建的難度。

    什么是自定義實(shí)體?

    自定義實(shí)體是代表業(yè)務(wù)域的對(duì)象,因此,它們是業(yè)務(wù)層的基礎(chǔ)。如果您有一個(gè)用戶身份驗(yàn)證組件(本指南通篇都使用該示例進(jìn)行講解),您就可能具有 UserRole 對(duì)象。電子商務(wù)系統(tǒng)可能具有 SupplierMerchandise 對(duì)象,而房地產(chǎn)公司則可能具有 House、RoomAddress 對(duì)象。在您的代碼中,自定義實(shí)體只是一些類(實(shí)體和“類”之間具有非常密切的關(guān)系,就像在 OO 編程中使用的那樣)。一個(gè)典型的 User 類可能如下所示:

    'Visual Basic .NET Public Class User #Region "Fields and Properties" Private _userId As Integer Private _userName As String Private _password As String Public Property UserId() As Integer Get Return _userId End Get Set(ByVal Value As Integer) _userId = Value End Set End Property Public Property UserName() As String Get Return _userName End Get Set(ByVal Value As String) _userName = Value End Set End Property Public Property Password() As String Get Return _password End Get Set(ByVal Value As String) _password = Value End Set End Property #End Region #Region "Constructors" Public Sub New() End Sub Public Sub New(id As Integer, name As String, password As String) Me.UserId = id Me.UserName = name Me.Password = password End Sub #End Region End Class //C# public class User { #region "Fields and Properties" private int userId; private string userName; private string password; public int UserId { get { return userId; } set { userId = value; } } public string UserName { get { return userName; } set { userName = value; } } public string Password { get { return password; } set { password = value; } } #endregion #region "Constructors" public User() {} public User(int id, string name, string password) { this.UserId = id; this.UserName = name; this.Password = password; } #endregion }

    為什么能夠從它們獲益?

    使用自定義實(shí)體獲得的主要好處來(lái)自這樣一個(gè)簡(jiǎn)單的事實(shí),即它們是完全受您控制的對(duì)象。具體而言,它們?cè)试S您:

    • 利用繼承和封裝等 OO 技術(shù)。

    • 添加自定義行為。

    例如,我們的 User 類可以通過為其添加 UpdatePassword 函數(shù)而受益(我們可能會(huì)使用外部/實(shí)用程序函數(shù)對(duì)數(shù)據(jù)集執(zhí)行此類操作,但會(huì)影響可讀性/維護(hù)性)。另外,它們屬于強(qiáng)類型,這表示我們可以獲得 IntelliSense 支持:

    1 User 類的 IntelliSense

    最后,因?yàn)樽远x實(shí)體為強(qiáng)類型,所以不太需要進(jìn)行容易出錯(cuò)的強(qiáng)制轉(zhuǎn)換:

    Dim userId As Integer = user.UserId '與 Dim userId As Integer = ? Convert.ToInt32(ds.Tables("users").Rows(0)("UserId")) 返回頁(yè)首

    對(duì)象關(guān)系映射

    正如前文所討論的那樣,此方法的主要挑戰(zhàn)之一就是處理關(guān)系數(shù)據(jù)和對(duì)象之間的差異。因?yàn)槲覀兊臄?shù)據(jù)始終存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中,所以我們只能在這兩個(gè)世界之間架起一座橋梁。對(duì)于上文的 User 示例,我們可能希望在數(shù)據(jù)庫(kù)中建立一個(gè)如下所示的用戶表:

    2 User 的數(shù)據(jù)視圖

    從這個(gè)關(guān)系架構(gòu)映射到自定義實(shí)體是一個(gè)非常簡(jiǎn)單的事情:

    'Visual Basic .NET Public Function GetUser(ByVal userId As Integer) As User Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("GetUserById", connection) command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId Dim dr As SqlDataReader = Nothing Try connection.Open() dr = command.ExecuteReader(CommandBehavior.SingleRow) If dr.Read Then Dim user As New User user.UserId = Convert.ToInt32(dr("UserId")) user.UserName = Convert.ToString(dr("UserName")) user.Password = Convert.ToString(dr("Password")) Return user End If Return Nothing Finally If Not dr is Nothing AndAlso Not dr.IsClosed Then dr.Close() End If connection.Dispose() command.Dispose() End Try End Function //C# public User GetUser(int userId) { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("GetUserById", connection); command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId; SqlDataReader dr = null; try{ connection.Open(); dr = command.ExecuteReader(CommandBehavior.SingleRow); if (dr.Read()){ User user = new User(); user.UserId = Convert.ToInt32(dr["UserId"]); user.UserName = Convert.ToString(dr["UserName"]); user.Password = Convert.ToString(dr["Password"]); return user; } return null; }finally{ if (dr != null && !dr.IsClosed){ dr.Close(); } connection.Dispose(); command.Dispose(); } }

    我們?nèi)匀话凑胀ǔ5姆绞皆O(shè)置連接和命令對(duì)象,但接著創(chuàng)建了 User 類的一個(gè)新實(shí)例并從 DataReader 中填充該實(shí)例。您仍然可以在此函數(shù)中使用 DataSet 并將其映射到您的自定義實(shí)體,但 DataSet 相對(duì)于 DataReader 的主要好處是前者提供了數(shù)據(jù)的斷開連接的視圖。在本例中,User 實(shí)例提供了斷開連接的視圖,使我們可以利用 DataReader 的速度。

    等一下!您并沒有解決任何問題!

    細(xì)心的讀者可能注意到我前面提到 DataSet 的問題之一是它們并非強(qiáng)類型,這導(dǎo)致效率降低并增加了出現(xiàn)運(yùn)行時(shí)錯(cuò)誤的可能性。它們還需要開發(fā)人員深入了解基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)??匆豢瓷衔牡拇a,您可能會(huì)注意到這些問題依然存在。但請(qǐng)注意,我們已經(jīng)將這些問題封裝到一個(gè)非常孤立的代碼區(qū)域內(nèi);這表示您的類實(shí)體的使用者(Web 界面、Web 服務(wù)使用者、Windows 表單)仍然完全沒有意識(shí)到這些問題。相反,使用 DataSet 可以將這些問題分散到整個(gè)代碼中。

    改進(jìn)

    上文的代碼對(duì)顯示映射的基本概念很有用,但可以在兩個(gè)關(guān)鍵的方面進(jìn)行改進(jìn)。首先,我們需要提取并將代碼填充到其自己的函數(shù)中,因?yàn)榇a有可能會(huì)被重新使用:

    'Visual Basic .NET Public Function PopulateUser(ByVal dr As IDataRecord) As User Dim user As New User user.UserId = Convert.ToInt32(dr("UserId")) '檢查 NULL 的示例 If Not dr("UserName") Is DBNull.Value Then user.UserName = Convert.ToString(dr("UserName")) End If user.Password = Convert.ToString(dr("Password")) Return user End Function //C# public User PopulateUser(IDataRecord dr) { User user = new User(); user.UserId = Convert.ToInt32(dr["UserId"]); //檢查 NULL 的示例 if (dr["UserName"] != DBNull.Value){ user.UserName = Convert.ToString(dr["UserName"]); } user.Password = Convert.ToString(dr["Password"]); return user; }

    第二個(gè)需要注意的事項(xiàng)是,我們不對(duì)映射函數(shù)使用 SqlDataReader,而是使用 IDataRecord。這是所有 DataReader 實(shí)現(xiàn)的接口。使用 IDataRecord 使我們的映射過程獨(dú)立于供應(yīng)商。也就是說(shuō),我們可以使用上一個(gè)函數(shù)從 Access 數(shù)據(jù)庫(kù)中映射 User,即使它使用 OleDbDataReader 也可以。如果您將這個(gè)特定的方法與 Provider Model Design Pattern(鏈接 1、鏈接 2)結(jié)合使用,您的代碼就可以輕松地用于不同的數(shù)據(jù)庫(kù)提供程序。

    最后,以上代碼說(shuō)明了封裝的強(qiáng)大功能。處理 DataSet 中的 NULL 并非最簡(jiǎn)單的事,因?yàn)槊看翁崛≈禃r(shí)都需要檢查它是否為 NULL。使用上述填充方法,我們?cè)谝粋€(gè)地方就輕松地解決了此問題,使我們的客戶無(wú)需處理它。

    映射到何處?

    關(guān)于此類數(shù)據(jù)訪問和映射函數(shù)的歸屬問題存在一些爭(zhēng)論,即究竟是作為獨(dú)立類的一部分,還是作為適當(dāng)自定義實(shí)體的一部分。將所有用戶相關(guān)的任務(wù)(獲取數(shù)據(jù)、更新和映射)都作為 User 自定義實(shí)體的一部分當(dāng)然很不錯(cuò)。這在數(shù)據(jù)庫(kù)架構(gòu)與自定義實(shí)體很相似時(shí)會(huì)很有用(比如在本例中)。隨著系統(tǒng)復(fù)雜性的增加,這兩個(gè)世界的差異開始顯現(xiàn)出來(lái),將數(shù)據(jù)層和業(yè)務(wù)層明確分離對(duì)簡(jiǎn)化維護(hù)有很大的幫助(我喜歡將其稱為數(shù)據(jù)訪問層)。將訪問和映射代碼放在其自己的層 (DAL) 上有一個(gè)副作用,即它為確保數(shù)據(jù)層與業(yè)務(wù)層的明確分離提供了一個(gè)嚴(yán)格的原則:

    永遠(yuǎn)不要從 System.Data 返回類或從 DAL 返回子命名空間

    返回頁(yè)首

    自定義集合

    到目前為止,我們只了解了如何處理單個(gè)實(shí)體,但您經(jīng)常需要處理多個(gè)對(duì)象。一個(gè)簡(jiǎn)單的解決方案是將多個(gè)值存儲(chǔ)在一個(gè)一般的集合(例如 Arraylist)中。這并非最理想的解決方案,因?yàn)樗之a(chǎn)生了與 DataSet 有關(guān)的一些問題,即:

    • 它們不是強(qiáng)類型,并且

    • 無(wú)法添加自定義行為。

    最能滿足我們需求的解決方案是創(chuàng)建我們自己的自定義集合。幸虧 Microsoft .NET Framework 提供了一個(gè)專門為了此目的而繼承的類:CollectionBase 。 CollectionBase 的工作原理是,將所有類型的對(duì)象都存儲(chǔ)在專有 Arraylist 中,但是通過只接受特定類型(例如 User 對(duì)象)的方法來(lái)提供對(duì)這些專有集合的訪問。也就是說(shuō),將弱類型代碼封裝在強(qiáng)類型的 API 中。

    雖然自定義集合可能看起來(lái)有很多代碼,但大多數(shù)都可以由代碼生成功能或通過剪切和粘貼方便地完成,并且通常只需要一次搜索和替換即可。讓我們看一看構(gòu)成 User 類的自定義集合的不同部分:

    'Visual Basic .NET Public Class UserCollection Inherits CollectionBase Default Public Property Item(ByVal index As Integer) As User Get Return CType(List(index), User) End Get Set List(index) = value End Set End Property Public Function Add(ByVal value As User) As Integer Return (List.Add(value)) End Function Public Function IndexOf(ByVal value As User) As Integer Return (List.IndexOf(value)) End Function Public Sub Insert(ByVal index As Integer, ByVal value As User) List.Insert(index, value) End Sub Public Sub Remove(ByVal value As User) List.Remove(value) End Sub Public Function Contains(ByVal value As User) As Boolean Return (List.Contains(value)) End Function End Class //C# public class UserCollection :CollectionBase { public User this[int index] { get {return (User)List[index];} set {List[index] = value;} } public int Add(User value) { return (List.Add(value)); } public int IndexOf(User value) { return (List.IndexOf(value)); } public void Insert(int index, User value) { List.Insert(index, value); } public void Remove(User value) { List.Remove(value); } public bool Contains(User value) { return (List.Contains(value)); } }

    通過實(shí)現(xiàn) CollectionBase 可以完成更多任務(wù),但上面的代碼代表了自定義集合所需的核心功能。觀察一下 Add 函數(shù),可以看出我們只是簡(jiǎn)單地將對(duì) List.Add(它是一個(gè) Arraylist)的調(diào)用封裝到僅允許 User 對(duì)象的函數(shù)中。

    映射自定義集合

    將我們的關(guān)系數(shù)據(jù)映射到自定義集合的過程與我們對(duì)自定義實(shí)體執(zhí)行的過程非常相似。我們不再創(chuàng)建一個(gè)實(shí)體并將其返回,而是將該實(shí)體添加到集合中并循環(huán)到下一個(gè):

    'Visual Basic .NET Public Function GetAllUsers() As UserCollection Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("GetAllUsers", connection) Dim dr As SqlDataReader = Nothing Try connection.Open() dr = command.ExecuteReader(CommandBehavior.SingleResult) Dim users As New UserCollection While dr.Read() users.Add(PopulateUser(dr)) End While Return users Finally If Not dr Is Nothing AndAlso Not dr.IsClosed Then dr.Close() End If connection.Dispose() command.Dispose() End Try End Function //C# public UserCollection GetAllUsers() { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command =new SqlCommand("GetAllUsers", connection); SqlDataReader dr = null; try{ connection.Open(); dr = command.ExecuteReader(CommandBehavior.SingleResult); UserCollection users = new UserCollection(); while (dr.Read()){ users.Add(PopulateUser(dr)); } return users; }finally{ if (dr != null && !dr.IsClosed){ dr.Close(); } connection.Dispose(); command.Dispose(); } }

    我們從數(shù)據(jù)庫(kù)中獲得數(shù)據(jù)、創(chuàng)建自定義集合,然后通過在結(jié)果中循環(huán)來(lái)創(chuàng)建每個(gè) User 對(duì)象并將其添加到集合中。同樣要注意 PopulateUser 映射函數(shù)是如何重新使用的。

    添加自定義行為

    在討論自定義實(shí)體時(shí),我們只是泛泛地提到可以將自定義行為添加到類中。您向?qū)嶓w中添加的功能類型很大程度上取決于您要實(shí)現(xiàn)的業(yè)務(wù)邏輯的類型,但您可能希望在自定義集合中實(shí)現(xiàn)某些常見的功能。一個(gè)示例就是返回一個(gè)基于某個(gè)鍵的實(shí)體,例如基于 userId 的用戶:

    'Visual Basic .NET Public Function FindUserById(ByVal userId As Integer) As User For Each user As User In List If user.UserId = userId Then Return user End If Next Return Nothing End Function //C# public User FindUserById(int userId) { foreach (User user in List) { if (user.UserId == userId){ return user; } } return null; }

    另一個(gè)示例可能是返回基于特定標(biāo)準(zhǔn)(例如部分用戶名)的用戶子集:

    'Visual Basic .NET Public Function FindMatchingUsers(ByVal search As String) As UserCollection If search Is Nothing Then Throw New ArgumentNullException("search cannot be null") End If Dim matchingUsers As New UserCollection For Each user As User In List Dim userName As String = user.UserName If Not userName Is Nothing And userName.StartsWith(search) Then matchingUsers.Add(user) End If Next Return matchingUsers End Function //C# public UserCollection FindMatchingUsers(string search) { if (search == null){ throw new ArgumentNullException("search cannot be null"); } UserCollection matchingUsers = new UserCollection(); foreach (User user in List) { string userName = user.UserName; if (userName != null && userName.StartsWith(search)){ matchingUsers.Add(user); } } return matchingUsers; }

    可以通過 DataTable.Select 以相同的方式使用 DataSets。需要說(shuō)明的重要一點(diǎn)是,盡管創(chuàng)建自己的功能使您可以完全控制您的代碼,但 Select 方法為完成同樣的操作提供了一個(gè)非常方便且不需要編寫代碼的方法。但另一方面,Select 需要開發(fā)人員了解基礎(chǔ)數(shù)據(jù)庫(kù),而且它不是強(qiáng)類型。

    綁定自定義集合

    我們看到的第一個(gè)示例是將 DataSet 綁定到 ASP.NET 控件??紤]到它很普通,您會(huì)高興地發(fā)現(xiàn)自定義集合綁定同樣很簡(jiǎn)單(這是因?yàn)?CollectionBase 實(shí)現(xiàn)了用于綁定的 Ilist)。自定義集合可以作為任何控件的 DataSource,而 DataBinder.Eval 只能像您使用 DataSet 那樣使用:

    'Visual Basic .NET Dim users as UserCollection = DAL.GetallUsers() repeater.DataSource = users repeater.DataBind() //C# UserCollection users = DAL.GetAllUsers(); repeater.DataSource = users; repeater.DataBind(); <!-- HTML --> <asp:Repeater onItemDataBound="r_IDB" ID="repeater" Runat="server"> <ItemTemplate> <asp:Label ID="userName" Runat="server"> <%# DataBinder.Eval(Container.DataItem, "UserName") %><br /> </asp:Label> </ItemTemplate> </asp:Repeater>

    您可以不使用列名稱作為 DataBinder.Eval 的第二個(gè)參數(shù),而指定您希望顯示的屬性名稱,在本例中為 UserName。

    對(duì)于在許多數(shù)據(jù)綁定控件提供的 OnItemDataBoundOnItemCreated 中執(zhí)行處理的人來(lái)說(shuō),您可能會(huì)將 e.Item.DataItem 強(qiáng)制轉(zhuǎn)換成 DataRowView。當(dāng)綁定到自定義集合時(shí),e.Item.DataItem 則被強(qiáng)制轉(zhuǎn)換成自定義實(shí)體,在我們的示例中為 User 類:

    'Visual Basic .NET Protected Sub r_ItemDataBound (s As Object, e As RepeaterItemEventArgs) Dim type As ListItemType = e.Item.ItemType If type = ListItemType.AlternatingItem OrElse ? type = ListItemType.Item Then Dim u As Label = CType(e.Item.FindControl("userName"), Label) Dim currentUser As User = CType(e.Item.DataItem, User) If Not PasswordUtility.PasswordIsSecure(currentUser.Password) Then ul.ForeColor = Drawing.Color.Red End If End If End Sub //C# protected void r_ItemDataBound(object sender, RepeaterItemEventArgs e) { ListItemType type = e.Item.ItemType; if (type == ListItemType.AlternatingItem || ? type == ListItemType.Item){ Label ul = (Label)e.Item.FindControl("userName"); User currentUser = (User)e.Item.DataItem; if (!PasswordUtility.PasswordIsSecure(currentUser.Password)){ ul.ForeColor = Color.Red; } } } 返回頁(yè)首

    管理關(guān)系

    即使在最簡(jiǎn)單的系統(tǒng)中,實(shí)體之間也存在關(guān)系。對(duì)于關(guān)系數(shù)據(jù)庫(kù),可以通過外鍵維護(hù)關(guān)系;而使用對(duì)象時(shí),關(guān)系只是對(duì)另一個(gè)對(duì)象的引用。例如,根據(jù)我們前面的示例,User 對(duì)象完全可以具有一個(gè) Role

    'Visual Basic .NET Public Class User Private _role As Role Public Property Role() As Role Get Return _role End Get Set(ByVal Value As Role) _role = Value End Set End Property End Class //C# public class User { private Role role; public Role Role { get {return role;} set {role = value;} } }

    或者一個(gè) Role 集合:

    'Visual Basic .NET Public Class User Private _roles As RoleCollection Public ReadOnly Property Roles() As RoleCollection Get If _roles Is Nothing Then _roles = New RoleCollection End If Return _roles End Get End Property End Class //C# public class User { private RoleCollection roles; public RoleCollection Roles { get { if (roles == null){ roles = new RoleCollection(); } return roles; } } }

    在這兩個(gè)示例中,我們有一個(gè)虛構(gòu)的 Role 類或 RoleCollection 類,它們就是類似于 UserUserCollection 類的其他自定義實(shí)體或集合類。

    映射關(guān)系

    真正的問題在于如何映射關(guān)系。讓我們看一個(gè)簡(jiǎn)單的示例,我們希望根據(jù) userId 及其角色來(lái)檢索一個(gè)用戶。首先,我們看一看關(guān)系模型:

    3 User Role 之間的關(guān)系

    這里,我們看到了一個(gè) User 表和一個(gè) Role 表,我們可以將這兩個(gè)表都以直觀的方式映射到自定義實(shí)體。我們還有一個(gè) UserRoleJoin 表,它代表了 UserRole 之間的多對(duì)多關(guān)系。

    然后,我們使用存儲(chǔ)過程來(lái)獲取兩個(gè)單獨(dú)的結(jié)果:第一個(gè)代表 User,第二個(gè)代表該用戶的 Role

    CREATE PROCEDURE GetUserById( @UserId INT )AS SELECT UserId, UserName, [Password] FROM Users WHERE UserId = @UserID SELECT R.RoleId, R.[Name], R.Code FROM Roles R INNER JOIN UserRoleJoin URJ ON R.RoleId = URJ.RoleId WHERE URJ.UserId = @UserId

    最后,我們從關(guān)系模型映射到對(duì)象模型:

    'Visual Basic .NET Public Function GetUserById(ByVal userId As Integer) As User Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("GetUserById", connection) command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId Dim dr As SqlDataReader = Nothing Try connection.Open() dr = command.ExecuteReader() Dim user As User = Nothing If dr.Read() Then user = PopulateUser(dr) dr.NextResult() While dr.Read() user.Roles.Add(PopulateRole(dr)) End While End If Return user Finally If Not dr Is Nothing AndAlso Not dr.IsClosed Then dr.Close() End If connection.Dispose() command.Dispose() End Try End Function //C# public User GetUserById(int userId) { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("GetUserById", connection); command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId; SqlDataReader dr = null; try{ connection.Open(); dr = command.ExecuteReader(); User user = null; if (dr.Read()){ user = PopulateUser(dr); dr.NextResult(); while(dr.Read()){ user.Roles.Add(PopulateRole(dr)); } } return user; }finally{ if (dr != null && !dr.IsClosed){ dr.Close(); } connection.Dispose(); command.Dispose(); } }

    User 實(shí)例即被創(chuàng)建和填充;我們轉(zhuǎn)移到下一個(gè)結(jié)果/選擇并進(jìn)行循環(huán),填充 Role 并將它們添加到 User 類的 RolesCollection 屬性中。

    返回頁(yè)首

    高級(jí)內(nèi)容

    本指南的目的是介紹自定義實(shí)體與集合的概念及使用。使用自定義實(shí)體是業(yè)界廣泛采用的做法,因此,也就產(chǎn)生了同樣多的模式以處理各種情況。設(shè)計(jì)模式具有優(yōu)勢(shì)的原因有很多。首先,在處理具體的情況時(shí),您可能不是第一次碰到某個(gè)給定的問題。設(shè)計(jì)模式使您可以重新使用給定問題的已經(jīng)過嘗試和測(cè)試的解決方案(雖然設(shè)計(jì)模式并不意味著全盤照抄,但它們幾乎總是能夠?yàn)榻鉀Q方案提供一個(gè)可靠的基礎(chǔ))。相應(yīng)地,這使您對(duì)系統(tǒng)隨著復(fù)雜性增加而進(jìn)行縮放的能力充滿了信心,不僅因?yàn)樗且粋€(gè)廣泛使用的方法,還因?yàn)樗哂性敱M的記錄。設(shè)計(jì)模式還為您提供了一個(gè)通用的詞匯表,使知識(shí)的傳播和傳授更容易實(shí)現(xiàn)。

    不能說(shuō)設(shè)計(jì)模式只適用于自定義實(shí)體,實(shí)際上許多設(shè)計(jì)模式都并非如此。但是,如果您找機(jī)會(huì)試一下,您可能會(huì)驚喜地發(fā)現(xiàn)許多記載詳盡的模式確實(shí)適用于自定義實(shí)體和映射過程。

    最后這一部分專門介紹大型或較復(fù)雜的系統(tǒng)可能會(huì)碰到的一些高級(jí)情況。因?yàn)榇蠖鄶?shù)主題都可能值得您單獨(dú)學(xué)習(xí),所以我會(huì)盡量為您提供一些入門資料。

    Martin Fowler 的 Patterns of Enterprise Application Architecture 就是一個(gè)很好的入門材料,它不僅可以作為常見設(shè)計(jì)模式的優(yōu)秀參考(具有詳細(xì)的解釋和大量的示例代碼),而且它的前 100 頁(yè)確實(shí)可以讓您透徹地了解整個(gè)概念。另外,Fowler 還提供了一個(gè)聯(lián)機(jī)模式目錄,它對(duì)于已經(jīng)熟悉概念但需要一個(gè)便利參考的人士很有用。

    并發(fā)

    前面的示例介紹的都是從數(shù)據(jù)庫(kù)中提取數(shù)據(jù)并根據(jù)這些數(shù)據(jù)創(chuàng)建對(duì)象。總體而言,更新、刪除和插入數(shù)據(jù)等操作是很直觀的。我們的業(yè)務(wù)層負(fù)責(zé)創(chuàng)建對(duì)象、將對(duì)象傳遞給數(shù)據(jù)訪問層,然后讓數(shù)據(jù)訪問層處理對(duì)象世界與關(guān)系世界之間的映射。例如:

    'Visual Basic .NET Public sub UpdateUser(ByVal user As User) Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("UpdateUser", connection) ' 可以借助可重新使用的函數(shù)對(duì)此進(jìn)行反向映射 command.Parameters.Add("@UserId", SqlDbType.Int) command.Parameters(0).Value = user.UserId command.Parameters.Add("@Password", SqlDbType.VarChar, 64) command.Parameters(1).Value = user.Password command.Parameters.Add("@UserName", SqlDbType.VarChar, 128) command.Parameters(2).Value = user.UserName Try connection.Open() command.ExecuteNonQuery() Finally connection.Dispose() command.Dispose() End Try End Sub //C# public void UpdateUser(User user) { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("UpdateUser", connection); // 可以借助可重新使用的函數(shù)對(duì)此進(jìn)行反向映射 command.Parameters.Add("@UserId", SqlDbType.Int); command.Parameters[0].Value = user.UserId; command.Parameters.Add("@Password", SqlDbType.VarChar, 64); command.Parameters[1].Value = user.Password; command.Parameters.Add("@UserName", SqlDbType.VarChar, 128); command.Parameters[2].Value = user.UserName; try{ connection.Open(); command.ExecuteNonQuery(); }finally{ connection.Dispose(); command.Dispose(); } }

    但在處理并發(fā)時(shí)就不那么直觀了,也就是說(shuō),當(dāng)兩個(gè)用戶試圖同時(shí)更新相同的數(shù)據(jù)時(shí)會(huì)出現(xiàn)什么情況呢?默認(rèn)的行為(如果您沒有執(zhí)行任何操作)是最后提交數(shù)據(jù)的人將覆蓋以前所有的工作。這可能不是理想的情況,因?yàn)橐粋€(gè)用戶的工作將在未獲得任何提示的情況下被覆蓋。要完全避免所有沖突,一種方法就是使用消極的并發(fā)技術(shù);但此方法需要具有某種鎖定機(jī)制,這可能很難通過可縮放的方式實(shí)現(xiàn)。替代方法就是使用積極的并發(fā)技術(shù)。讓第一個(gè)提交的用戶控制并通知后面的用戶是通常采取的更溫和、更用戶友好的方法。這可以通過某種行版本控制(例如時(shí)間戳)來(lái)實(shí)現(xiàn)。

    參考資料:

    • Introduction to Data Concurrency in ADO.NET

    • CSLA.NET's concurrency techniques

    • Unit of Work design pattern

    • Optimistic offline lock design pattern

    • Pessimistic offline lock design pattern

    性能

    與合理的靈活性和功能問題相對(duì)的是,我們經(jīng)常擔(dān)心細(xì)小的性能差異。盡管性能的確很重要,但提供適用于一切情況而不是最簡(jiǎn)單情況的通用原則通常很難。例如,將自定義集合與 DataSet 相比,哪個(gè)更快?使用自定義集合,您可以大量使用 DataReader,這是從數(shù)據(jù)庫(kù)中提取數(shù)據(jù)的較快方式。但答案實(shí)際上取決于您使用它們的方式以及處理的數(shù)據(jù)類型,所以一般性的說(shuō)明沒有任何用。更重要的一點(diǎn)是要認(rèn)識(shí)到,不管您能節(jié)省多少處理時(shí)間,與維護(hù)性方面的差異相比都可能微不足道。

    當(dāng)然,并不是說(shuō)您不可能找到一個(gè)既具有高性能又可維護(hù)的解決方案。雖然我強(qiáng)調(diào)說(shuō)答案實(shí)際上取決于您的使用方式,但的確有一些模式可以幫助您最大程度地提高性能。但是,首先要知道的是自定義實(shí)體與集合緩存以及 DataSet,并且能夠利用相同的機(jī)制(類似于 HttpCache)。DataSet 的優(yōu)勢(shì)之一是它能夠編寫 Select 語(yǔ)句,以便只獲取所需的信息。使用自定義實(shí)體時(shí),您常常感到不得不填充整個(gè)實(shí)體以及子實(shí)體。例如,如果要通過 DataSet 顯示一個(gè) Organization 列表,您可以只提取 OganizationId、NameAddress 并將其綁定到重復(fù)器。使用自定義實(shí)體時(shí),我總覺得還需要獲取所有其他的 Organization 信息,如果該組織通過了 ISO 認(rèn)證,則可能是一個(gè)位標(biāo)記,即所有員工、其他聯(lián)系信息等的集合??赡芷渌藳]有碰到這個(gè)大難題,但幸運(yùn)的是,如果我們?cè)敢?#xff0c;我們可以對(duì)自定義實(shí)體進(jìn)行很好的控制。最常用的方法是使用一種延遲加載模式,它只在首次需要時(shí)獲取信息(可以很好地封裝在屬性中)。這種對(duì)各個(gè)屬性的控制提供了通過其他方式無(wú)法輕易獲得的巨大靈活性(請(qǐng)想象一下在 DataColumn 級(jí)別執(zhí)行類似操作的情況)。

    參考資料:

    • Lazy Load 設(shè)計(jì)模式

    • CSLA.NET lazy load

    排序與篩選

    雖然 DataView 對(duì)排序和篩選的內(nèi)置支持需要您了解有關(guān) SQL 和基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)的知識(shí),但它提供的方便確實(shí)是自定義集合所不具備的。我們?nèi)匀豢梢耘判蚝秃Y選,但首先需要編寫功能。因?yàn)榧夹g(shù)不一定是最先進(jìn)的,所以代碼的完整描述不屬于本節(jié)要討論的范圍。大多數(shù)技術(shù)都很相似,例如使用篩選器類篩選集合以及使用比較器類進(jìn)行排序,我認(rèn)為不存在固定的模式。但是,的確存在一些參考資料:

    • Generic sort function

    • Sorting & Filtering Custom Collections 教程

    代碼生成

    解決概念上的障礙后,自定義實(shí)體與集合的主要缺點(diǎn)就是靈活性、抽象和維護(hù)性差所導(dǎo)致的代碼數(shù)量的增加。實(shí)際上,您可能會(huì)認(rèn)為我所說(shuō)的維護(hù)成本和錯(cuò)誤的降低這一切都抵不上代碼的增加。雖然這一觀點(diǎn)是成立的(同樣,因?yàn)槿魏谓鉀Q方案都不是完美無(wú)缺的),但可以通過設(shè)計(jì)模式和框架(例如 CSLA.NET)大大緩解此問題。代碼生成工具與模式和框架完全不同,這些工具可以大大降低您實(shí)際需要編寫的代碼數(shù)量。本指南最初打算專門辟出一節(jié)詳細(xì)介紹代碼生成工具,特別是流行的免費(fèi) CodeSmith;但現(xiàn)有的許多參考資料都可能超出了我自己對(duì)該產(chǎn)品的認(rèn)識(shí)。

    在繼續(xù)之前,我認(rèn)識(shí)到代碼生成聽起來(lái)像天方夜譚一樣。但經(jīng)過正確的使用和理解后,它的確是您工具包中不可缺少的一個(gè)強(qiáng)大的武器,即使您沒有處理自定義實(shí)體也是如此。雖然代碼生成的確不僅僅適用于自定義實(shí)體,但很多都是專為自定義實(shí)體而設(shè)計(jì)的。原因很簡(jiǎn)單:自定義實(shí)體需要大量重復(fù)代碼。

    簡(jiǎn)言之,代碼生成是如何工作的?構(gòu)想聽起來(lái)好像遙不可及甚至反而會(huì)降低效率,但您基本上通過編寫代碼(模板)來(lái)生成代碼。例如,CodeSmith 附帶了許多強(qiáng)大的類,使您可以連接到數(shù)據(jù)庫(kù)并獲取所有屬性:表、列(類型、大小等)和關(guān)系。獲得這些信息后,我們前面討論的大部分工作都可以自動(dòng)完成。例如,開發(fā)人員可以選擇一個(gè)表,然后使用正確的模板自動(dòng)創(chuàng)建自定義實(shí)體(帶有正確的字段、屬性和構(gòu)造函數(shù)),并獲得映射函數(shù)、自定義集合以及基本的選擇、插入、更新和刪除功能。甚至還可以更進(jìn)一步,實(shí)現(xiàn)排序、篩選以及我們提到的其他高級(jí)功能。

    CodeSmith 還附帶了許多現(xiàn)成的模板,可以作為很好的學(xué)習(xí)資料。最后,CodeSmith 還為實(shí)現(xiàn) CSLA.NET 框架提供了許多模板。我最初只花了幾個(gè)小時(shí)來(lái)學(xué)習(xí)基本概念、熟悉 CodeSmith 的功能,但它為我節(jié)省的時(shí)間已經(jīng)多得無(wú)法計(jì)算了。另外,如果所有的開發(fā)人員都使用相同的模板,代碼的高度一致性將使您能夠輕松地繼續(xù)其他人的工作。

    參考資料:

    • Code Generation with CodeSmith

    • CodeSmith 主頁(yè)

    O/R 映射器

    即使因?yàn)閷?duì) O/R 映射器知之甚少使我不敢隨便對(duì)它們發(fā)表議論,但它們自身的潛在價(jià)值使其不容忽視。代碼生成器生成基于模板的代碼,供您復(fù)制并粘貼到您自己的源代碼中,而 O/R 映射器則在運(yùn)行時(shí)通過某種配置機(jī)制動(dòng)態(tài)生成代碼。例如,在 XML 文件中,您可以指定某個(gè)表的列 X 映射到某個(gè)實(shí)體的屬性 Y。您仍然需要?jiǎng)?chuàng)建自定義實(shí)體,但是集合、映射和其他數(shù)據(jù)訪問函數(shù)(包括存儲(chǔ)過程)都是動(dòng)態(tài)創(chuàng)建的。從理論上講,O/R 映射器幾乎可以完全解決自定義實(shí)體存在的問題。隨著關(guān)系世界和對(duì)象世界的差異越來(lái)越明顯以及映射過程越來(lái)越復(fù)雜,O/R 映射器的價(jià)值就變得越發(fā)不可限量了。O/R 映射器的兩個(gè)缺點(diǎn)據(jù)說(shuō)就是不夠安全和性能較差(至少在 .NET 環(huán)境中是這樣)。根據(jù)我所閱讀的資料,我確信它們并不是不夠安全,雖然在有些情況下性能較差,但在另外一些情況下卻表現(xiàn)突出。O/R 映射器并不適合所有情況,但如果您要處理復(fù)雜的系統(tǒng),則應(yīng)嘗試一下它們的功能。

    參考資料:

    • Mapper 設(shè)計(jì)模式

    • Data Mapper 設(shè)計(jì)模式

    • Wilson ORMapper

    • Frans Bouma 關(guān)于 O/R 映射的帖子

    • LLBGenPro

    • NHibernate

    .NET Framework 2.0 的功能

    即將面世的 .NET Framework 2.0 版將改變我們?cè)诒局改现杏懻摰囊恍?shí)施細(xì)節(jié)。這些改變將減少支持自定義實(shí)體所需的代碼數(shù)量,并有助于處理映射問題。

    泛型

    議論頗多的泛型之所以存在,主要原因之一就是為了向開發(fā)人員提供現(xiàn)成的強(qiáng)類型的集合。我們避開 Arraylist 等現(xiàn)有集合是因?yàn)樗鼈儗儆谌躅愋汀7盒吞峁┝伺c當(dāng)前集合同樣的方便性,而且它們屬于強(qiáng)類型。這是通過在聲明時(shí)指定類型來(lái)實(shí)現(xiàn)的。例如,我們可以替換 UserCollection 而不需要增加代碼,然后只需創(chuàng)建一個(gè) List<T> 泛型的新實(shí)例并指定我們的 User 類即可:

    'Visual Basic .NET Dim users as new IList(of User) //C# IList<User> users = new IList<user>();

    聲明后,我們的 user 集合就只能處理 User 類型的對(duì)象了,這為我們提供了編譯時(shí)檢查和優(yōu)化的所有優(yōu)點(diǎn)。

    參考資料:

    • Introducing .NET Generics

    • An Introduction to C# Generics

    可以為空的類型

    可以為空的類型實(shí)際上就是由于其他原因而非上述原因而使用的泛型。處理數(shù)據(jù)庫(kù)時(shí)面臨的挑戰(zhàn)之一就是正確一致地處理支持 NULL 的列。在處理字符串和其他類(稱為引用類型)時(shí),您只需為代碼中的某個(gè)變量指定 nothing/null

    'Visual Basic .NET if dr("UserName") Is DBNull.Value Then user.UserName = nothing End If //C# if (dr["UserName"] == DBNull.Value){ user.UserName = null; }

    也可以什么都不做(默認(rèn)情況下,引用類型為 nothing/null)。這對(duì)值類型(例如整數(shù)、布爾值、小數(shù)等)并不完全一樣。您當(dāng)然也可以為這些值指定 nothing/null,但這樣將會(huì)指定一個(gè)默認(rèn)值。如果您只聲明整數(shù),或者為其指定 nothing/null,變量的值實(shí)際上將為 0。這使其很難映射回?cái)?shù)據(jù)庫(kù):值究竟為 0 還是 null?可以為空的類型允許值類型具有具體的值或者為空,從而解決了這個(gè)問題。例如,如果我們要在 userId 列中支持 null 值(并不是很符合實(shí)際情況),我們會(huì)首先將 userId 字段和對(duì)應(yīng)的屬性聲明為可以為空的類型:

    'Visual Basic .NET Private _userId As Nullable(Of Integer) Public Property UserId() As Nullable(Of Integer) Get Return _userId End Get Set(ByVal value As Nullable(Of Integer)) _userId = value End Set End Property //C# private Nullable<int> userId; public Nullable<int> UserId { get { return userId; } set { userId = value; } }

    然后利用 HasValue 屬性判斷是否指定了 nothing/null

    'Visual Basic .NET If UserId.HasValue Then Return UserId.Value Else Return DBNull.Value End If //C# if (UserId.HasValue) { return UserId.Value; } else { return DBNull.Value; }

    參考資料:

    • Nullable types in C#

    • Nullable types in VB.NET

    迭代程序

    我們前面討論的 UserCollection 示例只展示了自定義集合中可能需要的基本功能。有一個(gè)操作無(wú)法通過所提供的實(shí)現(xiàn)來(lái)完成,即通過一個(gè) foreach 循環(huán)在集合中循環(huán)。要完成此操作,您的自定義集合必須具有實(shí)現(xiàn) IEnumerable 接口的枚舉數(shù)支持類。這是一個(gè)非常直觀且重復(fù)性較強(qiáng)的過程,但卻引入了更多的代碼。C# 2.0 引入了新的 yield 關(guān)鍵字來(lái)為您處理此接口的實(shí)現(xiàn)細(xì)節(jié)。Visual Basic .NET 中當(dāng)前沒有與新的 yield 關(guān)鍵字等效的關(guān)鍵字。

    參考資料:

    • What's new In C# 2.0 - Iterators

    • C# Iterators

    返回頁(yè)首

    小結(jié)

    請(qǐng)勿輕率地做出向自定義實(shí)體與集合轉(zhuǎn)換的決定。這里有許多需要考慮的因素。例如,您對(duì) OO 概念的熟悉程度、可用來(lái)熟悉新方法的時(shí)間以及您打算部署它的環(huán)境。雖然總體上它們有很大的優(yōu)點(diǎn),但并不一定適合您的特定情況。即使適合您的情況,它們的缺點(diǎn)也可能會(huì)打消您使用它們的念頭。還要記住有許多可替代的解決方案。Jimmy Nilsson 在他的 Choosing Data Containers for .NET 中概述了其中的某些替代方案,此專欄系列包括 5 部分(1、2、3、4、5)。

    自定義實(shí)體使您獲得了面向?qū)ο蟮木幊痰呢S富功能,并幫助您構(gòu)建了可靠、可維護(hù)的 N 層體系結(jié)構(gòu)的框架。本指南的目的之一是讓您從構(gòu)成系統(tǒng)的業(yè)務(wù)實(shí)體,而不是一般的 DataSetDataTable 的角度來(lái)考慮您的系統(tǒng)。我們還討論了一些關(guān)鍵的問題,不管您選擇的途徑(即設(shè)計(jì)模式)、對(duì)象世界與關(guān)系世界的差異(了解詳細(xì)信息)以及 N 層體系結(jié)構(gòu)是什么,您都應(yīng)注意這些問題。請(qǐng)記住,您之前花費(fèi)的時(shí)間會(huì)在系統(tǒng)的整個(gè)生命周期內(nèi)為您帶來(lái)更多的回報(bào)。

    轉(zhuǎn)載于:https://www.cnblogs.com/jobin/articles/1175238.html

    總結(jié)

    以上是生活随笔為你收集整理的掌握 ASP.NET 之路:自定义实体类简介 来源 :msdn的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。