在 .NET 中使用类型化 DataSet 实现 Data Transfer Object

来源:互联网 发布:海关数据怎么分析 编辑:程序博客网 时间:2024/05/10 13:30

http://msdn.microsoft.com/zh-cn/library/ms998529.aspx

 

上下文

您要实现 .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 Data Transfer Object 模式。

实现策略

类型化 DataSet 是 System.Data.DataSet 的生成子类。您应当提供一个 XML 架构文件,以便随后用于生成包装 DataSet 的强类型包装器。以下两段代码示例说明了它们之间的区别。第一个示例是利用普通 DataSet 实现的:

DataTable dataTable = dataSet.Tables["recording"]; 
DataRow row = dataTable.Rows[0];
string artist = (string)row["artist"];

该示例说明,您需要知道表和列的名称才能访问 DataSet 中包含的表和字段。您还必须知道 Artist 字段的返回类型,以确保正确完成转换。如果不使用正确的类型,将出现运行时错误。下面是利用类型化 DataSet 实现的同一个示例:

   Recording recording = typedDataSet.Recordings[0]; 
string artist = recording.Artist;

该示例展示了类型化接口所提供的优点。您不必再通过名称来引用表或列,并且不必知道 Artist 列的返回类型是字符串。类型化 DataSet 定义了更为明确的接口,这样的接口在编译时而不是在运行时是可验证的。

除了强类型接口,类型化 DataSet 还用在所有可以使用 DataSet 的地方;因此,它也可以用作 DTO。它的加载方式与 DataSet 类似,并且可以序列化为 XML 或者相反。与普通的 DataSet 相比,您必须编写和维护用来描述类型化接口的 XML 架构。Microsoft Visual Studio? .NET 开发系统提供了许多工具来简化该架构的创建和维护。

该实现策略的其余部分概括了在为刚才描述的示例应用程序创建类型化 DataSet 时所需的步骤。

创建类型化 DataSet

类型化 DataSet 是从 XML 架构生成的。Visual Studio .NET 提供了一个拖放工具,该工具可以自动创建该架构并生成类型化 DataSet 类(请参阅图 3)。如果不使用 Visual Studio.NET,则可以自己编写 XML 架构,并使用名为 XSD.exe 的命令行工具来生成类型化 DataSet。有关这两种方法的详细说明,请参阅 2001 年 5 月的 .NET Developer 中的“Typed DataSets in ADO.NET”(ADO.NET 中的类型化 DataSet)[Wildermuth02]。

3 Visual Studio .NET DataSet 文件类型

RecordingDto.xsd

下面是本示例中要使用的 DTO 的 XML 架构。它将唱片表及其关联的曲目记录组合在一个单独的、名为 RecordingDto 的类型化 DataSet 中:

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema id="RecordingDto" targetNamespace="http://msdn.microsoft.com/practices/RecordingDto.xsd"
elementFormDefault="qualified" attributeFormDefault="qualified" xmlns="http://tempuri.org/RecordingDTO.xsd"
xmlns:mstns="http://msdn.microsoft.com/practices/RecordingDto.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:codegen="urn:schemas-microsoft-com:xml-msprop">
<xs:element name="RecordingDto" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="recording" codegen:typedName="Recording" codegen:typedPlural="Recordings"
codegen:typedChildren="Track">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:long" codegen:typedName="Id" />
<xs:element name="title" type="xs:string" codegen:typedName="Title" />
<xs:element name="artist" type="xs:string" codegen:typedName="Artist" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="track" codegen:typedName="Track" codegen:typedPlural="Tracks" codegen:typedParent="Recording">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:long" codegen:typedName="Id" />
<xs:element name="title" type="xs:string" codegen:typedName="Title" />
<xs:element name="duration" type="xs:string" codegen:typedName="Duration" />
<xs:element name="recordingId" type="xs:long" codegen:typedName="RecordingId" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:unique name="RecordingDTOKey1" msdata:PrimaryKey="true">
<xs:selector xpath=".//mstns:recording" />
<xs:field xpath="mstns:id" />
</xs:unique>
<xs:unique name="RecordingDTOKey2" msdata:PrimaryKey="true">
<xs:selector xpath=".//mstns:track" />
<xs:field xpath="mstns:id" />
</xs:unique>
<xs:keyref name="recordingtrack" refer="mstns:RecordingDTOKey1">
<xs:selector xpath=".//mstns:track" />
<xs:field xpath="mstns:recordingId" />
</xs:keyref>
</xs:element>
</xs:schema>

该架构与 Visual Studio .NET 生成的文件并不完全一样。它包含了以 codegen 命名空间为前缀的许多属性。这种修改是很有必要的,因为生成的代码不遵守 .NET 命名约定。例如,如果不进行修改,Visual Studio .NET 会生成与曲目表相对应的 track 类,而按照 .NET Framework 中使用的约定,该类应该命名为 Track。要更改生成的类的名称,必须将 codegen:typedName 属性添加到 XML 架构内的元素定义中:

<xs:element name="track" codegen:typedName="Track"> 
</element>

除了 codegen:typedName,还有许多其他属性。有关所有属性的详细说明,请参阅 2001 年 5 月的 .NET Developer 中的“Typed DataSets in ADO.NET”(ADO.NET 中的类型化 DataSet)[Wildermuth02]。

从数据库向类型化 DataSet 填入数据

以下代码示例展示如何向类型化 DataSet 填入示例应用程序所需的数据。其中包括特定的 recording 记录,以及所有与其关联的 track 记录。该代码与填写普通 DataSet 之间的区别是,您无需显式地定义唱片记录和曲目记录之间的关系。

Assembler.cs

与“在 .NET 中使用 DataSet 实现 Data Transfer Object”一样,Assembler 类将把实际的数据库调用映射到类型化 DataSet 中:

using System; 
using System.Data;
using System.Data.SqlClient;
using Recording;
public class Assembler
{
public static RecordingDto 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);
RecordingDto dto = new RecordingDto();
myCommand.Fill(dto, "recording");
String trackSelect =
String.Format(
"select * from Track where recordingId = {0} order by Id",
id);
SqlDataAdapter trackCommand =
new SqlDataAdapter(trackSelect, myConnection);
trackCommand.Fill(dto, "track");
return dto;
}
}

注意:这里显示的示例并不是向 DataSet 填入数据的唯一方式。有许多从数据库检索此数据的方式。例如,您可以使用存储过程。

ASP.NET 页中使用类型化 DataSet

前面已经提到,类型化 DataSet 是从 System.Data.DataSet 继承而来的。这意味着它可以替代 DataSet。例如,使用 .NET 用户界面控件(Web 窗体或 Windows 窗体)时,可以在所有可使用 DataSet 的地方使用类型化 DataSet 。以下代码示例中显示的示例应用程序页使用了两个 DataGrid 控件:RecordingGridTrackGrid。设置控件的 DataSource 属性时,由于类型化 DataSet 是从 DataSet 继承而来的,因此可以使用 RecordingDto 这个类型化 DataSet

using System; 
using System.Data;
using RecordingApplication.localhost;
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);
RecordingDTO dto = catalog.Get(id);
RecordingGrid.DataSource = dto.recording;
RecordingGrid.DataBind();
TrackGrid.DataSource = dto.track;
TrackGrid.DataBind();
}
}

测试

因为类型化 DataSet 是用 .NET Framework 工具生成的,所以无需编写测试来验证它是否能正常运行。在下面的测试中,您将测试 Assembler 类是否能正确加载类型化 DataSet

AssemblerFixture.cs

using NUnit.Framework; 
using System.Data;
using Recording;
[TestFixture]
public class AssemblerFixture
{
private RecordingDto dto;
private RecordingDto.Recording recording;
private RecordingDto.Track[] tracks;
[SetUp]
public void Init()
{
dto = Assembler.CreateRecordingDto(1234);
recording = dto.Recordings[0];
tracks = recording.GetTracks();
}
[Test]
public void RecordingCount()
{
Assert.Equals(1, dto.Recordings.Rows.Count);
}
[Test]
public void RecordingTitle()
{
Assert.Equals("Up", recording.Title.Trim());
}
[Test]
public void RecordingChild()
{
Assert.Equals(10, tracks.Length);
foreach(RecordingDto.Track track in tracks)
{
Assert.Equals(recording.Id, track.RecordingId);
}
}
[Test]
public void TrackParent()
{
RecordingDto.Track track = tracks[0];
RecordingDto.Recording parent = track.Recording;
Assert.Equals("Up", parent.Title.Trim());
}
[Test]
public void TrackContent()
{
RecordingDto.Track track = tracks[0];
Assert.Equals("Darkness", track.Title.Trim());
}
[Test]
public void InvalidRecording()
{
RecordingDto dto = Assembler.CreateRecordingDto(-1);
Assert.Equals(0, dto.Recordings.Rows.Count);
Assert.Equals(0, dto.Tracks.Rows.Count);
}
}

这些测试说明了如何访问 DataSet 的各个元素。由于使用了类型化 DataSet,因此测试代码不需要实际的列名,也不需要转换返回类型。将这些测试与“在 .NET 中使用 DataSet 实现 Data Transfer Object”中所描述的测试进行比较后,可以发现使用强类型接口和使用一般接口之间的区别。强类型接口更易于使用和理解。它还提供了另外一个优点,这就是对返回类型进行编译时检查。

返回页首

结果上下文

与使用 DataSet 实现 DTO 相比,使用类型化 DataSet 实现 DTO 有许多相同的优缺点;不过,某些优缺点是类型化 DataSet 实现所独有的。

优点

当用作 DTO 时,类型化 DataSet 具有以下 DataSet 的优点:

  • 开发工具支持。由于 DataSet 类是在 ADO.NET 中实现的,因此,无需设计和实现 DTO。Visual Studio 中的扩展支持也可以用于自动创建和填充 DataSet 对象和类型化 DataSet 对象。

  • 与控件集成DataSet 直接与 Windows 窗体和 Web 窗体中的内置控件协作,这使它成为理想的数据传输对象选择。

  • 序列化。DataSet 能够将自身序列化为 XML。它不但可以对内容进行序列化,还能在序列化中表现内容的架构。

  • 断开连接的数据库模型DataSet 代表了数据库当前内容的快照。这意味着,您可以更改 DataSet 的内容,并且随后使用 DataSet 作为更新数据库的手段。

  • 可以说服您不使用普通 DataSet 而使用类型化 DataSet 的另一个优点是类型化 DataSet 的强类型接口。正如这里所描述的,类型化 DataSet 所生成的类可用于访问所包含的数据。这些类提供的接口定义了如何以更显式的方式使用类。这样,您就无需进行在 DataSet 实现中所需的转换。

缺点

在 DTO 上下文中使用时,类型化 DataSet 具有以下 DataSet 的缺点

  • 互操作性。由于 DataSet 类是 ADO.NET 的一部分,因此,在要求与没有运行 .NET Framework 的客户端进行互操作的情况下,它不是数据传输对象的最佳选择。不过,您仍然可以使用 DataSet,这时,客户端将被强制分析 XML 语法,并构建它自己的表现形式。如果必须具有互操作性,请参阅“使用序列化对象在 .NET 中实现 Data Transfer Object”。

  • 过期数据。类型化 DataSetDataSet 一样,与数据库是断开连接的。构造它时,会将数据库中数据的快照填入其中。这意味着,数据库中的实际数据可能与类型化 DataSet 中包含的数据不同。如果主要是为了读取静态数据,这就不是重要的问题。不过,如果数据经常发生更改,建议不要使用任何一种 DataSet

  • 性能可能降低。对 DataSet 进行实例化和填入数据需要占用大量资源。对 DataSet 进行序列化和反序列化也会非常费时。关于使用 DataSet 的一条经验法则是,当使用一个以上的表或依靠 DataSet 的能力来更新数据库时,DataSet 是一个很好的选择。如果只需显示来自一个表的结果,那么,将 DataReader 与强类型对象一起使用可能会实现更高的性能。有关详细信息,请参阅“使用序列化对象在 .NET 中实现 Data Transfer Object”。

  • 下面是不使用普通 DataSet 而使用类型化 DataSet 时的其他缺点:

  • 类型化 DataSet 仍然是 DataSet。 类型化 DataSet 可以在运行时用 DataSet 来替代。这意味着,即使存在强类型接口,程序员仍然可以在不使用类型化接口的情况下访问数据。这种做法可能产生的结果是,可能有部分代码将应用程序与 DataSet 表和列名紧密联系起来。

  • 需要 XML 架构。使用类型化 DataSet 时,您必须创建和维护一个用来描述强类型接口的 XML 架构。Visual Studio .NET 提供了许多工具来帮助您完成这项工作,不过,您仍然必须维护一个额外的文件。