实现业务实体
业务实体具有以下特点:
业务实体提供对业务数据及相关功能(在某些设计中)的状态编程访问。
业务实体可以使用具有复杂架构的数据来构建。这种数据通常来自数据库中的多个相关表。
业务实体数据可以作为业务过程的部分 I/O 参数传递。
业务实体可以是可序列化的,以保持它们的当前状态。例如,应用程序可能需要在本地磁盘、桌面数据库(如果应用程序脱机工作)或消息队列消息中存储实体数据。
业务实体不直接访问数据库。全部数据库访问都是由相关联的数据访问逻辑组件提供的。
业务实体不启动任何类型的事务处理。事务处理由使用业务实体的应用程序或业务过程来启动。
如本文前面所述,在您的应用程序中表示业务实体的方法有很多(从以数据为中心的模型到更加面向对象的表示法):
XML
通用 DataSet
有类型的 DataSet
自定义业务实体组件
带有 CRUD 行为的自定义业务实体组件
以下各节将介绍如何使用这些格式来表示业务实体。为帮助您确定特定环境中最适宜的业务实体表示,以下各节将介绍如何为各业务实体格式执行以下任务:
组织业务实体集合
将业务实体数据绑定到用户界面控件
序列化业务实体数据
在层间传递业务实体数据
以下各节还针对非功能性要求(包括性能、效率、可缩放性和可扩展性)考虑了每种业务实体表示的适用性。
将业务实体表示为 XML
以下示例显示了如何将一个简单的业务实体表示为 XML。该业务实体包含一个产品。
<?xml version="1.0"?>
<Product xmlns="urn:aUniqueNamespace">
<ProductID>1</ProductID>
<ProductName>Chai</ProductName>
<QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit>
<UnitPrice>18.00</UnitPrice>
<UnitsInStock>39</UnitsInStock>
<UnitsOnOrder>0</UnitsOnOrder>
<ReorderLevel>10</ReorderLevel>
</Product>
当使用 XML 表示业务实体数据时,请考虑以下原则:
确定 XML 文档是包含单个业务实体还是包含一个业务实体集合。前面的示例表示的是单个 Product 业务实体。
使用一个命名空间唯一标识该 XML 文档,以避免与其他 XML 文档的内容发生命名冲突。前面的示例使用名为 urn:aUniqueNamespace 的默认命名空间。
为元素和属性选择合适的名称。前面的示例使用 Product 表的列名称,但并不要求一定这样。可以选择对您的应用程序有意义的名称。
使用以下方法之一以 XML 格式检索您的业务实体:
如果您使用的是 SQL Server 2000,则可以在查询或存储过程中使用 FOR XML 子句。在性能测试中,使用 FOR XML 只比返回 DataSet 稍微快一点。
检索 DataSet 并将其转换为 XML 流或以 XML 流的格式写出。这种方法会带来创建 DataSet 的系统开销和额外的转换开销(如果执行转换)。
使用输出参数或数据读取器构建一个 XML 文档。数据读取器是从数据库检索多个行的最快方法,但与构建 XML 相关联的过程可能会减弱这种性能优势。
将业务实体表示为 XML 的优点如下:
标准支持。XML 是 World Wide Web Consortium (W3C) 的标准数据表示格式。
灵活性。XML 能够表示信息的层次结构和集合。
互操作性。在所有平台上,XML 都是与外部各方及贸易伙伴交换信息的理想选择。如果 XML 数据将由 ASP.net 应用程序或 Windows 窗体应用程序使用,则还可以把这些 XML 数据装载到一个 DataSet 中,以利用 DataSet 提供的数据绑定支持。
将业务实体表示为 XML 的缺点如下:
类型保真。XML 不支持类型保真。然而,对于简单的数据分类可以使用 XSD 架构。
验证 XML。要验证 XML,可以手动分析代码,或者使用 XSD 架构。但这两种方法都比较慢。
显示 XML。您不能将 XML 数据自动显示在用户界面上。可以编写一个 XSLT 样式表将数据转换为 DataSet;但样式表的编写比较麻烦。另一种方法是通过样式表将 XML 转换为 HTML 等可显示格式。
分析 XML。要分析 XML,可以使用文档对象模型 (DOM) 或 Microsoft .NET Framework 类库提供的 XmlReader 类。XmlReader 提供对 XML 数据的快速只读的、仅向前的访问,而 DOM 可以提供随机读/写访问,因此更灵活。然而,使用 DOM 分析 XML 文档的速度较慢;您必须创建一个 XmlDocument 实例(或另一个 XML 分析器类)并把整个 XML 文件装载到内存中。
排序 XML。您不能自动排序 XML 数据,而应使用以下技术之一:
按预先排好的顺序提供数据。这种方法不支持在调用应用程序中动态重新排序数据。
应用 XSLT 样式表动态排序数据。如果需要,可以使用 DOM 在运行时改变 XSLT 样式表中的排序条件。
将 XML 数据转换为 DataSet,并使用 DataView 对象排序和搜索数据元素。
使用专用字段。您不能选择隐藏信息。
将业务实体表示为通用 DataSet
通用 DataSet 是 DataSet 类的实例,它是在 ADO.NET 的 System.Data 命名空间中定义的。DataSet 对象包含一个或多个 DataTable 对象,用以表示数据访问逻辑组件从数据库检索到的信息。
图 7 所示为用于 Product 业务实体的通用 DataSet 对象。该 DataSet 对象具有一个 DataTable,用于保存产品信息。该 DataTable 具有一个 UniqueConstraint 对象,用于将 ProductID 列标记为主键。DataTable 和 UniqueConstraint 对象是在数据访问逻辑组件中创建该 DataSet 时创建的。
图 7:用于 Product 业务实体的通用 DataSet
图 8 所示为用于 Order 业务实体的通用 DataSet 对象。此 DataSet 对象具有两个 DataTable 对象,分别保存订单信息和订单详细信息。每个 DataTable 具有一个对应的 UniqueConstraint 对象,用于标识表中的主键。此外,该 DataSet 还有一个 Relation 对象,用于将订单详细信息与订单相关联。
图 8:用于 Order 业务实体的通用 DataSet
以下代码显示了如何从数据访问逻辑组件检索通用 DataSet ,然后将该 DataSet 绑定到 DataGrid 控件,再将该 DataSet 传递到数据访问逻辑组件以保存对数据所做的更改:
// 创建 ProductDALC 对象
ProductDALC dalcProduct = new ProductDALC();
// 对 ProductDALC 调用一个方法以获取一个包含全部产品信息的 DataSet
DataSet dsProducts = dalcProduct.GetProducts();
// 在客户端中使用 DataSet。 例如,把该 DataSet 绑定到用户界面控件
dataGrid1.DataSource = dsProducts.Tables[0].DefaultView;
dataGrid1.DataBind();
// 然后,把更新后的 DataSet 传递给 ProductDALC,将更改
// 保存到数据库
dalcProduct.UpdateProducts(dsProducts);
您还可以在运行时查询和修改 DataSet 中的表、约束及关系。
将业务实体表示为通用 DataSet 的优点如下:
灵活性。DataSet 可以包含数据的集合,能够表示复杂的数据关系。
序列化。在层间传递时,DataSet 本身支持序列化。
数据绑定。可以把 DataSet 绑定到 ASP.NET 应用程序和 Windows 窗体应用程序的任意用户界面控件。
排序与过滤。可以使用 DataView 对象排序和过滤 DataSet。应用程序可以为同一个 DataSet 创建多个 DataView 对象,以便用不同方式查看数据。
与 XML 的互换性。可以用 XML 格式读写 DataSet。这种方法在远程和脱机应用程序中很有用,它可以用 XML 格式接收 DataSet,然后在本地重新创建该 DataSet 对象。应用程序在与数据库断开连接后,还可以将 DataSet 保持为 XML 格式。
元数据的可用性。可以用 XSD 架构的形式为 DataSet 提供完整的元数据。还可以使用 DataSet、DataTable、DataColumn、Constraint 和 Relation 类中的方法以编程方式为 DataSet 获取元数据。
开放式并发。在更新数据时,可以配合使用数据适配器与 DataSet 以方便地执行开放式并发检查。
可扩展性。如果修改了数据库架构,则适当情况下数据访问逻辑组件中的方法可以创建包含修改后的 DataTable 和 DataRelation 对象的 DataSet。数据访问逻辑组件方法签名并不改变。可以将调用应用程序修改为使用该 DataSet 中的这些新元素。
将业务实体表示为通用 DataSet 的缺点如下:
客户端代码必须通过 DataSet 中的集合访问数据。要访问 DataSet 中的表,客户端代码必须使用整数索引生成器或字符串索引生成器来索引 DataTable 集合。要访问特定列,必须使用列编号或列名称索引 DataColumn 集合。以下示例显示了如何访问 Products 表中第一行的 ProductName 列:
// 获取所调用的名为 dsProducts 的 DataSet 的第一行的
// 产品名称。 注意,该集合是基于零的。
String str = (String)dsProducts.Tables["Products"].Rows[0]["ProductName"];
...
注意:这里没有这些索引生成器的编译时检查。如果指定一个无效的表名称、列名称或列类型,会在运行时捕获该错误。使用通用 DataSet 时不支持 IntelliSense。
实例化和封送处理的成本很高。DataSet 需要创建多个子对象(DataTable、DataRow 和 DataColumn),这意味着在实例化和封送处理时,DataSet 会比 XML 字符串或自定义实体组件花费更长的时间。随着数据量的增大,创建 DataSet 内部结构的系统开销将明显少于将数据填充到 DataSet 中所需的开销,因此 DataSet 的相对性能会随之提高。
专用字段。您不能选择隐藏信息。
将业务实体表示为有类型的 DataSet
有类型的 DataSet 是包含具有严格类型的方法、属性和类型定义以公开 DataSet 中的数据和元数据的类。
下面列出了有类型的 DataSet 与通用 DataSet 相比的优缺点。注意,有类型的 DataSet 的实例化和封送处理性能与通用 DataSet 基本相同。
将业务实体表示为有类型的 DataSet 的优点如下:
代码易读。要访问有类型的 DataSet 中的表和列,可以使用有类型的方法和属性,如以下代码所示:
...
// 获取所调用的名为 dsProducts 的有类型的 DataSet 的第一行的
// 产品名称。注意,该集合是基于零的。
String str = dsProducts.Products[0].ProductName;
...
在本示例中,dsProducts 是有类型的 DataSet 的一个实例。该 DataSet 有一个 DataTable,它由一个命名为 Products 的属性公开。该 DataTable 中的列由 ProductName 等属性公开,后者返回列的相应数据类型(而不仅仅返回对象)。
有类型的方法和属性的提供使得使用有类型的 DataSet 比使用通用 DataSet 更方便。使用有类型的 DataSet 时,IntelliSense 将可用。
编译时类型检查。无效的表名称和列名称将在编译时而不是在运行时检测。
将业务实体表示为有类型的 DataSet 的缺点如下:
部署。必须将包含有类型的 DataSet 类的程序集部署到使用业务实体的所有层。
支持企业服务 (COM+) 调用程序。如果有类型的 DataSet 将由 COM+ 客户端使用,则必须为包含该有类型的 DataSet 类的程序集提供一个严格名称,并且必须在客户端计算机上注册。通常,该程序集安装在全局程序集缓存中。这些也是自定义实体类所要求的步骤,如本文后面所述。
可扩展性问题。如果修改了数据库架构,则可能需要重新生成有类型的 DataSet 类以支持新架构。重新生成过程将不会保留在有类型的 DataSet 类中实现的任何自定义代码。必须将包含有类型的 DataSet 类的程序集重新部署到所有客户端应用程序中。
实例化。您不能使用 new 运算符来实例化类型。
继承。有类型的 DataSet 必须从 DataSet 类继承,这会禁止使用任何其他基本类。
定义自定义业务实体组件
表示业务实体的自定义类通常包含以下成员:
用于在本地缓存业务实体的数据的专用字段。这些字段在数据访问逻辑组件从数据库检索数据时保存数据库数据的一个快照。
用于访问实体的状态和访问实体内数据的子集及层次结构的公共属性。这些属性的名称可以与数据库的列名称相同,但这并不是一个绝对要求。可以根据您的应用程序的需要选择属性名,而不必使用数据库中的名称。
用以使用实体组件中的数据执行本地化处理的方法和属性。
用以通知实体组件内部状态变化的事件。
图 9 所示为使用自定义实体类的方法。注意,实体类并不知道数据访问逻辑组件或基础数据库;所有数据库访问都由数据访问逻辑组件执行,以集中数据访问策略和业务逻辑。此外,在层间传递业务实体数据的方式与表示业务实体的格式也没有直接关系;例如,可以在本地将业务实体表示为对象,而用另一种方法(如标量值或 XML)将业务实体数据传递到其他层。
图 9:自定义业务实体组件的作用
定义自定义业务实体组件的建议
在实现自定义实体组件时,请考虑以下建议:
选择使用结构还是使用类。对于不包含分层数据或集合的简单业务实体,可以考虑定义一个结构来表示业务实体。对于复杂的业务实体或要求继承的业务实体,可将实体定义为类。
表示业务实体的状态。对于数字、字符串等简单值,可以使用等价的 .net 数据类型来定义字段。
表示自定义业务实体组件中的子集合和层次结构。表示自定义实体中的数据的子集合和层次结构的方法有两种:
.NET 集合(例如 ArrayList)。.NET 集合类为大小可调的集合提供了一个方便的编程模型,还为将数据绑定到用户界面控件提供了内置的支持。
DataSet。DataSet 适合于存储来自关系数据库或 XML 文件的数据的集合和层次结构。此外,如果需要过滤、排序或绑定子集合,也应首选 DataSet。
支持用户界面客户端的数据绑定。如果自定义实体将要由用户界面使用并且希望利用自动数据绑定,可能需要在自定义实体中实现数据绑定。请考虑以下方案:
Windows 窗体中的数据绑定。您可以将实体实例的数据绑定到控件而不必在自定义实体中实现数据绑定接口。也可以绑定实体的数组或 .NET 集合。
Web 窗体中的数据绑定。如果不实现 IBindingList 接口,则不能将实体实例的数据绑定到 Web 窗体中的控件。然而,如果只想绑定集合,则可以使用数组或 .NET 集合而不必在自定义实体中实现 IBindingList 接口。
公开内部数据更改的事件。公开事件可以获得丰富的客户端用户界面设计,因为它使得无论数据显示在哪里都可以对其进行刷新。事件应当只针对内部状态,而不是针对服务器上的数据更改。
使业务实体可序列化。使业务实体可序列化可以将业务实体的状态保持在中间状态而不进行数据库交互。这样可以方便脱机应用程序的开发和复杂用户界面过程的设计,即在完成前不会影响业务数据。序列化有两种类型:
使用 XmlSerializer 类进行 XML 序列化。如果只需要把公共字段和公共读/写属性序列化为 XML,则可以使用 XML 序列化。注意,如果从 Web 服务返回业务实体数据,对象将通过 XML 序列化自动序列化为 XML。
您可以对业务实体执行 XML 序列化而无需在实体中实现任何附加代码。然而,只有对象中的公共字段和公共读/写属性被序列化为 XML。专用字段、索引生成器、专用属性、只读属性及对象图不会被序列化。您可以使用自定义实体中的属性控制结果 XML。
使用 BinaryFormatter 或 SoapFormatter 类进行格式序列化。如果需要序列化对象的所有公共字段、专用字段及对象图,或者需要与远程服务器之间传递实体组件,则可以使用格式序列化。
格式类将序列化对象的所有公共和专用字段及属性。BinaryFormatter 将对象序列化为二进制格式,SoapFormatter 将对象序列化为 SOAP 格式。使用 BinaryFormatter 的序列化比使用 SoapFormatter 的序列化速度要快。要使用任何一个格式类,都必须将实体类标记为 [Serializable] 属性。如果需要显式控制序列化格式,您的类还必须实现 ISerializable 接口。
注意:还原序列化某个对象时,不会调用默认的构造函数。对还原序列化添加这项约束,是出于性能方面的考虑。
定义自定义实体的优点如下:
代码易读。要访问自定义实体类中的数据,可以使用有类型的方法和属性,如以下代码所示: // 创建一个 ProductDALC 对象
ProductDALC dalcProduct = new ProductDALC();
// 使用该 ProductDALC 对象创建和填充一个 ProductEntity 对象。
// 此代码假设 ProductDALC 类有一个名为 GetProduct 的方法,
// 该方法使用 Product ID 作参数(本例中为 21),并返回一个
// 包含该产品的所有数据的 ProductEntity 对象。
ProductEntity aProduct = dalcProduct.GetProduct(21);
// 更改该产品的产品名称
aProduct.ProductName = "Roasted Coffee Beans";
在上述示例中,产品是一个名为 ProductEntity 的自定义实体类的一个实例。ProductDALC 类有一个名为 GetProduct 的方法,后者创建一个 ProductEntity 对象,将某个特定产品的数据填充到该对象,然后返回 ProductEntity 对象。调用应用程序可以使用 ProductName 等属性访问 ProductEntity 对象中的数据,并且可以调用方法以操作该对象。
封装。自定义实体可以包含方法以封装简单的业务规则。这些方法操作缓存在实体组件中的业务实体数据,而不是访问数据库中的实时数据。请考虑以下示例: // 调用一个在 ProductEntity 类中定义的方法。
aProduct.IncreaseUnitPriceBy(1.50);
在上述示例中,调用应用程序对 ProductEntity 对象调用一个名为 IncreaseUnitPriceBy 的方法。在调用应用程序对 ProductDALC 对象调用相应方法,从而将 ProductEntity 对象保存到数据库之前,这一更改并不是永久性的。
构建复杂系统的模型。在构建复杂域问题(在不同业务实体之间存在很多交互)的模型时,可以定义自定义实体类,从而将复杂性隐藏在经过很好定义的类接口的后面。
本地化验证。自定义实体类可以在其属性存取器中执行简单的验证测试以检测无效的业务实体数据。
专用字段。您可以隐藏不希望向调用程序公开的信息。
定义自定义实体的缺点如下:
业务实体集合。自定义实体表示的是单个业务实体,而不是一个业务实体集合。要保存多个业务实体,调用应用程序必须创建一个数组或一个 .NET 集合。
序列化。您必须在自定义实体中实现自己的序列化机制。可以使用属性来控制实体组件的序列化方式,也可以通过实现 ISerializable 接口来控制自己的序列化。
表示业务实体中的复杂关系和层次结构。您必须在业务实体组件中实现自己的关系和层次结构表示机制。如前面所述,DataSet 通常是实现这一目的的最简单方式。
搜索和排序数据。您必须定义自己的机制来支持实体的搜索和排序。例如,可以通过实现 IComparable 接口以便将实体组件保存在一个 SortedList 集合或 Hashtable 集合中。
部署。您必须在所有物理层部署包含自定义实体的程序集。
支持企业服务 (COM+) 客户端。如果一个自定义实体将由 COM+ 客户端使用,则必须为包含该实体的程序集提供一个严格名称,并且必须在客户端计算机上注册。通常,该程序集安装在全局程序集缓存中。
可扩展性问题。如果修改了数据库架构,则可能需要修改自定义实体类并重新部署程序集。
定义带有 CRUD 行为的自定义业务实体组件
在定义一个自定义实体时,可以提供方法以完全封装对基础数据访问逻辑组件的 CRUD 操作。这是比较传统的面向对象的方法,可能适用于复杂的对象域。客户端应用程序不再直接访问数据访问逻辑组件类,而是创建一个实体组件并对该实体组件调用 CRUD 方法。这些方法将调用基础的数据访问逻辑组件。
图 10 所示为带有 CRUD 行为的自定义实体类的作用。
图 10:带有 CRUD 行为的自定义业务实体组件的作用
定义带有 CRUD 行为的自定义实体类的优点如下:
封装。自定义实体可以封装由基础数据访问逻辑组件定义的操作。
与调用程序的接口。调用程序必须只处理一个接口来保持业务实体数据。不必直接访问数据访问逻辑组件。
专用字段。您可以隐藏不希望向调用程序公开的信息。
定义带有 CRUD 行为的自定义实体类的缺点如下:
处理业务实体集合。自定义实体中的方法属于单个业务实体实例。要支持业务实体集合,可以定义静态方法以读取或返回一个数组或一个实体组件集合。
开发时间长。传统的面向对象方法通常比使用现有对象(如 DataSet)需要更多的设计和开发工作。
表示数据和在层间传递数据的建议
在您的应用程序中表示数据的方式以及在层间传递数据的方式不一定要相同。然而,一套一致而有限的格式能够降低对附加转换层的需要,从而提高性能并方便维护。
应根据自己特定的应用程序要求和操作数据的方式选择数据格式。这里并没有一个通用的表示方式,特别是由于当今的许多应用程序都需要支持多个调用程序。然而,我们还是建议遵循以下一般原则:
如果您的应用程序主要处理集合并需要排序、搜索和数据绑定等功能,则建议采用 DataSet。但如果应用程序处理实例数据,则采用标量值的效果会更好。
如果您的应用程序主要处理实例数据,则自定义业务实体组件可能是最佳选择,因为它们可以消除一个 DataSet 表示一行时的系统开销。
大多数情况下,应把应用程序设计为使用 XML 文档、DataSet 等以数据为中心的格式。可以利用 DataSet 提供的灵活性及固有功能来更方便地支持多个客户端、减少自定义代码的数量并使用为大多数开发人员所熟知的编程 API。虽然以面向对象的方式操作数据有很多好处,但自定义编码复杂的业务实体会使开发和维护成本随所提供功能的数量成比例增加。
事务处理
当今的大多数应用程序都需要支持事务处理以保持系统数据的完整性。事务处理的管理方法有多种,但每种方法都可归于以下两种基本编程模型之一:
手动事务处理。直接在组件代码或存储过程中编写使用 ADO.NET 或 Transact-SQL 事务处理支持功能的代码。
自动事务处理。使用企业服务 (COM+) 为 .NET 类添加声明属性以便在运行时指定对象的事务性要求。使用这种模型可以方便地配置多个组件以执行同一事务中的工作。
本节提供一些指导原则和建议,帮助您在数据访问逻辑组件和业务实体组件中实现事务处理支持。
实现事务处理
在大多数环境中,事务处理的根本是业务过程而不是数据访问逻辑组件或业务实体组件。这是因为业务过程一般要求事务处理跨多个业务实体而不仅仅是单个业务实体。
然而,也可能出现在没有高层次业务过程的帮助下对单个业务实体执行事务性操作的情况。例如,要把一个新客户添加到前面讨论的数据库中,您必须执行以下操作:
在 Customer 表中插入新的一行。
在 Address 表中插入新的一行或多行。
只有这两个操作都成功后客户才会被添加到数据库中。如果 Customer 业务实体不会成为启动该事务处理的更大的业务过程的一部分,则应在 Customer 业务实体中使用手动事务处理。手动事务处理不要求与 Microsoft 分布式事务处理协调器 (DTC) 之间进行任何进程间通信,因此比自动事务处理要快得多。
图 11 所示为确定使用手动事务处理还是自动事务处理的方法。由于 COM+ 事务处理的系统开销,建议将事务处理放到数据库中并在存储过程中控制事务性行为(如果可能)。
图 11:确定如何实现事务处理
注意:如果从基于 ASP.net 的客户端进行调用,并且没有用于启动事务处理的业务过程,则您可能会从 ASP.NET 代码中启动该事务处理。这种设计并不好;您决不能从基于 ASP.NET 的客户端启动事务处理,而应将数据的展示与业务过程相分离。此外,由于网络滞后等问题还会导致性能问题,因为这是要实际部署在其他层上的最常见的层。
在数据访问逻辑组件中使用手动事务处理的建议
在数据访问逻辑组件中实现手动事务处理时,请考虑以下建议:
尽可能在存储过程中执行处理。使用 Transact-SQL 语句 BEGIN TRANSACTION、END TRANSACTION 和 ROLLBACK TRANSACTION 控制事务处理。
如果没有使用存储过程,并且也不会从业务过程中调用数据访问逻辑组件,则可以使用 ADO.NET 来编程控制事务处理。
在数据访问逻辑组件中使用自动事务处理的建议
虽然 COM+ 事务处理会带来一些系统开销,但自动事务处理能够提供比手动事务处理更简单的编程模式,而且在事务处理跨多个分布式数据源(与 DTC 一起工作)时必须使用自动事务处理。在数据访问逻辑组件中实现自动事务处理时,请考虑以下建议:
数据访问逻辑组件必须是从 System.EntERPriseServices 命名空间中的 ServicedComponent 类继承而来。注意,使用 COM+ 服务注册的所有程序集都必须具有严格的名称。
使用 Transaction(TransactionOption.Supported) 属性注释数据访问逻辑组件,以便可以在同一组件中执行读写操作。与 Transaction(TransactionOption.Required) 不同,此选项在不需要事务处理时避免了不必要的系统开销,而前者始终会要求事务处理。
以下代码示例显示了如何在数据访问逻辑组件类中支持自动事务处理:
using System.EnterpriseServices;
[Transaction(TransactionOption.Supported)]
public class CustomerDALC : ServicedComponent
{
...
}
如果使用自动事务处理,则数据访问逻辑组件应在事务处理中表明操作是否成功。如果要隐式表明,应使用 AutoComplete 属性注释您的方法并在操作失败时发出一个异常。如果要显式表明,应对 ContextUtil 类调用 SetComplete 或 SetAbort 方法。
在业务实体组件中使用自动事务处理
在实现带有行为的自定义业务实体组件时,可以使用自动事务处理来指定这些对象的事务性行为。有关使用自动事务处理指定业务实体组件事务性行为的建议与前述有关在数据访问逻辑组件中实现自动事务处理的建议相同。
注意:如果业务实体组件不包含任何要求其在事务处理中表明是否成功的业务逻辑,则它可以忽略事务处理环境。自定义业务实体组件不需要从 ServicedComponent 继承;事务处理环境仍将继续其流程,但实体组件将忽略事务处理环境。
验证
您可以在应用程序的许多层上进行数据验证。各层适用不同的验证类型:
在提交数据之前,客户端应用程序可以在本地验证业务实体数据。
使用 XSD 架构接收业务文档时,业务过程可以验证这些文档。
数据访问逻辑组件和存储过程可以验证数据,以确保引用的完整性并强制遵循约束以及重要的业务规则。
常用验证有两种:
即时点验证。这是在一个特定时点执行的验证。例如,在接收 XML 文档时由业务过程对其进行验证。
连续验证。这是在应用程序的许多不同层次上持续进行的一种验证。连续验证的示例包括:
用户界面可以指定字段的最大长度以防止用户输入过长的字符串。
DataSet 可以指定数据列的最大长度。
自定义业务实体组件可以对实体数据执行范围检查、长度检查、非空检查以及其他简单测试。
数据访问逻辑组件、存储过程和数据库本身可以执行类似的测试,以便在将数据保存到数据库之前确保其有效性。
有时,您可能希望实现额外的聚合过程或转换过程。这种方法在验证和转换经常变化时可能很有用,但会损失性能。例如,如果一个 ISV 想要使用相同的组件支持数据库架构的两个版本,则您可以创建一个单独的组件来执行两个数据库架构版本之间的验证和转换。
如何使用 XSD 架构验证 XML
要使用 XSD 架构验证 XML 文档,请执行以下步骤:
创建一个 XmlValidatingReader 对象作为 XmlTextReader 对象的包装,如以下代码所示:
' 创建 XmlValidatingReader 对象,以读取和验证 Product.xml
XmlTextReader tr = new XmlTextReader("Product.xml");
XmlValidatingReader vr = new XmlValidatingReader(tr);
通过使用 ValidationType 枚举指定所需的验证类型。.NET Framework 支持三种类型的 XML 验证:
文档类型定义 (DTD);指定 ValidationType.DTD
Microsoft XML 精简数据 (XDR) 架构;指定 ValidationType.XDR
W3C 标准 XSD 架构;指定 ValidationType.Schema
以下代码显示了 ValidationType 枚举的使用:
vr.ValidationType = ValidationType.Schema; ' 指定 XSD 架构验证
注册一个验证事件处理程序方法,如以下代码所示:
vr.ValidationEventHandler += new ValidationEventHandler(MyHandlerMethod);
提供一个验证事件处理程序方法的实现,如以下代码所示:
public void MyHandlerMethod(object sender, ValidationEventArgs e)
{
Console.WriteLine("验证错误:" + e.Message);
}
读取和验证文档,如以下代码所示。验证错误将被验证事件处理程序方法拾取。
try
{
while (vr.Read())
{
// 适当处理 XML 数据...
}
}
catch (XmlException ex)
{
Console.WriteLine("XmlException: " + ex.Message);
}
vr.Close();
如何在业务实体组件的属性存取器中验证数据
以下代码片段显示了如何在自定义实体的属性存取器中进行简单验证。如果验证测试失败,您可以发出一个异常以显示问题的性质。也可以在属性存取器集合中使用正则表达式来验证特定的数据和格式。
public class ProductDALC
{
...
public short ReorderLevel
{
get { return reorderLevel; }
}
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("ReorderLevel 不能为负数。");
}
reorderLevel = value;
}
// 加上 ProductDALC 类中的其他成员...
}
异常管理
当 .net 应用程序出现错误时,通常的建议是发出异常而不是从方法返回错误值。这一建议暗示了您编写数据访问逻辑组件和业务实体组件的方式。异常大体上有两种:
技术异常,它包括:
ADO.NET
数据库连接
资源(如数据库、网络共享、消息队列等)不可用
业务逻辑异常,它包括:
验证错误
实现业务逻辑的存储过程中的错误
在数据访问逻辑组件中管理异常的建议
数据访问逻辑组件应该传播异常,并且仅在能够使客户端对异常的管理更加容易时才包装异常类型。将异常包装为两种主要异常类型(技术异常和业务异常)有利于各种可能的调用程序的异常处理结构和异常发布逻辑。
您的应用程序应当发布异常信息。可以将技术异常发布到一个由系统管理员或 Windows 管理规范 (WMI) 监视工具(如 Microsoft Operations Manager)监视的日志中;将业务异常发布到一个特定的应用程序日志中。通常,应允许从数据访问逻辑组件传播异常并允许由调用程序发布异常,以便您了解异常的整个环境。
以下示例说明了这些建议:
public class CustomerDALC
{
public void UpdateCustomer(Dataset aCustomer)
{
try
{
// 更新数据库中的客户...
}
catch (SqlException se)
{
// 捕获并包装异常,然后重新发出
throw new DataAccessException("数据库不可用", se);
}
finally
{
// 清除代码
}
}
}
在业务实体组件中管理异常的建议
业务实体组件应当向调用程序传播异常。在业务实体组件执行验证或者当调用程序试图执行某一操作而未提供该操作所需的数据时,业务实体组件也可以产生异常。
以下示例显示了业务实体组件如何产生异常。在此示例中,如果没有提供客户的名字,Update 方法将发出一个异常:
public class CustomerEntity
{
public void Update()
{
// 检查用户已提供了所需数据。这里是客户
// 的名字
if (FirstName == "" )
{
// 发出一个已定义的新的应用程序异常
throw new MyArgumentException("您必须提供名字。");
}
...
}
}
授权与安全性
本节说明如何将安全性应用于数据访问逻辑组件和业务实体组件。.NET 公共语言运行库使用权限对象实现其对托管代码的强制限制机制。权限对象有三种,各自具有特定的用途:
代码访问安全性。这些权限对象用于防止未经授权使用资源和操作。
身份标识。这些权限对象指定运行程序集时所必需的身份标识特征。
基于角色的安全性。这些权限对象提供了一个机制,用于判断用户(或用户的代理人)是否具有特定身份标识,或者是否是指定角色的成员。PrincipalPermission 对象是唯一基于角色的安全性权限对象。
托管代码可以使用 Principal 对象(包含对 Identity 对象的引用)来判断当事人的身份标识或角色。把 Identity 对象和 Principal 对象与用户、组帐户等大家所熟悉的概念比较可能会更容易理解。在 .NET Framework 中,Identity 对象表示用户,而角色表示成员身份和安全性环境。Principal 对象封装了 Identity 对象和角色。.NET Framework 中的应用程序根据 Principal 对象的身份标识或角色成员身份(后者更常见)授予 Principal 对象权限。
数据访问逻辑组件中的安全性建议
数据访问逻辑组件的设计目的是供其他应用程序组件使用,它也是您的应用程序代码中在调用程序可以访问数据之前实现安全性的最后一个地方。
通常,数据访问逻辑组件可以依赖于由调用程序设置的安全性环境。然而,有些情况下数据访问逻辑组件必须执行自己的授权检查,以确定是否允许当事人执行所请求的操作。授权在身份验证后进行,并使用当事人身份标识与角色等有关信息来确定该当事人可以访问的资源。
在以下情况下,应在数据访问逻辑组件层次上执行授权检查:
需要与不完全信任的业务过程开发人员共享数据访问逻辑组件
需要保护对数据存储提供的强大功能的访问
在定义了 Identity 对象和 Principal 对象后,可以用三种方式执行基于角色的安全性检查:
使用 PrincipalPermission 对象执行强制性安全性检查。
使用 PrincipalPermissionAttribute 属性执行说明性安全性检查。
使用 Principal 对象中的属性和 IsInRole 方法执行显式安全性检查。
以下代码示例显示了如何使用 PrincipalPermissionAttribute 为数据访问逻辑组件类中的方法指定基于角色的声明性安全性检查:
using System;
using System.Security.Permissions;
public class CustomerDALC
{
public CustomerDALC()
{
}
// 使用 PrincipalPermissionAttribute 要求此方法的调用程序
// 具有一个名为“MyUser”的身份标识,并且属于角色“Administrator”。
[PrincipalPermissionAttribute(SecurityAction.Demand,
Name="MyUser", Role="Administrator")]
public void DeleteCustomer(string customerID)
{
// 在此处删除客户代码
}
}
以下代码显示了如何创建具有所需身份标识和角色的 Principal 对象,以便对 CustomerDALC 对象调用 DeleteCustomer 方法:
using System;
using System.Security.Principal;
using System.Threading;
public class MainClass
{
public static int Main(string[] args)
{
Console.Write("用户名:");
string UserName = Console.ReadLine();
Console.Write("密码:");
string Password = Console.ReadLine();
if (Password == "password" && UserName == "MyUser")
{
// 创建一个名为“MyUser”的通用身份标识
GenericIdentity MyIdentity = new GenericIdentity("MyUser");
// 创建角色
String[] MyString = {"Administrator", "User"};
// 创建一个通用当事人
GenericPrincipal MyPrincipal = new GenericPrincipal(MyIdentity,
MyString);
// 设置此线程的当前当事人,以用于基于角色的安全性
Thread.CurrentPrincipal = MyPrincipal;
}
// 创建一个 CustomerDALC 对象,并尝试调用它的 DeleteCustomer 方法。
// 仅在当前当事人的身份标识和角色合格时这一步骤才能成功。
CustomerDALC c = new CustomerDALC();
c.DeleteCustomer("VINET");
}
}
Windows 身份验证
理想情况下,在连接到数据库时应使用 Windows 身份验证而不是 SQL Server 身份验证。然而,应使用服务帐户并避免模拟连接到数据库,因为它会妨碍连接池。连接池需要相同的连接字符串;如果尝试使用不同的连接字符串打开数据库,就会创建单独的连接池,而这将限制可缩放性。
安全通信建议
要实现调用应用程序与数据访问逻辑组件之间的安全通信,请考虑以下建议:
如果数据访问逻辑组件是通过各种层的线路调用的,并且信息交换包含需要保护的机密信息,则应使用分布式组件对象模型 (DCOM)、安全套接字层 (SSL)、安全 Internet 协议 (IPSec) 等安全通信技术。
如果数据是加密存储在数据库中,则通常由数据访问逻辑组件负责数据的加密与解密。如果信息暴露会导致巨大损害,则必须考虑保护与数据访问逻辑组件进行通信的通道。
业务实体组件中的安全性建议
如果将业务实体实现为数据结构(如 XML 或 DataSet),则不需要实现安全性检查。然而,如果将业务实体实现为带有 CRUD 操作的自定义业务实体组件,请考虑以下建议:
如果将实体提供给您不完全信任的业务过程,应在业务实体组件和数据访问逻辑组件中实现授权检查。然而,如果在这两个层次上都实现检查,可能会产生保持安全性策略同步的维护问题。
业务实体组件不应处理通信安全性或数据加密,应把这些任务留给相应的数据访问逻辑组件。
部署
本节提供一些建议以帮助您确定如何部署数据访问逻辑组件和业务实体组件。
部署数据访问逻辑组件
部署数据访问逻辑组件的方法有两种:
与业务过程对象一起部署数据访问逻辑组件。这种部署方法具有最佳的数据传输性能,还有一些额外的技术优势:
事务处理可以在业务过程对象和数据访问逻辑组件之间无缝流动。然而,事务处理不能跨越远程通道无缝流动。在远程方案下,需要使用 DCOM 来实现事务处理。此外,如果业务过程与数据访问逻辑组件被防火墙分开,还需要打开这两个物理层之间的防火墙端口以启用 DTC 通信。
一起部署业务过程对象和数据访问逻辑组件可以减少事务处理失败节点的数目。
安全性环境自动在业务过程对象和数据访问逻辑组件之间流动,无需设置当事人对象。
与用户界面代码一起部署数据访问逻辑组件。有时需要直接从 UI 组件和 UI 过程组件使用数据访问逻辑组件。为提高 Web 方案下的性能,可以与 UI 代码一起部署数据访问逻辑组件;这种部署方法可以使 UI 层充分利用数据读取器流以获得最佳性能。然而,在使用这种部署方法时必须牢记以下事项:
不与 UI 代码一起部署数据访问逻辑组件的一个常见原因是防止通过 Web 领域直接对数据源进行网络访问。
如果您的 Web 领域部署在 DMZ 环境中,则必须打开防火墙端口才能访问 SQL Server。如果使用 COM+ 事务处理,还必须为 DTC 通信打开其他的防火墙端口。
部署业务实体
应用程序的许多不同层都要使用业务实体。根据业务实体的实现方式,如果您的应用程序跨越各个物理层,则需要将业务实体部署到多个位置。下面列出了在不同实现方案中部署业务实体的方法:
部署作为有类型的 DataSet 实现的业务实体。有类型的 DataSet 类必须由数据访问逻辑组件和调用应用程序访问。因此,建议在一个要部署在多个层的公共程序集中定义有类型的 DataSet 类。
部署作为自定义业务实体组件实现的业务实体。根据数据访问逻辑组件中定义的方法签名,自定义实体类可能需要由数据访问逻辑组件访问。请遵循与有类型的 DataSet 相同的建议,即在一个要部署在多个层的公共程序集中定义自定义实体类。
部署作为通用 DataSet 或 XML 字符串实现的业务实体。通用 DataSet 和 XML 字符串不表示单独的数据类型。以这两种格式实现的业务实体不存在部署问题。
如何定义数据访问逻辑组件类
以下代码示例定义一个名为 CustomerDALC 的类,它是用于 Customer 业务实体的数据访问逻辑组件类。CustomerDALC 类为 Customer 业务实体实现 CRUD 操作,并提供了其他方法为此对象封装业务逻辑。
public class CustomerDALC
{
private string conn_string;
public CustomerDALC()
{
// 从安全或加密的位置获取连接字符串
// 并将其分配给 conn_string
}
public CustomerDataSet GetCustomer(string id)
{
// 检索包含 Customer 数据的有类型的 DataSet
}
public string CreateCustomer(string name,
string address, string city, string state,
string zip)
{
// 根据传递给此方法的标量参数,在数据库中创建一个
// 新客户。
// 从此方法返回 customerID。
}
public void UpdateCustomer(CustomerDataSet updatedCustomer)
{
// 根据作为类型 CustomerDataSet 的参数发送的 Customer 数据,更新
// 数据库。
}
public void DeleteCustomer(string id)
{
// 删除具有指定 ID 的客户
}
public DataSet GetCustomersWhoPurchasedProduct(int productID)
{
// 使用通用 DataSet 检索客户,因为此方法
// 不需要检索与客户关联的全部信息
}
}
如何使用 XML 表示数据的集合和层次结构
以下示例显示了如何在 XML 文档中表示数据的集合和层次结构。该 XML 文档表示客户的一个订单;注意,元素 <OrderDetails> 包含一个该订单的详细信息集合。
<Order xmlns="urn:aUniqueNamespace">
<OrderID>10248</OrderID>
<CustomerID>VINET</CustomerID>
<OrderDate>1996-07-04</OrderDate>
<ShippedDate>1996-07-16</ShippedDate>
<OrderDetails>
<OrderDetail>
<ProductID>11</ProductID>
<UnitPrice>14.00</UnitPrice>
<Quantity>12</Quantity>
</OrderDetail>
<OrderDetail>
<ProductID>42</ProductID>
<UnitPrice>9.80</UnitPrice>
<Quantity>10</Quantity>
</OrderDetail>
<OrderDetail>
<ProductID>72</ProductID>
<UnitPrice>34.80</UnitPrice>
<Quantity>5</Quantity>
</OrderDetail>
</OrderDetails>
</Order>
如何在 .net 应用程序中编程应用样式表
要在 .NET 应用程序中编程应用样式表,请执行以下步骤:
导入 System.Xml.Xsl 命名空间,如以下代码所示。System.Xml.Xsl 命名空间包含 .NET Framework 类库中的 XSLT 转换类。
using System.Xml.Xsl;
创建一个 XslTransform 对象,如以下代码所示:
XslTransform stylesheet = new XslTransform();
将所需样式表加载到 XslTransform 对象,如以下代码所示:
stylesheet.Load("MyStylesheet.xsl");
调用 XslTransform 对象的 Transform 方法,如以下代码所示。调用 Transform 方法指定 XML 源文档和结果文档的名称。
stylesheet.Transform(sourceDoc, resultDoc);
如何创建有类型的 DataSet
可以使用有类型的 DataSet 表示业务实体。创建有类型的 DataSet 的方法有多种:
从 Microsoft Visual Studio ®.NET 中的数据适配器创建
从 Visual Studio .NET 中的 XSD 架构文件创建
使用 XSD 架构定义工具 (xsd.exe) 从 .NET Framework 命令提示窗口创建
注意:也可以编程定义有类型的 DataSet,即从 DataSet 继承并定义方法、属性和嵌套类以表示该 DataSet 的结构。最简单的方法是使用以下过程之一创建一个有类型的 DataSet,然后将此有类型的 DataSet 类用作将来您自己的有类型的 DataSet 类的基础。
使用数据适配器创建有类型的 DataSet
要使用数据适配器创建有类型的 DataSet,请执行以下步骤:
在 Visual Studio .NET 中,向您的窗体或组件添加一个数据适配器。在数据适配器的配置向导中,指定该数据适配器的连接信息。同时根据具体情况,为数据适配器的 Select、Insert、Update 和 Delete 命令指定 SQL 字符串或存储过程。
在组件设计器中,在数据适配器对象上单击鼠标右键,然后单击 Generate DataSet(生成 DataSet)。
在 Generate DataSet(生成 DataSet)对话框中,单击 New(新建),键入新 DataSet 类的名称,然后单击 OK(确定)。
为确认已创建该有类型的 DataSet,可以在解决方案资源管理器中单击 Show All Files(显示所有文件)按钮。展开 XSD 架构文件的节点,确认存在一个与 XSD 架构相关联的代码文件。该代码文件定义了新的有类型的 DataSet 类。
从 XSD 架构文件创建有类型的 DataSet
要使用 Visual Studio .NET 从 XSD 架构文件创建有类型的 DataSet,请执行以下步骤:
在 Visual Studio .NET中,创建一个新项目或打开一个现有项目。
为项目添加一个现有的 XSD 架构,或在组件设计器中创建一个新的 XSD 架构。
在解决方案资源管理器中,双击 XSD 架构文件,在组件设计器中查看该 XSD 架构。
在组件设计器中选择主 XSD 架构元素。
在 Schema(架构)菜单中,单击 Generate DataSet(生成 DataSet)。
为确认已创建该有类型的 DataSet,可以在解决方案资源管理器中单击 Show All Files(显示所有文件)按钮。展开 XSD 架构文件的节点,确认存在一个与 XSD 架构相关联的代码文件。该代码文件定义了新的有类型的 DataSet 类。
使用 XSD 架构定义工具 (xsd.exe) 创建有类型的 DataSet
XML 架构定义工具可以从 XSD 架构文件、XDR 架构文件或 XML 实例文档生成有类型的 DataSet。以下命令使用名为 XsdSchemaFile.xsd 的 XSD 架构文件,在当前目录中名为 XsdSchemaFile.cs 的 Visual C# 源文件中生成一个有类型的 DataSet:
xsd /dataset /language:C# XsdSchemaFile.xsd
如何定义业务实体组件
以下示例显示了如何为 Product 业务实体定义自定义实体类:
public class ProductEntity
{
// 专用字段,用于保存 Product 实体的状态
private int productID;
private string productName;
private string quantityPerUnit;
private decimal unitPrice;
private short unitsInStock;
private short unitsOnOrder;
private short reorderLevel;
// 公共属性,用于公开 Product 实体的状态
public int ProductID
{
get { return productID; }
set { productID = value; }
}
public string ProductName
{
get { return productName; }
set { productName = value; }
}
public string QuantityPerUnit
{
get { return quantityPerUnit; }
set { quantityPerUnit = value; }
}
public decimal UnitPrice
{
get { return unitPrice; }
set { unitPrice = value; }
}
public short UnitsInStock
{
get { return unitsInStock; }
set { unitsInStock = value; }
}
public short UnitsOnOrder
{
get { return unitsOnOrder; }
set { unitsOnOrder = value; }
}
public short ReorderLevel
{
get { return reorderLevel; }
set { reorderLevel = value; }
}
// 执行本地化处理的方法和属性
public void IncreaseUnitPriceBy(decimal amount)
{
unitPrice += amount;
}
public short UnitsAboveReorderLevel
{
get { return (short)(unitsInStock - reorderLevel); }
}
public string StockStatus
{
get
{
return "库存:"+ unitsInStock + ",订购:" + unitsOnOrder;
}
}
}
如何表示业务实体组件中数据的集合和层次结构
以下示例显示了如何为 Order 业务实体定义自定义实体类。每个订单都包含许多订购项目,这些订购项目保存在 OrderEntity 类的一个 DataSet 中。
public class OrderEntity
{
// 专用字段,用于保存订单信息
private int orderID;
private string customerID;
private DateTime orderDate;
private DateTime shippedDate;
// 专用字段,用于保存订单详细信息
private DataSet orderDetails;
// 公共属性,用于提供订单信息
public int OrderID
{
get { return orderID; }
set { orderID = value; }
}
public string CustomerID
{
get { return customerID; }
set { customerID = value; }
}
public DateTime OrderDate
{
get { return orderDate; }
set { orderDate = value; }
}
public DateTime ShippedDate
{
get { return shippedDate; }
set { shippedDate = value; }
}
// 公共属性,用于提供订单详细信息
public DataSet OrderDetails
{
get { return orderDetails; }
set { orderDetails = value; }
}
// 附加方法,用于简化对订单详细信息的访问
public bool IsProductOrdered(int productID)
{
// 必须在 DataTable 中定义主关键字列
DataRow row = orderDetails.Tables[0].Rows.Find(productID);
if (row != null)
return true;
else
return false;
}
// 附加属性,用于简化对订单详细信息的访问
public int NumberOfOrderItems
{
get
{
return orderDetails.Tables[0].Rows.Count;
}
}
}
关于 OrderEntity 类,请注意以下几点:
该类包含用于保存有关订单的信息的专用字段。还有一个专用 DataSet 字段,用于保存订单的其他详细信息。数据访问逻辑组件将在创建 OrderEntity 对象时填充所有这些字段。
该类包含用于提供有关订单的信息的公共属性。此外还有一个用于提供该 DataSet 的属性,以便使调用应用程序能够访问订单详细信息。
该类包含一个附加方法和一个附加属性,用于简化对订单详细信息的访问:
IsProductOrdered 方法接收一个 ProductID 参数,并返回一个布尔值以表明该产品是否出现在订单中。
NumberOfOrderItems 属性表明订单中的订购行数目。
如何将业务实体组件绑定到用户界面控件
可以将用户界面控件绑定到 Windows 窗体和 ASP.NET 应用程序中的自定义实体。有两种可能的方案:
在用户界面控件上绑定单个业务实体。以下代码示例显示了如何从 OrderDALC 对象获取一个 OrderEntity 对象并将其绑定到 Windows 窗体的控件上。当用户更改这些控件中的值时,基础 OrderEntity 对象中的数据也将自动更改。 // 创建 OrderDALC 对象。
OrderDALC dalcOrder = new OrderDALC();
// 使用 dalcOrder 为订单 ID 10248 获取一个 OrderEntity 对象。
// 此代码假设 OrderDALC 类有一个名为 GetOrder() 的方法,
// 该方法为特定订单 ID 返回一个 OrderEntity 对象。
OrderEntity order = dalcOrder.GetOrder(10248);
// 将 OrderEntity 的 OrderID 属性绑定到 TextBox 控件。
textBox1.DataBindings.Add("Text", order, "OrderID");
// 将 OrderEntity 的 CustomerID 属性绑定到另一个 TextBox 控件。
control.
textBox2.DataBindings.Add("Text", order, "CustomerID");
// 将 OrderEntity 的 OrderDate 属性绑定到 DatePicker 控件。
dateTimePicker1.DataBindings.Add("Value", order, "OrderDate");
// 将 OrderEntity 的 ShippedDate 属性绑定到另一个 DatePicker 控件。
dateTimePicker2.DataBindings.Add("Value", order, "ShippedDate");
// 将 OrderEntity 的 OrderDetails DataSet 绑定到 DataGrid 控件。
// DataGrid 分别用网格中的一行显示 DataSet 的各个 DataRow。
dataGrid1.DataSource = order.OrderDetails.Tables[0].DefaultView;
准备好后,您可以将修改后的 OrderEntity 对象传递给 OrderDALC,以便将数据保存到数据库中,如以下代码所示。
// 通过 dalcOrder 将 OrderEntity 对象保存到数据库中。
// 此代码假设 OrderDALC 类有一个名为 UpdateOrder() 的方法,
// 该方法接收一个 OrderEntity 参数,并更新数据库中的相应项
dalcOrder.UpdateOrder(order);
将业务实体集合绑定到 DataGrid 控件。以下代码示例显示了如何从 OrderDALC 获取一个 OrderEntity 对象数组并将其绑定到 Windows 窗体的 DataGrid 控件。DataGrid 分别用网格中的一行显示每个数组元素(即每个 OrderEntity 对象)。 // 创建 OrderDALC 对象。
OrderDALC dalcOrder = new OrderDALC();
// 使用 dalcOrder 获取客户“VINET”的 OrderEntity 对象数组。
// 此代码假设 OrderDALC 类有一个名为
GetOrdersForCustomer(),
// 的方法,该方法返回特定客户的 OrderEntity 对象数组。
OrderEntity[] orderEntities = dalcOrder.GetOrdersForCustomer("VINET");
// 将该数组绑定到 DataGrid 控件。
dataGrid1.DataSource = orderEntities;
准备好后,您可以将修改后的数组传递给 OrderDALC,以便将数据保存到数据库中,如以下代码所示:
// 通过 dalcOrder 将 OrderEntity 对象保存到数据库中。
// 此代码假设 OrderDALC 类有一个名为 UpdateOrder() 的方法,该方法获取
// 一个 OrderEntity 对象数组,并更新数据库中的相应项。
dalcOrder.UpdateOrders(orderEntities);
如何在业务实体组件中提供事件
自定义实体可以在业务实体状态修改时产生事件。这些事件可用于获得丰富的客户端用户界面设计,因为这使得无论数据显示在哪里都可以对其进行刷新。以下代码示例显示了如何在 OrderEntity 类中产生业务实体相关事件:
// 为所有业务实体事件定义公用事件类
public class EntityEventArgs : EventArgs
{
// 定义事件成员,用于提供有关事件的信息
}
// 定义一个代理,用于为业务实体相关事件指定签名
public delegate void EntityEventHandler(Object source, EntityEventArgs e);
// 定义自定义实体类,它可以在业务实体状态改变时产生事件
public class OrderEntity
{
// 定义业务实体状态改变的“before”事件和“after”事件
public event EntityEventHandler BeforeChange, AfterChange;
// 专用字段,用于保存业务实体的状态
private int orderID;
private int customerID;
private DateTime orderDate;
private DateTime shippedDate;
private DataSet orderDetails;
// 公共属性,用于提供业务实体的状态
public int OrderID
{
get { return orderID; }
set
{
BeforeChange(this, new EntityEventArgs()); // 产生“before”事件
orderID = value;
AfterChange(this, new EntityEventArgs()); // 产生“after”事件
}
}
public int CustomerID
{
get { return customerID; }
set
{
BeforeChange(this, new EntityEventArgs()); // 产生“before”事件
customerID = value;
AfterChange(this, new EntityEventArgs()); // 产生“after”事件
}
}
public DateTime OrderDate
{
get { return orderDate; }
set
{
BeforeChange(this, new EntityEventArgs()); // 产生“before”事件
orderDate = value;
AfterChange(this, new EntityEventArgs()); // 产生“after”事件
}
}
public DateTime ShippedDate
{
get { return shippedDate; }
set
{
BeforeChange(this, new EntityEventArgs()); // 产生“before”事件
shippedDate = value;
AfterChange(this, new EntityEventArgs()); // 产生“after”事件
}
}
// 必要时使用更多成员...
}
关于上述代码,请注意以下几点:
EntityEvent 类提供有关业务实体相关事件的信息。EntityEventHandler 代理为自定义实体类产生的所有业务实体相关事件指定签名。该代理签名遵循所建议的 .NET Framework 事件处理程序代理的原则。
OrderEntity 类定义了两个名为 BeforeChange 和 AfterChange 的事件。
OrderEntity 中的属性设置器在业务实体状态改变前产生一个 BeforeChange 事件,在业务实体状态改变后产生一个 AfterChange 事件。
如何将业务实体组件序列化为 XML 格式
本节讨论以下问题:
使用 XmlSerializer 序列化自定义实体对象
XML Web Services 中对象的 XML 序列化
序列化自定义实体对象的默认 XML 格式
控制序列化自定义实体对象的 XML 格式
使用 XmlSerializer 序列化自定义实体对象
以下代码示例显示了如何使用 XmlSerializer 类将 OrderEntity 对象序列化为 XML 格式:
using System.Xml.Serialization; // 此命名空间包含 XmlSerializer 类
...
// 创建一个 XmlSerializer 对象,用于序列化 OrderEntity 类型的对象
XmlSerializer serializer = new XmlSerializer(typeof(OrderEntity));
// 将 OrderEntity 对象序列化为名为“MyXmlOrderEntity.xml”的 XML 文件
TextWriter writer = new StreamWriter("MyXmlOrderEntity.xml");
serializer.Serialize(writer, order);
writer.Close();
在 XML Web services 中序列化对象
以下代码示例显示了如何编写使用自定义实体对象的 XML Web services:
namespace MyWebService
{
[WebService(Namespace="urn:MyWebServiceNamespace")]
public class OrderWS : System.Web.Services.WebService
{
[WebMethod]
public OrderEntity GetOrder(int orderID)
{
// 创建 OrderDALC 对象
OrderDALC dalcOrder = new OrderDALC();
// 使用 dalcOrder 获取指定订单 ID 的 OrderEntity 对象。
// 此代码假设 OrderDALC 类有一个名为 GetOrder 的方法,
// 该方法获取一个订单 ID 作为参数,并返回一个 OrderEntity 对象,
// 其中包含该订单的所有数据。
OrderEntity order = dalcOrder.GetOrder(10248);
// 返回 OrderEntity 对象, 该对象将自动序列化。
return order;
}
[WebMethod]
public void UpdateOrder(OrderEntity order)
{
// 创建 OrderDALC 对象。
OrderDALC dalcOrder = new OrderDALC();
// 使用 dalcOrder 将 OrderEntity 对象的数据保存到数据库中。
// 此代码假设 OrderDALC 类有一个名为 UpdateOrder 的方法,
// 该方法接收一个 OrderEntity 对象并将数据保存到数据库中。
dalcOrder.UpdateOrder(order);
}
关于上述代码,请注意以下几点:
GetOrder 方法接收一个订单 ID 作为参数,并返回包含该订单的数据的 OrderEntity 对象。
UpdateOrder 方法接收一个 OrderEntity 对象并将该对象的数据保存到数据库中。
如果客户端应用程序调用 GetOrder 和 UpdateOrder 方法,OrderEntity 对象将为该方法调用自动序列化为 XML 格式。
序列化自定义实体对象的默认 XML 格式
以下 XML 文档显示了 OrderEntity 对象的默认 XML 序列化格式:
<?xml version="1.0" encoding="utf-8"?>
<OrderEntity xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<OrderID>10248</OrderID>
<CustomerID>VINET</CustomerID>
<OrderDate>1996-07-04T00:00:00.0000000+01:00</OrderDate>
<OrderDetails> ... see below ... </OrderDetails>
<ShippedDate>1996-07-16T00:00:00.0000000+01:00</ShippedDate>
</OrderEntity>
上述文档说明了 XML 序列化的默认规则:
该 XML 文档的根元素与类名称 OrderEntity 相同。
OrderEntity 对象中的每个公共属性(及字段)都被序列化为具有相同名称的元素。
OrderEntity 类中的 OrderDetails 属性是一个 DataSet,DataSet 提供了内置的 XML 序列化支持。OrderDetails DataSet 的序列化结果如下:
<OrderDetails>
<xs:schema id="NewDataSet" xmlns=""
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="en-
UK">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="OrderDetails">
<xs:complexType>
<xs:sequence>
<xs:element name="OrderID" type="xs:int" minOccurs="0" />
<xs:element name="ProductID" type="xs:int" minOccurs="0" />
<xs:element name="UnitPrice" type="xs:decimal" minOccurs="0"
/>
<xs:element name="Quantity" type="xs:short" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<NewDataSet>
<OrderDetails diffgr:id="OrderDetails1" msdata:rowOrder="0"
diffgr:hasChanges="inserted">
<OrderID>10248</OrderID>
<ProductID>11</ProductID>
<UnitPrice>14</UnitPrice>
<Quantity>12</Quantity>
</OrderDetails>
<OrderDetails diffgr:id="OrderDetails2" msdata:rowOrder="1"
diffgr:hasChanges="inserted">
<OrderID>10248</OrderID>
<ProductID>42</ProductID>
<UnitPrice>9.8</UnitPrice>
<Quantity>10</Quantity>
</OrderDetails>
<OrderDetails diffgr:id="OrderDetails3" msdata:rowOrder="2"
diffgr:hasChanges="inserted">
<OrderID>10248</OrderID>
<ProductID>72</ProductID>
<UnitPrice>34.8</UnitPrice>
<Quantity>5</Quantity>
</OrderDetails>
</NewDataSet>
</diffgr:diffgram>
</OrderDetails>
关于 DataSet 的序列化,请注意以下几点:
<xs:schema> 段描述了 DataSet 的结构,包括表、列名称和列类型。
<xs:diffgram> 段包含该 DataSet 的数据。每个 <OrderDetails> 元素表示该 DataSet 中 OrderDetails 表中的单独一行。
控制序列化自定义实体对象的 XML 格式
您可以在自定义实体类中使用 .NET 属性来控制属性和字段序列化为 XML 的方式。请考虑以下修订后的 OrderEntity 类:
[XmlRoot(ElementName="Order", Namespace="urn:MyNamespace")]
public class OrderEntity
{
[XmlAttribute(AttributeName="ID")]
public int OrderID {...获取和设置代码,同前...}
[XmlAttribute(AttributeName="CustID")]
public string CustomerID {...获取和设置代码,同前...}
[XmlElement(ElementName="Ordered")]
public DateTime OrderDate {...获取和设置代码,同前...}
public DataSet OrderDetails {...获取和设置代码,同前...}
[XmlElement(ElementName="Shipped")
public DateTime ShippedDate {...获取和设置代码,同前...}
// 必要时使用更多成员...
}
将 OrderEntity 对象序列化为 XML 后,其格式如下:
<?xml version="1.0" encoding="utf-8" ?>
<Order ID="10248"
CustID="VINET"
xmlns="urn:MyNamespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Ordered>1996-07-04T00:00:00.0000000+01:00</Ordered>
<OrderDetails>...详细代码同前...</OrderDetails>
<Shipped>1996-07-16T00:00:00.0000000+01:00</Shipped>
</Order>
如何将业务实体组件序列化为 SOAP 格式
以下代码示例显示了如何使用 SoapFormatter 类将 OrderEntity 对象序列化为 SOAP 格式。当使用 SOAP 协议向或从 XML Web services 传递对象,或者当使用 HTTP 远程通道向或从 Remoting 服务器传递对象时,也会发生 SOAP 序列化(隐式)。此外,您也可以在使用 TCP 远程通道时指定 SOAP 格式化。
using System.Runtime.Serialization.Formatters.Soap; // 用于 SoapFormatter 类
...
// 创建 SoapFormatter 对象,用于序列化 OrderEntity 类型的对象
SoapFormatter formatter = new SoapFormatter();
// 将 OrderEntity 对象序列化为名为“MySoapOrderEntity.xml”的 SOAP (XML) 文件
FileStream stream = File.Create("MySoapOrderEntity.xml");
formatter.Serialize(stream, order);
stream.Close();
要对自定义实体组件使用 SOAP 序列化,必须使用 Serializable 属性注释您的实体类,如以下代码所示:
[Serializable]
public class OrderEntity
{
// 成员,同前
如果要自定义序列化过程中生成的 SOAP 格式,实体类必须实现 ISerializable 接口。您必须提供一个 GetObjectData 方法供 SoapFormatter 在序列化过程中调用,并提供一个特殊构造函数供 SoapFormatter 在还原序列化过程中调用以重新创建对象。以下代码显示了 ISerializable 接口、GetObjectData 方法和特殊构造函数的使用:
using System.Runtime.Serialization; // 用于 ISerializable 接口以及相关类型
...
[Serializable]
public class OrderEntity : ISerializable
{
// 序列化函数,由 SoapFormatter 在序列化过程中调用
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext
ctxt)
{
// 向 SerializationInfo 对象中添加每个字段
info.AddValue("OrderID", orderID);
// 必要时使用更多代码...
}
// 还原序列化构造函数,由 SoapFormatter 在还原序列化过程中调用
public OrderEntity(SerializationInfo info, StreamingContext ctxt)
{
// 从 SerializationInfo 对象中还原序列化出各个 OrderEntity 字段
orderID = (int)info.GetValue("OrderID", typeof(int));
// 必要时使用更多代码...
}
// 其他成员,同前...
}
如何将业务实体组件序列化为二进制格式
以下代码示例显示了如何使用 BinaryFormatter 类将 OrderEntity 对象序列化为二进制格式。当使用 TCP 远程通道向或从 Remoting 服务器传递对象时,也会发生二进制序列化(隐式)。此外,为提高性能,您也可以在使用 HTTP 远程通道时指定二进制格式化。
using System.Runtime.Serialization.Formatters.Binary; // 用于 BinaryFormatter 类
...
// 创建 BinaryFormatter 对象,用于序列化 OrderEntity 类型的对象
BinaryFormatter formatter = new BinaryFormatter();
// 将 OrderEntity 对象序列化为名为“MyBinaryOrderEntity.dat”的二进制文件
FileStream stream = File.Create("MyBinaryOrderEntity.dat");
formatter.Serialize(stream, order);
stream.Close();
要对自定义实体对象使用二进制序列化,必须使用 Serializable 属性注释您的自定义实体类。要自定义序列化过程中生成的二进制格式,自定义实体类必须实现 ISerializable 接口。这两种方案中的详细代码与 SOAP 序列化的代码相同。