设计数据层组件并在层间传递数据Angela Crocker、Andy Olsen 和 Edward Jezierski Microsoft Corporation 2002年8月 适用于:
摘要:学习向 Microsoft .NET 应用程序公开数据的最佳方式,以及如何实现一个有效的策略以便在分布式应用程序的层间传递数据。(本文包含一些指向英文站点的链接。) 目录简介在设计分布式应用程序时需要确定如何访问和表示与该应用程序相关联的业务数据。本文提供一些指导原则以帮助您选择公开数据、保持数据和在应用程序的层间传递数据的最佳方式。 图 1 所示为分布式应用程序中的常见层。本文区分业务数据与使用这些数据的业务过程,并且仅在需要明确说明时讨论业务过程层。同样,本文仅在直接涉及数据表示方式(例如 Microsoft® ASP.NET Web 页面公开业务数据的方式)时讨论表示层。图 1 中使用了两个新术语:数据访问逻辑组件和业务实体组件。本文后面将解释这些术语。
图 1:分布式应用程序中数据的访问与表示 多数应用程序将数据存储在关系数据库中。除此之外还有其他数据存储方式,但本文重点讨论 .NET 应用程序与关系数据库交互的方式,而并不专门讨论它如何与平面文件、非关系数据库等其他数据存储中的数据进行交互。 本文明确区分保持逻辑与数据本身。将保持逻辑与数据区分开来的原因如下:
为区分保持逻辑与数据本身,本文提出了两种不同的组件类型。
数据访问逻辑组件数据访问逻辑组件代表调用程序提供对数据库执行以下任务的方法:
执行上述任务的方法通常称为“CRUD”方法,这是由各项任务的首字母组成的一个缩写词。 数据访问逻辑组件还提供对数据库实现业务逻辑的方法。例如,数据访问逻辑组件可能包含一个查找目录中本月销售额最高的产品的方法。 通常,数据访问逻辑组件访问一个单一数据库,并封装了针对该数据库中一个表或一组相关表的数据相关操作。例如,可以定义一个数据访问逻辑组件来处理数据库中的 Customer 表和 Address 表,同时定义另一个数据访问逻辑组件来处理 Orders 表和 OrderDetails 表。本文后面将讨论将数据访问逻辑组件映射到数据库表的设计决策。 表示业务实体每个数据访问逻辑组件都处理一种特定类型的业务实体。例如,Customer 数据访问逻辑组件处理 Customer 业务实体。表示业务实体的方式很多,这取决于诸如以下因素:
本文将概述以下实现选项的优缺点:
技术因素图 2 所示为影响数据访问逻辑组件和业务实体实现策略的一些技术因素。本文将分别讨论这些技术因素并提供相关建议。
图 2:影响数据访问逻辑组件和业务实体设计的技术因素 将关系数据映射到业务实体数据库通常包含许多表,这些表之间的关系通过主键和外键来实现。当定义业务实体以在 .NET 应用程序中表示这些数据时,必须确定如何把这些表映射到业务实体。 请考虑图 3 所示的假想零售商数据库。
图 3:假想的关系数据库中的表关系 下表总结了示例数据库中的关系类型。
当定义业务实体以在数据库中建立信息模型时,应考虑要如何在您的应用程序中使用这些信息。应当标识封装您的应用程序的功能的核心业务实体,而不是为每个表定义单独的业务实体。 该假想零售商的应用程序中的典型操作如下:
为满足这些应用程序要求,该应用程序要处理三个逻辑业务实体:Customer、Order 和 Product。对于每个业务实体,都将定义一个单独的数据访问逻辑组件,如下所示:
图 4 所示为这些数据访问逻辑组件与它们所表示的数据库中的表之间的关系。
图 4:定义向 .NET 应用程序公开关系数据的数据访问逻辑组件 有关如何实现数据访问逻辑组件的说明,请参阅本文后面的实现数据访问逻辑组件。 将关系数据映射到业务实体的建议要将关系数据映射到业务实体,请考虑以下建议:
实现数据访问逻辑组件数据访问逻辑组件是一个无状态类,也就是说,所交换的所有消息都可以独立解释。调用之间不存在状态。数据访问逻辑组件为访问单一数据库(某些情况下可以是多个数据库,例如水平数据库分区)中的一个或多个相关表提供方法。通常,数据访问逻辑组件中的这些方法将调用存储过程以执行相应操作。 数据访问逻辑组件的主要目标之一是从调用应用程序中隐藏数据库的调用及格式特性。数据访问逻辑组件为这些应用程序提供封装的数据访问服务。具体地说,数据访问逻辑组件处理以下实现细节:
本节后面将详细讨论其中的某些问题。 数据访问逻辑组件的应用方案图 5 所示为从各种应用程序类型(包括 Windows 窗体应用程序、ASP.NET 应用程序、XML Web services 和业务过程)中调用数据访问逻辑组件的方式。根据应用程序的部署方式,这些调用可以是本地的,也可以是远程的。 图 5:数据访问逻辑组件的应用方案(单击缩略图以查看大图像)实现数据访问逻辑组件类数据访问逻辑组件使用 ADO.NET 执行 SQL 语句或调用存储过程。有关数据访问逻辑组件类的示例,请参阅附录中的如何定义数据访问逻辑组件类。 如果您的应用程序包含多个数据访问逻辑组件,可以使用数据访问助手组件来简化数据访问逻辑组件类的实现。该组件可以帮助管理数据库连接、执行 SQL 命令以及缓存参数。数据访问逻辑组件仍然封装访问特定业务数据所需的逻辑,而数据访问助手组件则专注于数据访问 API 的开发和数据连接配置,从而帮助减少代码的重复。Microsoft 提供了 Data Access Application Block for .NET,当使用 Microsoft SQL Server™ 数据库时,可在您的应用程序中将其用作一个通用的数据访问助手组件。图 6 所示为使用数据访问助手组件帮助实现数据访问逻辑组件的方法。
图 6: 使用数据访问助手组件实现数据访问逻辑组件 当存在所有数据访问逻辑组件公用的实用程序功能时,可以定义一个基本类以从中继承和扩展数据访问逻辑组件。 将数据访问逻辑组件类设计为可以为不同类型的客户端提供一致的接口。如果将数据访问逻辑组件设计为与当前及潜在的业务过程层的实现要求相兼容,可以减少必须实现的附加接口、接触面或映射层的数目。 要支持广泛的业务过程和应用程序,请考虑以下技术以便将数据传入和传出数据访问逻辑组件方法:
以下各节将说明用于将业务实体数据传入和传出数据访问逻辑组件的各种方式以及每种方式的优缺点。这些信息有助于您根据自己特定的应用程序方案做出相应选择。 将标量值作为输入和输出传递这种方法的优点如下:
这种方法的缺点如下:
将 XML 字符串作为输入和输出传递这种方法的优点如下:
这种方法的缺点如下:
将 DataSet 作为输入和输出传递这种方法的优点如下:
这种方法的缺点如下:
将自定义业务实体组件作为输入和输出传递这种方法的优点如下:
这种方法的缺点如下:
将数据读取器作为输出返回这种方法的优点如下:
这种方法的缺点如下:
配合使用数据访问逻辑组件与存储过程可以使用存储过程执行数据访问逻辑组件支持的许多数据访问任务。 优点
尽管存储过程具有上述优点,但仍有某些情况不适合使用存储过程。 缺点
配合使用数据访问逻辑组件与存储过程的建议配合使用数据访问逻辑组件与存储过程时,请考虑以下建议:
管理锁定和并发某些应用程序在更新数据库数据时采用“后进有效”(Last in Wins) 法。使用“后进有效”法更新数据库时不会将更新与原始记录相比较,因此可能会覆盖掉自上次刷新记录以来其他用户所做的所有更改。然而,有时应用程序却需要在执行更新之前确定数据自最初读取以来是否被更改。 数据访问逻辑组件可以实现管理锁定和并发的代码。管理锁定和并发的方法有两种:
使用保守式并发保守式并发主要用于数据争用量大以及通过锁定来保护数据的成本低于发生并发冲突时回滚事务的成本的环境。如果锁定时间很短(例如在编程处理的记录中),则实现保守式并发效果最好。 保守式并发要求与数据库建立持久连接,并且因为记录可能被锁定较长时间,因此当用户与数据进行交互时,不能提供可缩放的性能。 使用开放式并发开放式并发适用于数据争用量低或要求只读访问数据的环境。开放式并发可以减少所需锁定的数量,从而降低数据库服务器的负荷,提高数据库的性能。 开放式并发在 .NET 中被广泛使用以满足移动和脱机应用程序的需要。在这种情况下,长时间锁定数据是不可行的。此外,保持记录锁定还要求与数据库服务器的持久连接,这在脱机应用程序中是不可能的。 测试开放式并发冲突测试开放式并发冲突的方法有多种:
手动实现开放式并发请考虑以下 SQL 查询: SELECT Column1, Column2, Column3 FROM Table1 要在更新 Table1 的行时测试开放式并发冲突,可以发出以下 UPDATE 语句: UPDATE Table1 Set Column1 = @NewValueColumn1, Set Column2 = @NewValueColumn2, Set Column3 = @NewValueColumn3 WHERE Column1 = @OldValueColumn1 AND Column2 = @OldValueColumn2 AND Column3 = @OldValueColumn3 如果原始值与数据库中的值匹配,则执行更新。如果某个值被修改,WHERE 子句将无法找到相应匹配,从而更新将不会修改该行。您可以对此技术稍加变化,即只对特定列应用 WHERE 子句,使得如果自上次查询以来特定字段被更新,则不覆盖数据。 注意:请始终返回一个唯一标识查询中的一行的值,例如一个主关键字,以用于 UPDATE 语句的 WHERE 子句。这样可以确保 UPDATE 语句更新正确的行。 如果数据源中的列允许空值,则可能需要扩展 WHERE 子句,以便检查本地表与数据源中匹配的空引用。例如,以下 UPDATE 语句将验证本地行中的空引用(或值)是否仍然与数据源中的空引用(或值)相匹配。 UPDATE Table1 Set Column1 = @NewColumn1Value WHERE (@OldColumn1Value IS NULL AND Column1 IS NULL) OR Column1 = @OldColumn1Value 使用数据适配器和 DataSet 实现开放式并发可以配合使用 DataAdapter.RowUpdated 事件与前面所述技术以通知您的应用程序发生了开放式并发冲突。每当试图更新 DataSet 中的修改过的行时,都将引发 RowUpdated 事件。可以使用 RowUpdated 事件添加特殊处理代码,包括发生异常时的处理、添加自定义错误信息以及添加重试逻辑。 RowUpdated 事件处理程序接收一个 RowUpdatedEventArgs 对象,该对象具有 RecordsAffected 属性,可以显示针对表中的一个修改过的行的更新命令会影响多少行。如果把更新命令设置为测试开放式并发,则当发生开放式并发冲突时,RecordsAffected 属性将为 0。设置 RowUpdatedEventArgs.Status 属性以表明要采取的操作;例如,可以把该属性设置为 UpdateStatus.SkipCurrentRow 以跳过对当前行的更新,但是继续更新该更新命令中的其他行。有关 RowUpdated 事件的详细信息,请参阅 Working with DataAdapter Events。 使用数据适配器测试并发错误的另一种方法是在调用 Update 方法之前把 DataAdapter.ContinueUpdateOnError 属性设置为 true。完成更新后,调用 DataTable 对象的 GetErrors 方法以确定哪些行发生了错误。然后,使用这些行的 RowError 属性找到特定的详细错误信息。有关如何处理行错误的详细信息,请参阅 Adding and Reading Row Error Information。 以下代码示例显示了 Customer 数据访问逻辑组件如何检查并发冲突。该示例假设客户端检索到了一个 DataSet 并修改了数据,然后把该 DataSet 传递给了数据访问逻辑组件中的 UpdateCustomer 方法。UpdateCustomer 方法将通过调用以下存储过程来更新相应的客户记录;仅当客户 ID 与公司名称未被修改时存储过程才能更新该客户记录: CREATE PROCEDURE CustomerUpdate { @CompanyName varchar(30), @oldCustomerID varchar(10), @oldCompanyName varchar(30) } AS UPDATE Customers Set CompanyName = @CompanyName WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName GO 在 UpdateCustomer 方法中,以下代码示例将一个数据适配器的 UpdateCommand 属性设置为测试开放式并发,然后使用 RowUpdated 事件测试开放式并发冲突。如果遇到开放式并发冲突,应用程序将通过设置要更新的行的 RowError 来表明开放式并发冲突。注意,传递给 UPDATE 命令中的 WHERE 子句的参数值被映射到 DataSet 中各相应列的原始值。 // CustomerDALC 类中的 UpdateCustomer 方法 public void UpdateCustomer(DataSet dsCustomer) { // 连接到 Northwind 数据库 SqlConnection cnNorthwind = new SqlConnection( "Data source=localhost;Integrated security=SSPI;Initial Catalog=northwind"); // 创建一个数据适配器以访问 Northwind 中的 Customers 表 SqlDataAdapter da = new SqlDataAdapter(); // 设置数据适配器的 UPDATE 命令,调用存储过程“UpdateCustomer” da.UpdateCommand = new SqlCommand("CustomerUpdate", cnNorthwind); da.UpdateCommand.CommandType = CommandType.StoredProcedure; // 向数据适配器的 UPDATE 命令添加两个参数, // 为 WHERE 子句指定信息(用于检查开放式并发冲突) da.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30, "CompanyName"); // 将 CustomerID 的原始值指定为第一个 WHERE 子句参数 SqlParameter myParm = da.UpdateCommand.Parameters.Add( "@oldCustomerID", SqlDbType.NChar, 5, "CustomerID"); myParm.SourceVersion = DataRowVersion.Original; // 将 CustomerName 的原始值指定为第二个 WHERE 子句参数 myParm = da.UpdateCommand.Parameters.Add( "@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName"); myParm.SourceVersion = DataRowVersion.Original; // 为 RowUpdated 事件添加一个处理程序 da.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated); // 更新数据库 da.Update(ds, "Customers"); foreach (DataRow myRow in ds.Tables["Customers"].Rows) { if (myRow.HasErrors) Console.WriteLine(myRow[0] + " " + myRow.RowError); } } // 处理 RowUpdated 事件的方法。 如果登记该事件但不处理它, // 则引发一个 SQL 异常。 protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args) { if (args.RecordsAffected == 0) { args.Row.RowError = "遇到开放式并发冲突"; args.Status = UpdateStatus.SkipCurrentRow; } } 当在一个 SQL Server 存储过程中执行多个 SQL 语句时,出于性能原因,可以使用 SET NOCOUNT ON 选项。此选项将禁止 SQL Server 在每次执行完一条语句时都向客户端返回一条消息,从而可以降低网络流量。然而,这样将不能像前面的代码示例那样检查 RecordsAffected 属性。RecordsAffected 属性将始终为 1。另一种方法是在存储过程中返回 @@ROWCOUNT 函数(或将它指定为一个输出参数);@@ROWCOUNT 包含了存储过程中上一条语句完成时的记录数目,并且即使使用了 SET NOCOUNT ON,该函数也会被更新。因此,如果存储过程中执行的上一条 SQL 语句是实际的 UPDATE 语句,并且已经指定 @@ROWCOUNT 作为返回值,则可以对应用程序代码进行如下修改: // 向数据适配器的 UPDATE 命令添加另一个参数来接收返回值。 // 可以任意命名该参数。 myParm = da.UpdateCommand.Parameters.Add("@RowCount", SqlDbType.Int); myParm.Direction = ParameterDirection.ReturnValue; // 将 OnRowUpdated 方法修改为检查该参数的值 // 而不是 RecordsAffected 属性。 protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args) { if (args.Command.Parameters["@RowCount"].Value == 0) { args.Row.RowError = "遇到开放式并发冲突"; args.Status = UpdateStatus.SkipCurrentRow; } } COM 互操作性如果希望数据访问逻辑组件类能够被 COM 客户端调用,则建议按前面所述的原则定义数据存取逻辑组件,并提供一个包装组件。然而,如果希望 COM 客户端能够访问数据访问逻辑组件,请考虑以下建议:
有关 COM 互操作性的详细信息,请参阅 Microsoft .NET/COM Migration and Interoperability 指南。 实现业务实体业务实体具有以下特点:
如本文前面所述,在您的应用程序中表示业务实体的方法有很多(从以数据为中心的模型到更加面向对象的表示法):
以下各节将介绍如何使用这些格式来表示业务实体。为帮助您确定特定环境中最适宜的业务实体表示,以下各节将介绍如何为各业务实体格式执行以下任务:
以下各节还针对非功能性要求(包括性能、效率、可缩放性和可扩展性)考虑了每种业务实体表示的适用性。 将业务实体表示为 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 表示业务实体数据时,请考虑以下原则:
有关详细信息或性能方面的考虑,请参阅 Performance Comparison:Data Access Techniques。 将业务实体表示为 XML 的优点如下:
将业务实体表示为 XML 的缺点如下:
将业务实体表示为通用 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 中的表、约束及关系。有关详细信息,请参阅 Creating and Using DataSets。 将业务实体表示为通用 DataSet 的优点如下:
将业务实体表示为通用 DataSet 的缺点如下:
将业务实体表示为有类型的 DataSet有类型的 DataSet 是包含具有严格类型的方法、属性和类型定义以公开 DataSet 中的数据和元数据的类。有关如何创建有类型的 DataSet 的示例,请参阅附录中的如何创建有类型的 DataSet。 下面列出了有类型的 DataSet 与通用 DataSet 相比的优缺点。注意,有类型的 DataSet 的实例化和封送处理性能与通用 DataSet 基本相同。 将业务实体表示为有类型的 DataSet 的优点如下:
将业务实体表示为有类型的 DataSet 的缺点如下:
定义自定义业务实体组件表示业务实体的自定义类通常包含以下成员:
图 9 所示为使用自定义实体类的方法。注意,实体类并不知道数据访问逻辑组件或基础数据库;所有数据库访问都由数据访问逻辑组件执行,以集中数据访问策略和业务逻辑。此外,在层间传递业务实体数据的方式与表示业务实体的格式也没有直接关系;例如,可以在本地将业务实体表示为对象,而用另一种方法(如标量值或 XML)将业务实体数据传递到其他层。 图 9:自定义业务实体组件的作用(单击缩略图以查看大图像)定义自定义业务实体组件的建议在实现自定义实体组件时,请考虑以下建议:
定义自定义实体的优点如下:
定义自定义实体的缺点如下:
定义带有 CRUD 行为的自定义业务实体组件在定义一个自定义实体时,可以提供方法以完全封装对基础数据访问逻辑组件的 CRUD 操作。这是比较传统的面向对象的方法,可能适用于复杂的对象域。客户端应用程序不再直接访问数据访问逻辑组件类,而是创建一个实体组件并对该实体组件调用 CRUD 方法。这些方法将调用基础的数据访问逻辑组件。 图 10 所示为带有 CRUD 行为的自定义实体类的作用。 图 10:带有 CRUD 行为的自定义业务实体组件的作用(单击缩略图以查看大图像)定义带有 CRUD 行为的自定义实体类的优点如下:
定义带有 CRUD 行为的自定义实体类的缺点如下:
表示数据和在层间传递数据的建议在您的应用程序中表示数据的方式以及在层间传递数据的方式不一定要相同。然而,一套一致而有限的格式能够降低对附加转换层的需要,从而提高性能并方便维护。 应根据自己特定的应用程序要求和操作数据的方式选择数据格式。这里并没有一个通用的表示方式,特别是由于当今的许多应用程序都需要支持多个调用程序。然而,我们还是建议遵循以下一般原则:
事务处理当今的大多数应用程序都需要支持事务处理以保持系统数据的完整性。事务处理的管理方法有多种,但每种方法都可归于以下两种基本编程模型之一:
本节提供一些指导原则和建议,帮助您在数据访问逻辑组件和业务实体组件中实现事务处理支持。有关 .NET 中的事务处理示例和更深入的讨论,请参阅 .NET Data Access Architecture Guide。 实现事务处理在大多数环境中,事务处理的根本是业务过程而不是数据访问逻辑组件或业务实体组件。这是因为业务过程一般要求事务处理跨多个业务实体而不仅仅是单个业务实体。 然而,也可能出现在没有高层次业务过程的帮助下对单个业务实体执行事务性操作的情况。例如,要把一个新客户添加到前面讨论的数据库中,您必须执行以下操作:
只有这两个操作都成功后客户才会被添加到数据库中。如果 Customer 业务实体不会成为启动该事务处理的更大的业务过程的一部分,则应在 Customer 业务实体中使用手动事务处理。手动事务处理不要求与 Microsoft 分布式事务处理协调器 (DTC) 之间进行任何进程间通信,因此比自动事务处理要快得多。 图 11 所示为确定使用手动事务处理还是自动事务处理的方法。由于 COM+ 事务处理的系统开销,建议将事务处理放到数据库中并在存储过程中控制事务性行为(如果可能)。
图 11:确定如何实现事务处理 注意:如果从基于 ASP.NET 的客户端进行调用,并且没有用于启动事务处理的业务过程,则您可能会从 ASP.NET 代码中启动该事务处理。这种设计并不好;您决不能从基于 ASP.NET 的客户端启动事务处理,而应将数据的展示与业务过程相分离。此外,由于网络滞后等问题还会导致性能问题,因为这是要实际部署在其他层上的最常见的层。 在数据访问逻辑组件中使用手动事务处理的建议在数据访问逻辑组件中实现手动事务处理时,请考虑以下建议:
在数据访问逻辑组件中使用自动事务处理的建议虽然 COM+ 事务处理会带来一些系统开销,但自动事务处理能够提供比手动事务处理更简单的编程模式,而且在事务处理跨多个分布式数据源(与 DTC 一起工作)时必须使用自动事务处理。在数据访问逻辑组件中实现自动事务处理时,请考虑以下建议:
以下代码示例显示了如何在数据访问逻辑组件类中支持自动事务处理: using System.EnterpriseServices; [Transaction(TransactionOption.Supported)] public class CustomerDALC : ServicedComponent { ... } 如果使用自动事务处理,则数据访问逻辑组件应在事务处理中表明操作是否成功。如果要隐式表明,应使用 AutoComplete 属性注释您的方法并在操作失败时发出一个异常。如果要显式表明,应对 ContextUtil 类调用 SetComplete 或 SetAbort 方法。 有关自动事务处理的详细信息,请参阅 .NET Data Access Architecture Guide 中的“Using Automatic Transactions”。 在业务实体组件中使用自动事务处理在实现带有行为的自定义业务实体组件时,可以使用自动事务处理来指定这些对象的事务性行为。有关使用自动事务处理指定业务实体组件事务性行为的建议与前述有关在数据访问逻辑组件中实现自动事务处理的建议相同。 注意:如果业务实体组件不包含任何要求其在事务处理中表明是否成功的业务逻辑,则它可以忽略事务处理环境。自定义业务实体组件不需要从 ServicedComponent 继承;事务处理环境仍将继续其流程,但实体组件将忽略事务处理环境。 验证您可以在应用程序的许多层上进行数据验证。各层适用不同的验证类型:
常用验证有两种:
有时,您可能希望实现额外的聚合过程或转换过程。这种方法在验证和转换经常变化时可能很有用,但会损失性能。例如,如果一个 ISV 想要使用相同的组件支持数据库架构的两个版本,则您可以创建一个单独的组件来执行两个数据库架构版本之间的验证和转换。 如何使用 XSD 架构验证 XML要使用 XSD 架构验证 XML 文档,请执行以下步骤:
如何在业务实体组件的属性存取器中验证数据以下代码片段显示了如何在自定义实体的属性存取器中进行简单验证。如果验证测试失败,您可以发出一个异常以显示问题的性质。也可以在属性存取器集合中使用正则表达式来验证特定的数据和格式。 public class ProductDALC { ... public short ReorderLevel { get { return reorderLevel; } } set { if (value < 0) { throw new ArgumentOutOfRangeException("ReorderLevel 不能为负数。"); } reorderLevel = value; } // 加上 ProductDALC 类中的其他成员... } 异常管理当 .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 应用程序中处理异常的详细信息,请参阅 Exception Management in .NET。可以从 Exception Management Application Block 提供的 ApplicationException 类或 BaseApplicationException 类中继承自定义技术异常和自定义业务异常。 授权与安全性本节说明如何将安全性应用于数据访问逻辑组件和业务实体组件。.NET 公共语言运行库使用权限对象实现其对托管代码的强制限制机制。权限对象有三种,各自具有特定的用途:
托管代码可以使用 Principal 对象(包含对 Identity 对象的引用)来判断当事人的身份标识或角色。把 Identity 对象和 Principal 对象与用户、组帐户等大家所熟悉的概念比较可能会更容易理解。在 .NET Framework 中,Identity 对象表示用户,而角色表示成员身份和安全性环境。Principal 对象封装了 Identity 对象和角色。.NET Framework 中的应用程序根据 Principal 对象的身份标识或角色成员身份(后者更常见)授予 Principal 对象权限。 有关 .NET 中的权限与安全性的详细信息,请参阅 Key Security Concepts。 数据访问逻辑组件中的安全性建议数据访问逻辑组件的设计目的是供其他应用程序组件使用,它也是您的应用程序代码中在调用程序可以访问数据之前实现安全性的最后一个地方。 通常,数据访问逻辑组件可以依赖于由调用程序设置的安全性环境。然而,有些情况下数据访问逻辑组件必须执行自己的授权检查,以确定是否允许当事人执行所请求的操作。授权在身份验证后进行,并使用当事人身份标识与角色等有关信息来确定该当事人可以访问的资源。 在以下情况下,应在数据访问逻辑组件层次上执行授权检查:
在定义了 Identity 对象和 Principal 对象后,可以用三种方式执行基于角色的安全性检查:
以下代码示例显示了如何使用 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 身份验证。然而,应使用服务帐户并避免模拟连接到数据库,因为它会妨碍连接池。连接池需要相同的连接字符串;如果尝试使用不同的连接字符串打开数据库,就会创建单独的连接池,而这将限制可缩放性。 有关 Windows 身份验证和连接池的详细信息,请参阅 .NET Data Access Architecture Guide 中的“Managing Database Connections”。 安全通信建议要实现调用应用程序与数据访问逻辑组件之间的安全通信,请考虑以下建议:
业务实体组件中的安全性建议如果将业务实体实现为数据结构(如 XML 或 DataSet),则不需要实现安全性检查。然而,如果将业务实体实现为带有 CRUD 操作的自定义业务实体组件,请考虑以下建议:
部署本节提供一些建议以帮助您确定如何部署数据访问逻辑组件和业务实体组件。 部署数据访问逻辑组件部署数据访问逻辑组件的方法有两种:
部署业务实体应用程序的许多不同层都要使用业务实体。根据业务实体的实现方式,如果您的应用程序跨越各个物理层,则需要将业务实体部署到多个位置。下面列出了在不同实现方案中部署业务实体的方法:
附录如何定义数据访问逻辑组件类
如何定义数据访问逻辑组件类以下代码示例定义一个名为 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 应用程序中编程应用样式表,请执行以下步骤:
如何创建有类型的 DataSet可以使用有类型的 DataSet 表示业务实体。创建有类型的 DataSet 的方法有多种:
注意:也可以编程定义有类型的 DataSet,即从 DataSet 继承并定义方法、属性和嵌套类以表示该 DataSet 的结构。最简单的方法是使用以下过程之一创建一个有类型的 DataSet,然后将此有类型的 DataSet 类用作将来您自己的有类型的 DataSet 类的基础。 使用数据适配器创建有类型的 DataSet要使用数据适配器创建有类型的 DataSet,请执行以下步骤:
从 XSD 架构文件创建有类型的 DataSet要使用 Visual Studio .NET 从 XSD 架构文件创建有类型的 DataSet,请执行以下步骤:
使用 XSD 架构定义工具 (xsd.exe) 创建有类型的 DataSetXML 架构定义工具可以从 XSD 架构文件、XDR 架构文件或 XML 实例文档生成有类型的 DataSet。以下命令使用名为 XsdSchemaFile.xsd 的 XSD 架构文件,在当前目录中名为 XsdSchemaFile.cs 的 Visual C# 源文件中生成一个有类型的 DataSet: xsd /dataset /language:C# XsdSchemaFile.xsd 有关详细信息,请参阅 Generating a Strongly Typed DataSet。 如何定义业务实体组件以下示例显示了如何为 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 类,请注意以下几点:
如何将业务实体组件绑定到用户界面控件可以将用户界面控件绑定到 Windows 窗体和 ASP.NET 应用程序中的自定义实体。有两种可能的方案:
如何在业务实体组件中提供事件自定义实体可以在业务实体状态修改时产生事件。这些事件可用于获得丰富的客户端用户界面设计,因为这使得无论数据显示在哪里都可以对其进行刷新。以下代码示例显示了如何在 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”事件 } } // 必要时使用更多成员... } 关于上述代码,请注意以下几点:
如何将业务实体组件序列化为 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); } 关于上述代码,请注意以下几点:
序列化自定义实体对象的默认 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 序列化的默认规则:
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 的序列化,请注意以下几点:
控制序列化自定义实体对象的 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 {...获取和设置代码,同前...} // 必要时使用更多成员... }
<?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> 有关如何使用属性来控制 XML 序列化的详细信息,请参阅 Attributes that Control XML Serialization。 如何将业务实体组件序列化为 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)); // 必要时使用更多代码... } // 其他成员,同前... } 有关自定义 SOAP 序列化的详细信息,请参阅 Basic Serialization。 如何将业务实体组件序列化为二进制格式以下代码示例显示了如何使用 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 序列化的代码相同。 有关二进制序列化的详细信息,请参阅 Binary Serialization。 合作者衷心感谢以下投稿人和审阅人:Luca Bolognese、Mike Pizzo、Keith Short、Martin Petersen - Frey (PSS)、Pablo De Grande、Bernard Chen (Sapient)、Dimitris Georgakopoulos (Sapient)、Kenny Jones、Chris Brooks、Lance Hendrix、Pradyumna Siddhartha、Franco A. Ceruti (VBNext)、Diego Gonzalez (Lagash)、Chris Schoon。 同时,感谢以下内容小组成员:Chris Sfanos、Angela Crocker、Andy Olsen、Sharon Smith。 |
|||||||||