第5章分佈式系統模式 在 .NET 中使用 DataSet 實現 Data Transfer Object

要在 .NET Framework 中實現分佈式應用程序。客戶端應用程序需要顯示一個窗體,該窗體要求對 ASP.NET Web Service 進行多個調用以滿足單個用戶請求。基於性能方面的考慮,我們發現,進行多個調用會降低應用程序性能。爲了提高性能,需要通過對 Web Service 進行一次調用就能檢索到用戶請求所需的所有數據。

背景信息

注意:以下是在 .NET 中使用類型化 DataSet 實現 Data Transfer Object 中所描述的同一個示例應用程序。

下面是一個簡化的 Web 應用程序,該程序與 ASP.NET Web Service 進行通信,以便將唱片和曲目信息傳遞給用戶。然後,Web Service 調用數據庫來提供客戶端所請求的數據。下面的順序圖顯示了典型頁面的應用程序、Web Service 和數據庫之間的交互。

圖 1 典型用戶請求的行爲

圖 1 說明了滿足整個用戶請求所需的調用順序。第一個調用會檢索唱片信息,第二個調用則檢索特定唱片的曲目信息。此外,Web Service 必須對數據庫進行單獨調用,以檢索所需的信息。

數據庫架構

圖 2 中顯示的示例所使用的架構描述了與 track 記錄具有一對多關係的 recording 記錄。

2 示例應用程序的架構

實現 DTO

提高此用戶請求性能的一個方法是,將所有需要的數據打包到一個 Data Transfer Object (DTO) 中,利用對 Web Service 的一個調用就可以發送該對象。這樣可以減少兩個單獨的調用所關聯的開銷,並且允許您使用與數據庫的單個連接來既檢索唱片信息又檢索曲目信息。

實現策略

Data Transfer Object 在 .NET Framework 中有許多可能的實現方法。要使用 DTO,您必須完成以下四個步驟。令人高興的是,內置在 .NET Framework 中的 DataSet 類已經完成了以下步驟中的三個(實際上,差不多是三個半):

  • 1.設計 DTO 類。此過程中的一個步驟是確定要支持哪些數據類型和結構。DataSet 是最一般的類,足以適用於任何 DTO 用途;因此,您無需爲每個 DTO 設計一個新類。

  • 2.爲數據傳輸類編寫或生成代碼。DataSet 是 .NET 庫的一部分,因此無需爲其編寫代碼。

  • 3.創建 DTO 的實例,然後填入數據。這是唯一需要編程的步驟。DataSet 提供了很方便的函數,幫助您用數據庫或可擴展標記語言 (XML) 文檔的數據加載 DTO,從而極大地簡化了任務。

  • 4.將 DTO 序列化爲一個字節流或字符流(或者相反),以便可以通過網絡發送該對象的內容。DTO 有內置的序列化函數。

DataSet 中包含一個 DataTable 對象的集合。每個 DataTable 對象代表使用 SELECT 語句或執行存儲過程所檢索到的數據。DataSet 中的數據可以作爲 XML 寫出和讀取。DataSet 還存儲了架構信息、約束以及多個 DataTable 對象之間的關係。通過 DataSet,可以添加、編輯和刪除數據;因此,DataSet 成爲 .NET Framework 中理想的數據傳輸對象,尤其是需要在窗體控件中顯示 DataSet 的時候。

因爲 .NET Framework 已經實現了 DataSet,因此,該實現策略的其餘部分的重點是如何從數據源向 DataSet 填入數據,以及如何在 Web 窗體中使用所產生的 DataSet

從數據庫向 DataSet 填入數據

本示例說明如何使用數據庫查詢將示例應用程序所需要的數據填入 DataSet。其中包括 recording 記錄,以及 recordingId 所關聯的所有 track 記錄。

Assembler.cs

Assembler 類是 Mapper 模式 [Fowler03] 的特殊化實例。其用途是將 DTO 與系統的其餘部分隔離開。下面的代碼示例顯示瞭如何從數據庫創建 DTO:

using System;
using System.Data;
using System.Data.SqlClient;
public class Assembler
{
   public static DataSet CreateRecordingDto(long id)
   {
      string selectCmd =
         String.Format(
         "select * from recording where id = {0}",
         id);
      SqlConnection myConnection =
         new SqlConnection(
         "server=(local);database=recordings;Trusted_Connection=yes");
      SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd,
         myConnection);
      DataSet ds = new DataSet();
      myCommand.Fill(ds, "recording");
      String trackSelect =
         String.Format(
         "select * from Track where recordingId = {0} order by Id",
         id);
      SqlDataAdapter trackCommand =
         new SqlDataAdapter(trackSelect, myConnection);
      trackCommand.Fill(ds, "track");
      ds.Relations.Add("RecordingTracks",
         ds.Tables["recording"].Columns["id"],
         ds.Tables["track"].Columns["recordingId"]);
      return ds;
   }
}

上面的代碼有一些應該注意的地方。您需要執行查詢來填寫唱片表和曲目表。還必須顯式定義這兩個表之間的關係,即使數據庫中已定義了該關係。

注意:這裏顯示的示例並不是向 DataSet 填入數據的唯一方式。有許多從數據庫檢索此數據的方式。例如,您可以使用存儲過程。

ASP.NET 頁中使用 DataSet

使用 .NET 用戶界面控件(Web 窗體或 Windows 窗體)時,DataSet 是一個非常合適的選擇。例如,示例應用程序頁使用兩個 DataGrid 控件:RecordingGridTrackGrid。因爲您需要既檢索唱片又檢索唱片的曲目,所以,最好使用包含多個表的單個 DataSet

如果 DataSetAssembler 類構建的,那麼,該代碼將顯示如何將 DataSet 指定給兩個網格控件的 DataSource 屬性。

using System;
using System.Data;
public class RetrieveForm : System.Web.UI.Page
{
   private RecordingCatalog catalog = new RecordingCatalog();
   //
   protected void Button1_Click(object sender, System.EventArgs e)
   {
      string stringId = TextBox1.Text;
      long id = Convert.ToInt64(stringId);
      DataSet ds = catalog.Get(id);
      RecordingGrid.DataSource = ds.Tables["recording"];
      RecordingGrid.DataBind();
      TrackGrid.DataSource = ds.Tables["track"];
      TrackGrid.DataBind();
   }
}

測試

因爲 DataSet 是 .NET Framework 提供的,所以您無需編寫測試來驗證它是否能正常運行。您可能會對此表示懷疑,但您應該假設:除非有經過證明的證據,否則 Framework 提供的類都是正確無誤的,因此,您需要測試的是組裝 DataSet 的代碼,在這裏就是 Assembler 類。

RecordingAssemblerFixture.cs

該硬件將測試是否已填入 DataSet 的內容,以及是否正確定義了唱片和曲目之間的關係:

using NUnit.Framework;
using System.Data;
[TestFixture]
public class RecordingAssemblerFixture
{
   private DataSet ds;
   private DataTable recordingTable;
   private DataRelation relationship;
   private DataRow[] trackRows;
   [SetUp]
   public void Init()
   {
      ds = Assembler.CreateRecordingDto(1234);
      recordingTable = ds.Tables["recording"];
      relationship = recordingTable.ChildRelations[0];
      trackRows = recordingTable.Rows[0].GetChildRows(relationship);
   }
   [Test]
   public void RecordingCount()
   {
      Assert.Equals(1, recordingTable.Rows.Count);
   }
   [Test]
   public void RecordingTitle()
   {
      DataRow recording = recordingTable.Rows[0];
      string title = (string)recording["title"];
      Assert.Equals("Up", title.Trim());
   }
   [Test]
   public void RecordingTrackRelationship()
   {
      Assert.Equals(10, trackRows.Length);
   }
   [Test]
   public void TrackContent()
   {
      DataRow track = trackRows[0];
      string title = (string)track["title"];
      Assert.Equals("Darkness", title.Trim());
   }
   [Test]
   public void InvalidRecording()
   {
      DataSet ds = Assembler.CreateRecordingDto(-1);
      Assert.Equals(0, ds.Tables["recording"].Rows.Count);
      Assert.Equals(0, ds.Tables["track"].Rows.Count);
   }
}

這些測試說明了如何訪問 DataSet 的各個元素。測試本身也會暴露一些問題,這是因爲您需要知道列名以及對象的類型而產生的。由於這種直接的依賴性,如果數據庫架構發生更改,該代碼也必須更 改。這些類型的問題在您使用類型化的 DataSet 時會有所緩解。有關詳細信息,請參閱“在 .NET 中使用類型化 DataSet 實現 Data Transfer Object”。

結果上下文

下面列出了使用 DataSet 作爲數據傳輸對象的優缺點:

優點

  • 開發工具支持。DataSet 類是在 ADO.NET 中實現的,因此,無需設計和實現數據傳輸對象。Microsoft Visual Studio? 版本 6.0 開發系統中的擴展支持也可以用於自動創建 DataSet 對象和填入數據。

  • 與控件集成。DataSet 直接與 Windows 窗體和 Web 窗體中的內置控件協作,這使得它成爲理想的數據傳輸對象選擇。

  • 序列化。DataSet 能夠將自身序列化爲 XML。它不但可以對內容進行序列化,還能在序列化中表現內容的架構。

  • 斷開連接的數據庫模型。DataSet 是數據庫當前內容的快照。這意味着,您可以更改 DataSet 的內容,並且隨後使用 DataSet 作爲更新數據庫的手段。

缺點

  • 互操作性。由於 DataSet 類是 ADO.NET 的一部分,因此,在需要與不運行 .NET Framework 的客戶端進行互操作的情況下,它不是數據傳輸對象的最佳選擇。不過,您仍然可以使用 DataSet,這時,客戶端將被強制分析 XML 語法,並構建它自己的表現形式。如果必須具有互操作性,請參閱“在 .NET 中使用序列化對象實現 Data Transfer Object”。

  • 過期數據。前面已經說明,DataSet 與數據庫的連接是斷開的。構造它時,會將數據庫中數據的快照填入其中。這意味着,數據庫中的實際數據可能與 DataSet 中包含的數據不同。如果主要是爲了讀取靜態數據,這就不是重要的問題。不過,如果數據經常發生更改,建議不要使用 DataSet

  • 對數據庫架構的依賴性。由於大多數時候 DataSet 需要填入數據庫數據,因此任何引用列名的代碼都會依賴於數據庫架構。另外,因爲程序員必須對錶之間的關係進行顯式編碼,所以,如果數據庫中的關係發生更改,也必須對代碼進行修改。

  • 性能可能降低。對 DataSet 進行實例化和填入數據需要佔用大量資源。對 DataSet 進行序列化和反序列化也會非常費時。關於使用 DataSet 的一條經驗法則是,當使用一個以上的表或依靠 DataSet 的能力來更新數據庫時,DataSet 是一個很好的選擇。如果只需要顯示來自單個表的結果,並且不需要 DataSet 所提供的功能,則可以考慮使用 DataReader 來加載強類型對象,這樣可以獲得更好的性能。

  • 非類型安全。您從 DataSet 收到的值可能必須被轉換爲正確的數據類型。這就要求您判斷應該有哪些類型。這個過程可能比較冗長,並且容易產生錯誤,因爲您必須顯式檢查 DataSet 的類型信息。正如“使用類型化 DataSet”[Microsoft02] 部分所描述的那樣,類型化的 DataSet 可以生成從一般 DataSet 類繼承來的強類型 DataSet 子類,從而緩解了這個問題。

擴展二級體系結構。使用 DataSet 所帶來的方便可能會成爲缺點,因爲開發人員更願意直接從數據庫將 DataSets 傳遞到用戶界面。這樣會使用戶界面與物理數據庫架構緊密地聯繫在一起。許多機制都有助於避免這個問題。例如,可以從存儲過程向 DataSet 填入數據,以便將 DataSet 結構從物理數據庫架構中抽象出來。另外,也可以從 XML 文檔(可以使用可擴展樣式表語言 (XSL) 進行轉換)加載 DataSets。這種方式在用戶界面、業務邏輯和數據存儲之間提供了另一層間接關係。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章