虽然 COM+ 事务处理会带来一些系统开销,但自动事务处理能够提供比手动事务处理更简单的编程模式,而且在事务处理跨多个分布式数据源(与 DTC 一起工作)时必须使用自动事务处理。在数据访问逻辑组件中实现自动事务处理时,请考虑以下建议:
如果使用自动事务处理,则数据访问逻辑组件应在事务处理中表明操作是否成功。如果要隐式表明,应使用 AutoComplete 属性注释您的方法并在操作失败时发出一个异常。如果要显式表明,应对 ContextUtil 类调用 SetComplete 或 SetAbort 方法。
在实现带有行为的自定义业务实体组件时,可以使用自动事务处理来指定这些对象的事务性行为。有关使用自动事务处理指定业务实体组件事务性行为的建议与前述有关在数据访问逻辑组件中实现自动事务处理的建议相同。
验证
您可以在应用程序的许多层上进行数据验证。各层适用不同的验证类型:
- 在提交数据之前,客户端应用程序可以在本地验证业务实体数据。
- 使用 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 验证:
- 注册一个验证事件处理程序方法,如以下代码所示:
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,请执行以下步骤:
- 在 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 序列化的代码相同。