品牌网站建设

psd转xhtml/css 88元/页起

  • 符合W3C标准的XHTML/CSS编码
  • 多浏览器及操作平台支持
  • SEO(搜索引擎)语义代码标准
  • 经过优化的和切片图像
  • 结构良好的XHTML/CSS
  • 转换页面越多,折扣越多
更多优惠

手机13146413981qq393992480msnyibing98@hotmail.com

当 n 层体系结构的架构师评估任何新技术、模式或策略时,他们必须考虑新的谜团将如何与体系结构相融合。有了实体框架,集成将不再是问题。它可以集成到 n 层体系结构以及单层体系结构中。
在这里,我将介绍如何使实体框架能够适合于使用 Windows® Communication Foundation (WCF)、Windows Presentation Foundation (WPF) 技术以及 Model View Presenter (MVP) 模式的 n 层体系结构。我将演示一个示例体系结构,其中包含逻辑存储数据库层、数据访问层、域模型层、业务经理层、服务层、表示层以及被动 UI 层,同时我还将介绍如何使用实体框架来集成这些层。

定义层

我 将要展示的应用程序允许用户在 NorthwindEF 示例数据库中搜索客户并对其执行查看、添加、编辑或删除操作。在深入探究代码和示例之前,让我们先讨论一下此示例的整体体系结构。由于我的重点并不在体系 结构本身,而是如何将实体框架与体系结构设计相集成,因此我选择了一个比较常见的体系结构,它可以在经过修改后非常方便地与其他策略相集成。

图 1 显示了典型的分层式体系结构的高级视图。顶部的两层使用 UI 层和表示层来处理用户界面表示和导航。UI 层可通过各种技术来实现;但是,在本专栏的相关示例中,我将使用 WPF。UI 层遵从带有被动视图的 MVP 模式,这意味着视图(顶部 UI 层)由表示层进行管理和控制。表示器负责为这些视图提供数据、从视图中抽取数据以保存在较低层,一般情况下还负责响应由视图引发的事件。

图 1 体系结构概述(单击图像可查看大图)

 

在此处的示例中,表示器通过 WCF 与较低的层进行通信。表示器使用服务的约定作为指导通过 WCF 来调用该服务。服务层通过服务约定接口提供服务。利用这些约定,表示器可以确定如何调用服务。

服 务层负责接收来自表示器的通信并调用相应的业务层方法,这些方法将执行相应的业务逻辑和数据收集或修改操作。业务层是业务逻辑和此项目的 LINQ to Entities 代码将要驻留的位置。LINQ to Entities 代码将引用从实体框架所生成的实体模型。执行 LINQ 查询时,实体框架会将 LINQ 查询转换为概念实体模型(实体数据模型或 EDM)、将实体内容映射到存储层、生成 SQL 查询并针对数据库加以执行。

 

构建模型

现在我已提供了对各个层在体系结构中的工作方式的深层次说明,接下来让我们看一下当各个层与实体框架关联时要注意的关键问题。由于应用程序的数据库已经存在,因此我使用 NorthwindEF 数据库作为起点来生成实体模型。

也可以先构建实体模型,然后再将实体映射到数据库中。EDM 向导将帮助生成基本实体模型,然后可根据需要对此模型进行修改以合并集成、实体拆分以及其他域建模概念。图 2 显示了 EDM 向导,以及被选中导入 EDM 的所有表格和存储过程。


 
图 2 从数据库生成模型(单击图像可查看大图)

 

人们经常会对 EDM 感到困惑的一个主题是 EntitySets 和 EntityTypes 的默认命名约定。我喜欢对域模型中的所有实体使用单数名称。我创建一个 Customer 实例或使用 List<Order> 返回一个 Order 实例的列表。每个实体都是蓝图的一个单一实例,而该蓝图则拥有定义实体的各种属性。

但是,对于 EntitySets 我喜欢使用复数命名约定。当要求 ObjectContext 引用其 Customers 或 Orders 集时,通常会在 LINQ 查询中使用 EntitySets。

如下例所示,让我们来看一看下面的 LINQ to Entities 查询:

var q = from c in context.Customers
        select c;
List<Customer> customerList = q.ToList();

此查询告诉 LINQ to Entities 访问 Customers EntitySet 并在执行后返回所有 Customer 实体实例。第二行执行查询并将 List<Customer> 返回到名为 customerList 的本地变量。在本例中,EntitySet 为复数形式,这是为了指明它正在查询 EntitySets 并将返回 Customer 实体的实例(请注意为单数)。

有 必要采用此命名约定吗?当然没有。不过,我发现它有助于提高代码的可读性。否则,如果您采用 EDM 向导返回的默认值,那么您将得到一个名为 Customers 的 EntitySets 和一个名为 Customers 的 EntityType,从而使您的 LINQ to Entities 查询类似于下面所示:

var q = from c in context.Customers
          select c;
  List<Customers> customerList = q.ToList();

EDM 向导生成模型时,可以方便地对 EntitySet 和 EntityType 的名称进行修改。通过在图表中选择该实体、在“属性”窗口中查看其属性、然后修改所需的设置即可完成此操作(请参见图 3)。对于此应用程序,我通过设置 Name 属性将所有 EntityTypes 都修改为单数形式。我没有更改 EntitySet Name 属性,因为它已经是复数形式。
 

图 3 更改 EntityType Name(单击图像可查看大图)

工作原理

现 在我将演示此应用程序并通过视图(位于 NWUI 项目中)和表示器(位于 NWPresentation 项目中)来讨论它如何从顶层向下开始运行。这两个项目均可从本专栏随附的代码下载中获得。此应用程序会加载客户搜索视图,允许用户通过匹配公司名称这一条 件来搜索客户(请参见图 4)。此视图是使用 WPF 实现的,当用户与其交互时,它会引发一些事件,其表示器能够侦听到这些事件并随后采取相应的操作。


 
图 4 搜索客户(单击图像可查看大图)
 
如果用户搜索以字母 D 开头的所有客户(如图 4 所示),则当用户单击“搜索”按钮时,此视图会引发一个事件。表示器会侦听此事件,并通过在 WCF 中调用服务层来作为响应,从而获取将要显示在 CustomerSearchView 中的客户实体的列表。以下是用户单击“搜索”按钮时视图中的代码:
private void btnSearch_Click(object sender,     RoutedEventArgs e)  {
      if (FindCustomerSearchResults != null)          FindCustomerSearchResults();
  }

此代码并不与返回的实体列表交互,而是将其留给表示器来处理。视图使用 WPF 数据绑定来引用实体的属性,因此它知道如何将实体列表绑定到列表视图控件的元素。视图与实体间存在的唯一交互是通过数据绑定完成的。

CustomerSearchView 会引发事件 FindCustomerSearchResults,而 CustomerSearchPresenter 会侦听此事件,然后接管任务并继续执行搜索作为对此的响应。以下代码显示了 CustomerSearchPresenter 类如何创建 NWServiceClient 类的实例,其中 NWServiceClient 类是在较低层提供的 WCF 服务的代理:

public void view_FindCustomerSearchResults()
{
    if (this.view.CompanyNameCriteria.Length > 0)
        using (var svc = new NWServiceClient())
        {
            IList<Customer> customerList = svc.FindCustomerList(                view.CompanyNameCriteria);
            view.CustomerSearchResultsList = customerList;
        }
}

NWServiceClient 是通过 ServiceReference 从 NWPresentation 项目引用的,因此表示器知道应如何调用服务以及将会返回哪些类型的数据。表示层不会也不应当直接引用 EDM。相反,应该通过 WCF 所提供的 DataContracts 来告诉它应使用哪些类型的实体。这样即可通过 WCF 将实体框架中的实体跨物理网络边界传递给表示器。

请 注意,此 Customer 实体列表一经返回,即会被设置为视图的公共属性。此视图属性随后将接受 List<Customer> 并将其绑定到视图的 DataContext。表示器会提供数据并进行传递,然后由视图处理任何特定于视图的绑定(因为该代码技术针对性特别强,可能会涉及 WPF、Silverlight®、Windows Forms 或 ASP.NET 等领域)。

此方法允许使用同一个表示器与实现 ICustomerSearchView 接口的所有视图进行交互。此应用程序是使用 DataContext 并通过 WPF 绑定技术对绑定进行处理的。

约定会公开可在服务层中调用的方法以及将被返回的实体。在此应用程序中,我只有一个返回 Customer 和 Order 实体类型的方法。这意味着只有这些实体类型才会包括在约定中。

WCF 通过根据需要将 WCF DataContract 属性应用到实体中来处理实体的序列化。通过 DataContracts 提供实体,可在 UI 层中使用这些实体而无需直接引用 EDM。

请 注意,从 .NET Framework 3.5 SP1 Beta 1 开始,实体框架就已支持自动图形序列化。例如,如果某个父实体具有关联的子实体,则该父实体及其子实体都将被序列化。在示例应用程序中,由于 OrderManager 的 FindOrderList 方法使用了为每个 Order 预先加载 Order Details 的 LINQ to Entities 查询,因此从中间层返回的每个 Order 实体都将包含可通过其导航属性访问的 List<OrderDetail>。

虽然序列化实体可通过 WCF 在表示器和服务层之间传递,但是 ObjectContext 既不会被序列化也不会传递给表示器。这意味着这些实体可在 UI 层中使用,但 ObjectContext 会留在较低层,在那里它可以访问 EDM 和实体框架的全部资源。

舍 弃 ObjectContext 意味着无法使用它在 UI 层中直接检索或修改实体,也无法使用它在 UI 层中管理更改跟踪。总之,这些角色原则上被留在了较低层。但是当实体被传回较低层时,应用程序必须与 ObjectContext 同步,以便可以保留在实体中所做的任何更改。

用户单击图 4 中的“搜索”按钮后,表示器会调用服务层,而服务层随后会调用业务层(位于 NWBusinessManagers 项目中)来检索 List<Customer>。此层具有两个主要角色。第一个角色将从 EDM 获取数据或将数据放入其中。第二个角色将处理可能存在的任何业务逻辑。

CustomerManager 使用 ObjectContext 处理与 EDM 的交互,因此它将定义一个名为 context 的本地字段并在其构造函数中创建一个自身的实例。ObjectContext 可以在每个方法中创建和销毁。但是,它已被优化为根据需要打开和关闭数据库连接资源。另外,通过使 ObjectContext 在整个类中都可访问,它将能够一直跟踪更改,而不必在类中使用一系列专用方法进行传递:

public CustomerManager()
{
    context = new NWEntities();
}

但请注意,对于此类应用程序,ObjectContext 不应保留而应根据需要创建/销毁。由于身份解析原因,保持同样的对象上下文会最终导致数据的不一致和失效(因为跟踪的数据越来越多),并会在进行身份解析时使性能降低,此外还可能在多线程环境中引发更新问题。

以 下代码显示了业务层中 CustomerManager 类的 FindCustomerList 方法。此方法声明了一个 LINQ to Entities 查询,可用于访问上下文以请求使用该此条件开始的 Customer 实体的列表。此查询执行完毕后,它会评估从概念层到存储层的映射并生成相应的 SELECT 命令:

public List<Customer> FindCustomerList(string companyName)
  {
      var q = from c in context.Customers
              where c.CompanyName.StartsWith(companyName)
              select c;
      return q.ToList();
  }
如果需要,您可以使用 SQL Server® Profiler 在查询执行时对其进行查看。

 

保持更改

至此我已通过一个简单的检索过程演示了这一应用程序,接下来再了解一下如何保持对数据的修改。用户编辑客户时,CustomerView 视图会与相应的 Customer 实体实例绑定(请参见图 5)。CustomerView 会引发一个表示器事件,表示器随后会从较低层请求 Customer 实体实例。

图 5 编辑客户(单击图像可查看大图)
 

当用户对客户进行修改并将结果保存下来时,可使用图 6 所示的代码将实体从表示器传递到较低的层。此代码先判断用户是添加还是修改客户,然后调用相应的服务层方法并传递实体。

 图 6 表示器中的 SaveCustomer

public virtual void view_SaveCustomer()
{
    Customer customer = view.CurrentCustomer;
    var svc = new NWServiceClient();
    switch (view.Mode)
    {
        case ViewMode.EditMode:
            svc.UpdateCustomer(customer);
            break;
        case ViewMode.AddMode:
            svc.AddCustomer(customer);
            break;
        default:
            break;
    }
    view.CurrentCustomer = FindCustomer();

}

服务层然后会将控制传递到业务层,再由业务层将客户实体保存到数据库。由于客户实体不再是 ObjectContext 的一部分,因此它必须首先通过使用 ObjectContext 的 Attach 方法重新结合一个,如以下代码所示。实体被附加到上下文后,必须将实体的属性标记为已修改。使用上下文的 ObjectStateManager 并调用每个属性的 SetModified 方法即可实现此目的。现在上下文已知道实体被修改,接下来会启动 SaveChanges 方法,这将生成 SQL UPDATE 命令并对数据库执行此命令:

public void UpdateCustomer(Customer customer)
{
    context.Attach(customer);
    customer.SetAllModified(context);     // custom extension method
    context.SaveChanges();
}

请注意,UpdateCustomer 方法中的代码将使用被我命名为 SetAllModified<T> 的扩展方法,它可以更加轻松地设置要修改实体的所有属性的状态。SetAllModified<T> 可获取给定实体 T 中 ObjectStateEntry 的一个实例。然后它会检索该实体所有属性名的列表并为每个属性循环调用 SetModifiedProperty:

public static void SetAllModified<T>(this T entity, ObjectContext  context) 
where T : IEntityWithKey
{
    var stateEntry = context.ObjectStateManager.      GetObjectStateEntry(entity.EntityKey);
    var propertyNameList = stateEntry.CurrentValues.DataRecordInfo.      FieldMetadata.Select
      (pn => pn.FieldType.Name);
    foreach (var propName in propertyNameList)
        stateEntry.SetModifiedProperty(propName);
}

最终保存实体的另一个方法是调用上下文的 Refresh 方法。这会告诉上下文需要获取实体实例的数据并根据数据库的值刷新其属性值。ClientWins 的 RefreshMode 枚举器将用数据库中的最新值替换原始值,从而允许后进有效策略。

StoreWins 的 RefreshMode 会用数据库中的值将实体缓存中的原始值和当前值均覆盖掉。ClientWins 是适合后进有效的策略,而当您要取消更改并用最新的数据库值刷新 UI 视图时适合使用 StoreWins 策略:

context.Refresh(RefreshMode.ClientWins, customer);  // Last in wins

生成更新和删除命令时,实体框架会强制执行最优的并发操作。为此,需要将原始值包括在任意属性的 WHERE 子句中并将 ConcurrencyMode 属性值设置为 Fixed。

默 认情况下,在生成模型时字段不会被指定为并发字段。这意味着当某个用户保存其所做的更改时,他可能会意外覆盖其他用户的更改结果。如果其他用户在 CustomerView 打开的情况下更改了某个值,则当您想要使用最优并发操作时,可以通过在概念模型中设置 EntityType 的 ConcurrencyMode 属性来达到此目的。

编 辑 EDM 文件并将 ConcurrencyMode 设置为 Fixed 时,将告诉实体框架将此列添加到任意 Update 或 Delete 命令的 WHERE 子句中。这样,如果未找到匹配行,就会引发 OptimisticConcurrencyException。图 7 显示的是由于我在其他用户试图修改数据库中的某个区域之前修改了该区域而引发的这种异常。

图 7 OptimisticConcurrencyException(单击图像可查看大图)
 

您可以捕获此异常并采取任何适当的操作。例如,您可以捕获该异常,将其记录下来,然后以任何一种方式覆盖该用户的更改结果,如下所示:

catch (OptimisticConcurrencyException e){
    context.Refresh(RefreshMode.ClientWins, customer); // Last in wins
    logger.Write(e);
    context.SaveChanges();
}

删除和添加

当用户删除某个客户时,CustomerManager 的 DeleteCustomer 方法会获取该客户实体并应用删除操作:

context.Attach(customer);
context.DeleteObject(customer);
context.SaveChanges();

首先,Customer 实体实例必须使用 Attach 方法重新结合一个 ObjectContext。然后必须从 ObjectContext 中将该客户删除。这样,ObjectContext 中的更改跟踪机制会了解到 Customer 实体实例已被删除。最后,当调用 SaveChanges 方法时,ObjectContext 会了解到实体已被删除,自己应生成 DELETE SQL 命令并执行它。

删除客户时,CustomerManager 的 AddCustomer 方法会获取该客户实体并应用插入操作,如下所示:

context.AddToCustomers(customer);
context.SaveChanges();

由于这是一个新实例,因此必须使用 AddToCustomer 方法将 Customer 实体实例与 ObjectContext 相关联,以将其添加到上下文中并标记为 Customer 实体的新实例。最后,当调用 SaveChanges 方法时,ObjectContext 会了解到实体已被添加,自己应生成 INSERT SQL 命令并执行它。

 

总结

在 本文中,我演示了如何将实体框架集成到体系结构中、如何使用一些最新的模式(如 MVP 模式)以及如何处理常见的体系结构问题。分层式体系结构中实体框架的主要内容包括它的更改跟踪机制、与 LINQ to Entities 的集成、断开和重新连接其 ObjectContext 的能力以及为开发人员提供的处理并发问题的方法。

代码下载(3,549 KB)

Add comment


 

biuquote
  • Comment
  • Preview
微笑得意调皮害羞酷大笑惊讶发呆喜欢可怜尴尬闭嘴噘嘴皱眉伤心抓狂呕吐坏笑漫骂发怒
Loading



订阅新易网博客

  • 订阅到抓虾
  • 哪吒提醒
  • pageflakes
  • Add to My Yahoo!
  • Add to Google
  • 鲜果阅读器订阅图标
  • 订阅到有道阅读
  • 用QQ邮箱阅读空间订阅我的博客。
专业设计 量身定制 品牌网站建设 体验价只需999元
.me 我要我的域名 新网域名 260元/年 再送空间100M
印彩色名片,每盒仅5元
免费推广您的网站或产品 互换广告位、友情连接、软文

Recent comments