第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。这种方式在用户界面、业务逻辑和数据存储之间提供了另一层间接关系。

时间: 2024-08-27 20:34:45

第5章分布式系统模式 在 .NET 中使用 DataSet 实现 Data Transfer Object的相关文章

第5章分布式系统模式 Data Transfer Object(数据传输对象)

正在设计一个分布式应用程序,为了满足单个客户端请求,您发现自己对一个远程接口发出了多个调用,而这些调用所增加的响应时间超出了可接受的程度. 影响因素 在与远程对象通信时,请考虑下列需要权衡的因素: 远程调用(那些必须跨越网络的调用)速度缓慢.虽然许多远程调用框架可以隐藏进行远程调用的复杂性,但是它们不能消除发生通信所需的步骤.例如,必须先找到 远程对象位置,而且建立与远程计算机的连接,然后才能将数据串行化为字节流,然后可能进行加密,最后才能将其传输到远程计算机. 在 考虑网络性能时,必须同时考虑

第5章分布式系统模式

在当今的互联世界中,越来越多的企业应用程序跨多个服务器分布和运行.连接到远程数据源和 Web Service,并可通过 Internet 访问.分布式计算功能强大,但也并非没有面临挑战.网络在本质上并不可靠,同本地的进程间通信相比,与远程服务器的通信速度较慢.另外,同时在多台计算机 上运行一个程序可能会导致许多并发和同步问题. 基于实例的协作和基于服务的协议 按照 Business Component Factory(业务组件工厂)的说,分布式计算可以基于两个截然不同的体系结构样式: 基于实例的

第5章分布式系统模式 使用服务器激活对象通过 .NET Remoting 实现 Broker

正在使用 Microsoft? .NET Framework 构建一个需要使用分布式对象的应用程序.您的要求包括能够按值或按引用来传递对象,无论这些对象驻留在同一台计算机上,还是驻留在同一个局域网 (LAN) 中的不同计算机上,或者是驻留在广域网 (WAN) 中的不同计算机上.应用程序不需要您显式控制远程对象的生存期. 关于 .NET Remoting 的背景信息 远 程处理使用对象引用来进行客户端和服务器之间的通信.在服务器激活情况下,客户端使用远程处理基础结构 (Activator.GetO

第5章分布式系统模式 使用客户端激活对象通过 .NET Remoting 实现 Broker

正在 .NET 中构建一个需要使用分布式对象的应用程序,并且分布式对象的生存期由客户端控制.您的要求包括能够按值或按引用来传递对象,无论这些对象驻留在同一台计算 机上,还是驻留在同一个局域网 (LAN) 中的不同计算机上,或者是驻留在广域网 (WAN) 中的不同计算机上. 实现策略 这 种模式为在 .NET Remoting 中实现客户端激活对象提供了两种实现方式.客户端激活对象 (CAO) 和服务器激活对象 (SAO) 之间的主要区别在于,是什么控制着远程对象的生存期.在使用 CAO 的情况下

第5章分布式系统模式 Broker(代理程序)

许多复杂的软件系统运行在多个处理器或分布式计算机上.将软件分布在多台计算机上的原因有多种,例如: 分布式系统可以利用多个 CPU 或一群低成本计算机的计算能力. 某个软件可能仅在特定计算机上可用. 出于安全考虑,软件的各部分可能必须运行在不同的网段上. 一些服务可能是由业务合作伙伴提供的,并且只能通过 Internet 进行访问. 但是,实现分布式系统是不容易的,因为您必须处理诸如并发性.跨平台连接和不可靠网络连接之类的问题. 影响因素 在构建分布式系统时,必须协调下列影响因素: 虽 然分布式系

第6章 服务模式 在 .NET 中实现 Service Gateway(服务网关)

上下文 您正在设计企业应用程序,该程序需要使用由其他应用程序提供的服务.该服务定义了一个合约,所有服务使用者要访问该服务都必须遵守该合约.该合约定义了与此服务通信所需的技术.通信协议和消息定义等内容.要与该服务通信,应用程序需要按合约中的详细说明履行其责任. 问题 如何将该服务所规定的履行合约责任的细节与应用程序的其余部分分隔开来? 影响因素 在设计使用由其他应用程序提供的服务的应用程序时,必须考虑下列影响因素: 履行使用者的合约责任需要实现安全和通信机制,例如验证.封送.加密和消息路由.这些机

第5章分布式系统模式 Singleton

上下文 在某些情况下,特定类型的数据需要提供给应用程序中的其他所有对象使用.在大多数情况下,这种类型的数据在系统中还是唯一的.例如,用户界面只能有一个所有应用程序必须访问的鼠标指针.同样,企业解决方案可能用单网关对象作为接口来管理与特定旧系统的连接. 影响因素 以下因素影响这种情况中的系统,在考虑上述问题的解决方案时必须协调这些影响因素: 很多编程语言(例如 Microsoft Visual Basic® 6.0 版或 C++)都支持全局对象的定义.这些对象位于命名空间的根部,应用程序中所有对象

使用Micrisoft.net设计方案 第二章组织模式

模式不仅依赖于它所包含的更小模式,同时也依赖包含它的更大的模式.它是描述复杂软件的系统方法. 本章的目标是让我们了解以下问题: 1.如何标识模式与模式的关系 2.如何把模式组织成模式集合 3.如何采用不同抽象级别去划分模式 4.如何使用模式解决系统中涉及到的各个方面 5.如何用模式描述解决方案 模式与模式 模式能够描述关系.采用面向对象设计的软件都是有类组成,如果抛开类与类间的关系,模式将什么问题也不能解决.模式把一组类组织成便于管理的模式集合. 我们设计系统时,会发现使用的模式比使用的类都多,

第 7 章 门面模式【Facade Pattern】

以下内容出自:<<24种设计模式介绍与6大设计原则>> 好,我们继续讲课.大家都是高智商的人,都写过纸质的信件吧,比如给女朋友写情书什么的,写信的过程大家都还记得吧,先写信的内容,然后写信封,然后把信放到信封中,封好,投递到信箱中进行邮递,这个过程还是比较简单的,虽然简单,这四个步骤都是要跑的呀,信多了还是麻烦,比如到了情人节,为了大海捞针,给十个女孩子发情书,都要这样跑一遍,你不要累死,更别说你要发个广告信啥的,一下子发1 千万封邮件,那不就完蛋了?那怎么办呢?还好,现在邮局开发