数据访问逻辑组件是一个无状态类,也就是说,所交换的所有消息都可以独立解释。调用之间不存在状态。数据访问逻辑组件为访问单一数据库(某些情况下可以是多个数据库,例如水平数据库分区)中的一个或多个相关表提供方法。通常,数据访问逻辑组件中的这些方法将调用存储过程以执行相应操作。数据访问逻辑组件的主要目标之一是从调用应用程序中隐藏数据库的调用及格式特性。数据访问逻辑组件为这些应用程序提供封装的数据访问服务。具体地说,数据访问逻辑组件处理以下实现细节:管理和封装锁定模式 
正确处理安全性和授权问题 
正确处理事务处理问题 
执行数据分页 
必要时执行数据相关路由 
为非事务性数据的查询实现缓存策略(如果适用) 
执行数据流处理和数据序列化 
数据访问逻辑组件使用 ADO.NET 执行 SQL 语句或调用存储过程。有关数据访问逻辑组件类的示例,请参阅附录中的如何定义数据访问逻辑组件类。如果您的应用程序包含多个数据访问逻辑组件,可以使用数据访问助手组件来简化数据访问逻辑组件类的实现。该组件可以帮助管理数据库连接、执行 SQL 命令以及缓存参数。数据访问逻辑组件仍然封装访问特定业务数据所需的逻辑,而数据访问助手组件则专注于数据访问 API 的开发和数据连接配置,从而帮助减少代码的重复。Microsoft 提供了 Data Access Application Block for .NET,当使用 Microsoft SQL Server™ 数据库时,可在您的应用程序中将其用作一个通用的数据访问助手组件。图 6 所示为使用数据访问助手组件帮助实现数据访问逻辑组件的方法。图 6: 使用数据访问助手组件实现数据访问逻辑组件当存在所有数据访问逻辑组件公用的实用程序功能时,可以定义一个基本类以从中继承和扩展数据访问逻辑组件。将数据访问逻辑组件类设计为可以为不同类型的客户端提供一致的接口。如果将数据访问逻辑组件设计为与当前及潜在的业务过程层的实现要求相兼容,可以减少必须实现的附加接口、接触面或映射层的数目。要支持广泛的业务过程和应用程序,请考虑以下技术以便将数据传入和传出数据访问逻辑组件方法:将业务实体数据传递给数据访问逻辑组件中的方法。您可以用多种不同的格式传递数据:作为一系列标量值、作为 XML 字符串、作为 DataSet 或作为自定义业务实体组件。 
从数据访问逻辑组件中的方法返回业务实体数据。您可以用多种不同的格式返回数据:作为输出参数标量值、作为 XML 字符串、作为 DataSet、作为自定义业务实体组件或作为数据读取器。 
以下各节将说明用于将业务实体数据传入和传出数据访问逻辑组件的各种方式以及每种方式的优缺点。这些信息有助于您根据自己特定的应用程序方案做出相应选择。将标量值作为输入和输出传递
这种方法的优点如下:抽象。调用程序只需要知道定义业务实体的数据,而不需要知道业务实体的具体类型或具体结构。 
序列化。标量值本身支持序列化。 
内存使用效率高。标量值只传递实际需要的数据。 
性能。当处理实例数据时,标量值具有比本文所述的其他方法更高的性能。 
这种方法的缺点如下:紧密耦合与维护。架构的更改可能需要修改方法签名,这会影响调用代码。 
实体集合。要向数据访问逻辑组件保存或更新多个实体,必须进行多次单独的方法调用。这在分布式环境中会给性能带来很大影响。 
支持开放式并发。要支持开放式并发,必须在数据库中定义时间戳列并将其作为数据的一部分。 
将 XML 字符串作为输入和输出传递
这种方法的优点如下:松散耦合。调用程序只需要知道定义业务实体的数据和为业务实体提供元数据的架构。 
集成。采用 XML 可以支持以各种方式(例如,.NET 应用程序、BizTalk Orchestration 规则和第三方业务规则引擎)实现的调用程序。 
业务实体集合。一个 XML 字符串可以包含多个业务实体的数据。 
序列化。字符串本身支持序列化。 
这种方法的缺点如下:需要重新分析 XML 字符串。必须在接收端重新分析 XML 字符串。很大的 XML 字符串会影响性能。 
内存使用效率低。XML 字符串比较繁琐,因而在需要传递大量数据时会降低内存使用效率。 
支持开放式并发。要支持开放式并发,必须在数据库中定义时间戳列并将其作为 XML 数据的一部分。 
将 DataSet 作为输入和输出传递
这种方法的优点如下:固有功能。DataSet 提供了内置功能,可以处理开放式并发(以及数据适配器)并支持复杂的数据结构。此外,有类型的 DataSet 还提供了数据验证支持。 
业务实体集合。DataSet 是为处理复杂的关系集合而设计的,因此不需要再编写自定义代码来实现这一功能。 
维护。更改架构不会影响方法签名。然而,如果使用的有类型的 DataSet 和程序集具有严格名称,则必须按照新版本重新编译数据访问逻辑组件类,或在全局程序集缓存中使用发布者策略,或在配置文件中定义一个 <bindingRedirect> 元素。有关运行时如何定位程序集的信息,请参阅 How the Runtime Locates Assemblies。 
序列化。DataSet 本身支持 XML 序列化,并且可以跨层序列化。 
这种方法的缺点如下:性能。实例化和封送处理 DataSet 会增加运行时负担。 
表示单个业务实体。DataSet 是为处理一组数据而设计的。如果您的应用程序主要处理实例数据,则标量值或自定义实体是更好的方法,后者不会影响性能。 
将自定义业务实体组件作为输入和输出传递
这种方法的优点如下:维护。更改架构不会影响数据访问逻辑组件方法签名。然而,如果业务实体组件包含在严格命名的程序集中,就会出现与有类型的 DataSet 同样的问题。 
业务实体集合。可以将自定义业务实体组件的数组和集合传入和传出方法。 
这种方法的缺点如下:支持开放式并发。要方便地支持开放式并发,必须在数据库中定义时间戳列并将其作为实例数据的一部分。 
集成限制。当使用自定义业务实体组件作为数据访问逻辑组件的输入时,调用程序必须知道业务实体的类型,而这会限制不使用 .NET 的调用程序的集成。然而,如果调用程序使用自定义业务实体组件作为数据访问逻辑组件的输出,则上述问题并不会限制集成。例如,Web 方法可以返回从数据访问逻辑组件返回的自定义业务实体组件,并使用 XML 序列化自动将该业务实体组件序列化为 XML。 
将数据读取器作为输出返回
这种方法的优点如下:性能。当需要快速呈现数据时,这种方法具有性能优势,并且可以使用表示层代码部署数据访问逻辑组件。 
这种方法的缺点如下:远程。建议不要在远程方案中使用数据读取器,因为它可能会使客户端应用程序与数据库保持长时间的连接。 
手动实现开放式并发
请考虑以下 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 子句,使得如果自上次查询以来特定字段被更新,则不覆盖数据。使用数据适配器和 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 Upda

解决方案 »

  1.   

    以下代码示例显示了如何使用 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");
      }
    }