你的位置:首页 > ASP.net教程

[ASP.net教程]第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 提供了很方便的函数,帮助您用数据库或可扩展标记语言 (
  • 4.将 DTO 序列化为一个字节流或字符流(或者相反),以便可以通过网络发送该对象的内容。DTO 有内置的序列化函数。

DataSet 中包含一个 DataTable 对象的集合。每个 DataTable 对象代表使用 SELECT 语句或执行存储过程所检索到的数据。DataSet 中的数据可以作为 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 能够将自身序列化为
  • 断开连接的数据库模型。DataSet 是数据库当前内容的快照。这意味着,您可以更改 DataSet 的内容,并且随后使用 DataSet 作为更新数据库的手段。

缺点

  • 互操作性。由于 DataSet 类是 ADO.NET 的一部分,因此,在需要与不运行 .NET Framework 的客户端进行互操作的情况下,它不是数据传输对象的最佳选择。不过,您仍然可以使用 DataSet,这时,客户端将被强制分析
  • 过期数据。前面已经说明,DataSet 与数据库的连接是断开的。构造它时,会将数据库中数据的快照填入其中。这意味着,数据库中的实际数据可能与 DataSet 中包含的数据不同。如果主要是为了读取静态数据,这就不是重要的问题。不过,如果数据经常发生更改,建议不要使用 DataSet
  • 对数据库架构的依赖性。由于大多数时候 DataSet 需要填入数据库数据,因此任何引用列名的代码都会依赖于数据库架构。另外,因为程序员必须对表之间的关系进行显式编码,所以,如果数据库中的关系发生更改,也必须对代码进行修改。
  • 性能可能降低。对 DataSet 进行实例化和填入数据需要占用大量资源。对 DataSet 进行序列化和反序列化也会非常费时。关于使用 DataSet 的一条经验法则是,当使用一个以上的表或依靠 DataSet 的能力来更新数据库时,DataSet 是一个很好的选择。如果只需要显示来自单个表的结果,并且不需要 DataSet 所提供的功能,则可以考虑使用 DataReader 来加载强类型对象,这样可以获得更好的性能。
  • 非类型安全。您从 DataSet 收到的值可能必须被转换为正确的数据类型。这就要求您判断应该有哪些类型。这个过程可能比较冗长,并且容易产生错误,因为您必须显式检查 DataSet 的类型信息。正如“使用类型化 DataSet”[Microsoft02] 部分所描述的那样,类型化的 DataSet 可以生成从一般 DataSet 类继承来的强类型 DataSet 子类,从而缓解了这个问题。

扩展二级体系结构。使用 DataSet 所带来的方便可能会成为缺点,因为开发人员更愿意直接从数据库将 DataSets 传递到用户界面。这样会使用户界面与物理数据库架构紧密地联系在一起。许多机制都有助于避免这个问题。例如,可以从存储过程向 DataSet 填入数据,以便将 DataSet 结构从物理数据库架构中抽象出来。另外,也可以从 DataSets。这种方式在用户界面、业务逻辑和数据存储之间提供了另一层间接关系。