MSDN Magazine > Home > All Issues > 2008 > 二月 >  数据点: 设计实体数据模型
数据点
设计实体数据模型
John Papa

代码下载位置: DataPoints2008_02.exe (174 KB)
Browse the Code Online
实体框架是为 ADO.NET 开发的一种激动人心的新技术。开发人员可以借助它使用逻辑模型(而不是物理模型)查看数据,并提供了更高的灵活性。在 2007 年 7 月期的“数据点”中,我比较详细地概述了实体框架(预计在 2008 年上半年发布)。如果您还不熟悉实体框架,可以阅读此概述,网址是:msdn.microsoft.com/msdnmag/issues/07/07/DataPoints
实体框架的核心是实体数据模型 (EDM)。EDM 定义开发人员通过代码进行交互的实体类型、关系和容器。实体框架将这些元素映射到关系数据库公开的存储架构上。EDM 通过用于定义概念应用程序模型的 XML 向实体框架公开。概念模型可单独定义,也可与用于定义实际存储架构的 XML 以及用于定义两者之间映射的 XML 一起定义。尽管可以(有时也有必要)手动编辑 XML,但使用新的可视化实体数据模型设计器工具来创建和修改实体模型和映射会更加容易。
在本月的专栏中,将讨论如何使用新的可视化 EDM 设计器工具来设计实体模型,以及如何修改用于定义模型和映射的基础 XML。我将首先介绍在实体框架(包括 LINQ)中进行交互的各个方面,然后讨论 EDM 适合在何处使用。此外,还将演示如何使用可视化设计器工具创建实体模型和关联的映射。最后,将向您介绍可以帮助您修改和探索模型和映射的几个窗口。
在整个专栏中,我将解释 EDM 的不同组件(例如 EntityType 和 Association)的作用。本专栏中的示例演示了如何创建基本实体。请注意,本专栏中的所有示例均使用 Visual Studio® 2008 以及相应的 Entity Framework Beta 3(这些需要单独安装)。我使用的数据库是含有 Beta 3 中示例的经修改的 Northwind 示例数据库。

理解 EDM
在探讨如何创建和管理实体模型之前,首先介绍什么是 EDM 以及它与实体框架的其他元素之间如何进行交互。实体框架由许多部分组成,包括 EDM 规范及关联的映射、与 EDM 交互的 API,以及帮助定义和管理实体模型和映射的工具。设计实体模型之后,可以使用不同的 API 根据实体模型来编写代码,例如 EntityClient 提供程序或对象服务(包括 LINQ to Entities)。
EntityClient 数据提供程序具有与传统 ADO.NET 对象类似的模型,因为它使用 EntityConnection 和 EntityCommand 对象返回 DbDataReader。EntityClient 提供程序的命令是使用与 T-SQL 类似的实体 SQL 编写的,它对实体模型中定义的实体以及通过对象服务具体化的对象进行操作,而不是对数据库对象进行操作。您可以通过实体 SQL 或 LINQ to Entities 使用对象服务与 EDM 进行交互。通过对象服务,您可以利用概念模型的生成类,这些生成类提供了强类型化对象和持久性等特性(参见图 1)。
图 1 实体框架概述 
通过这些数据访问技术,您可以与实体模型中定义的概念实体进行交互,而不是与物理存储区(例如关系数据库)的对象进行交互。使用可视化设计器工具或手动编辑用于定义模型和映射的 XML,可以创建数据模型及关联的映射。图 2 中显示的实体框架为应用程序及其数据库之间提供了关联。EDM 用于通过概念实体模型和实体框架、通过映射规范来描述业务实体,然后将其转换为数据库物理存储区的表、视图、功能和过程。
图 2 实体框架将应用程序连接到其数据库 (单击该图像获得较大视图)
应用程序的实体模型是使用概念架构定义语言 (CSDL) 描述的。CSDL 是一种用于定义实体及实体之间关联的 XML 格式,开发人员通过 API(例如 LINQ to Entities)可与实体进行交互。实体框架还使用存储架构定义语言 (SSDL)(一种用于定义关系数据库的存储架构的 XML 格式)和映射架构语言 (MSL),来转换 CSDL 的实体映射到 SSDL 所描述的存储架构的方式。
CSDL 是开发人员影响作用最大的地方,因为开发人员最常交互的实体就是用它定义的。图 2 显示了可以将一些实体直接映射到数据库中的单个表中,而其他实体可以映射到多个表中。此实体是由开发团队根据业务模型来决定的。业务模型通常对数据库中多个物理表中存在的单个实体进行操作。从图 2 还可以看出,实体可以映射到数据库的视图中,也可以获取用于调用存储过程的方法。实体还可以使用概念模型中的继承从其他实体派生而来。以下是使用 EDM 设计器工具设计实体模型的一些方法。

使用向导构建 EDM
若要创建实体模型,首先需要添加新的 ADO。项目的 NET Entity Data Model 文件(参见图 3)。完成此操作后,实体数据模型向导将提示您是从数据库中生成模型还是从空模型开始构建。从现有数据库表中生成模型是一种不错的开始方法,只要有权访问数据库即可。某些开发方法(例如域驱动的设计)提倡在设计数据库之前设计实体域模型。如果您计划采用此类方法,则可能需要通过 EDM 可视化设计器创建一个空模型,然后创建您的实体。在我的示例中,开始时我使用 Northwind 数据库构建了一个实体模型。
图 3 向您的项目添加 EDM 文件 (单击该图像获得较大视图)
向导的下一屏幕提示您输入数据库连接信息。然后,向导会要求您选择模型中要包含的数据库对象。在图 4 中可以看出,我选择了 Northwind 数据库中的所有表和存储过程(帮助绘制图表的除外)。表最初直接映射到实体,存储过程可映射到生成容器上的方法。
图 4 选择数据库对象 
指定要在模型中包含的数据库对象之后,EDM 向导会生成用于定义模型和映射的 .edmx 文件,并向实体框架需要的项目添加相应的引用。.edmx 文件是一个 XML 文件,包含四个主要部分:关于设计器中概念模型的可视化布局的信息、概念模型的 CSDL、映射层的 MSL 以及物理模型的 SSDL。上述所有信息均包含在一个文件中。
.edmx 文件的设计器信息用于辅助 Visual Studio 在设计器中对实体模型进行布局;此文件仅在设计时使用。CSDL、MSL 和 SSDL 在编译时使用,用于生成表示 EDM 的类。

实体模型中的存储过程
我已选择向我创建的实体模型添加存储过程,但仅将存储过程添加到了 SSDL 定义。因为存储过程可以与数据库中的许多不同表或其他对象进行交互,所以实体框架不会将存储过程自动映射到 CSDL 中的任一特定实体。如果需要创建映射到存储过程的方法,则可以通过在 .edmx 文件中编辑 XML 来完成。
为了创建用于返回给定类型实体的方法,首先要选择一个存储过程。在本例中,我将添加一个名为 GetTenMostExpensiveProducts 的方法,该方法将执行 Ten Most Expensive Products 存储过程,并作为 Products 对象返回结果。在 SSDL 将存储过程描述为 Function 元素之后,下一步就是向 CSDL 中添加方法。通过将 FunctionImport 元素添加为 EntityContainer 元素的子元素可以完成此过程:
<FunctionImport Name="GetTenMostExpensiveProducts"
  EntitySet="Products"
  ReturnType="Collection(Self.Products)">
</FunctionImport>
Name 属性表示实体容器上方法的名称。EntitySet 属性表示 EntitySet,ReturnType 属性指将要返回的 EntityType(在本例中是指 EntityTypes 的集合)。
在本示例中,Self 的引用是一个别名,指当前命名空间(即 NWModel)。请注意,在此处 NWModel 或 Self 均可使用。
如果该方法中需要参数,则可以通过包含 <Parameter> 标记来添加参数。例如,如果还想在方法中添加存储过程中的 CategoryId 参数,则可以在 FunctionImport 元素中包含以下 XML 元素,从而将该参数添加到该方法中:
<Parameter Name="CategoryId" Type="Int32" Mode="in"/>
但我的示例中没有参数,因此跳过了这一步。
这时,CSDL 定义了方法、方法返回的实体类型以及返回的实体所属的 EntitySet,而 SSDL 定义了存储过程。现在,需要将 CSDL 映射到 SSDL,以使概念方法了解要执行何种存储过程。通过将以下 FunctionImportMapping 插入 MSL 的 EntityContainerMapping 部分可完成此映射:
<FunctionImportMapping 
  FunctionImportName="GetTenMostExpensiveProducts" 
  FunctionName="NWModel.Store.Ten_Most_Expensive_Products"/>
此处,FunctionImportMapping 元素使用 FunctionName 属性引用 SSDL 中 Function 元素的完全限定名称。FunctionImportName 属性引用 CSDL 中 FunctionImport 元素的名称。

EDM 中的窗口
设计和构建 EDM 之后,可以使用各种窗口来检查您创建的实体模型的状态。当需要检查对象是否可用于进行开发时,“类视图”窗口(参见图 5,该窗口不是一个新窗口)十分有用。例如,它将显示 NWEntities EntityContainer 类上的新 GetTenMostExpensiveProducts 方法,以及 NWModel 命名空间包含的所有类。其中有一个用于表示 CSDL 中每个 EntityType 的类,还有一个用于主要 NWEntities 模型的类。
Figure 5(图 5) 类视图中的 EDM 
还有一些新窗口,通过这些窗口您可以查看实体模型及相关映射。这些窗口包括“Entity Data Model Designer”(实体数据模型设计器)、“Entity mapping Details”(实体映射详细信息)窗口以及“Entity Model Browser”(实体模型浏览器)窗口。
“Entity Model Browser”(实体模型浏览器)显示了所有 CSDL 组件和 SSDL 组件。其中包括 CSDL 组件 EntityTypes、Associations、EntitySets、AssociationsSets 和 Function Imports 以及所有 SSDL 元素。
“Entity Model Designer”(实体模型设计器)中(参见图 6)显示了包含已生成的模型的布局。通过此设计器可以查看实体模型并对其进行操作,实体模型表示以可视化布局显示的所有概念模型的元素。请注意,图 6 中有一个 Product 实体,它表示 CSDL 中的 Product EntityType。每个 EntityType 均包含一个属性列表(标量和导航)。
图 6 实体数据模型设计器 (单击该图像获得较大视图)
导航属性用于在 CSDL 的关联中进行导航。导航属性成为其 EntityType 类上的公共属性,用于引用另一实体或与原始实体相关的一系列实体。例如,Product EntityType 具有名为 Categories 的导航属性,该属性将引用特定 Product 实体实例的 Category 实体。Category EntityType 还具有名为 Products 的导航属性。此属性的作用是使您可以通过 Category 实体实例引用与其关联的 Product 实体。
设计器可用于添加、编辑或删除实体类型、关联、标量属性和导航属性。例如,您可以将所有 EntityTypes 的名称更改为开发人员在命名实体时常用的单数形式。只需单击 EntityType 的名称并在其中编辑名称即可。也可以选择 EntityType,并在“Properties”(属性)窗口中更改其名称。在这些示例中,我将所有 EntityTypes 重命名为单数形式,这在图 6 可以反映出来。完成更改后,我还修复了此前修改的 XML,将存储过程调用添加为 NWEntities EntityContainer 的一个方法。此步骤非常简单,只需更新引用 Product EntityType(与 Products 相对)的代码即可,如下所示:
<FunctionImport Name="GetTenMostExpensiveProducts"
  EntitySet="Products"
  ReturnType="Collection(Self.Product)">
</FunctionImport>
下面是需要重点注意的问题。最好在开始修改数据模型之前命名主要元素,例如 EntityType、EntityContainer 和 Association。这样可以最大限度地减少需要手动更改的次数,对于您已针对数据模型编写的代码而言,也可以最大限度地减少需要更改的代码次数。如果遇到问题,“Error List”(错误列表)窗口将显示哪些元素在 XML 中的引用无效,为您提供帮助。
最后,当设计器显示概念模型时,“Entity Mapping Details”(实体映射详细信息)窗口将提供一种查看和编辑 EDM 与数据存储区之间映射 (MSL) 的方法。

派生实体
帮助您设计实体模型的工具也可以帮助您修改模型。对于面向对象的编程而言,一个关键点是要了解继承的概念。EDM 支持在 XML 中和以可视化方式在 EDM 设计器中创建和修改继承的实体。
为演示设计继承实体的任务,我将基于 Product EntityType 创建 DiscontinuedProduct EntityType。Product EntityType 具有名为 Discontinued 的 Boolean 标量属性,我将其用作在确定产品特定实例类型时要计算的 Condition。使用“Entity Model Designer”(实体模型设计器)时,可以在设计器中单击右键,然后从弹出菜单中选择“Add”(添加)|“Entity”(实体)。然后,我输入 DiscontinuedProduct 作为新实体名称,并选择 Product 作为基本实体(参见图 7)。
图 7 创建继承实体 
在下一步中,需要使用 Condition 定义这些实体之间的区别因素,因此我在设计器中选择了 Product EntityType,并转至“Entity Mapping Details”(实体映射详细信息)窗口。然后,在“Column Mappings”(列映射)中选择 Discontinued 属性,并删除该映射。这将从 Product 和 DiscontinuedProduct EntityTypes 中删除 Discontinued 属性(创建 Condition 时,Condition 的可用表达式必须尚未用作属性)。接着,转至“Entity Mapping Details”(实体映射详细信息)窗口的“Maps to Products”(映射到产品)部分,并添加 Condition of Discontinued = 0。然后选择 DiscontinuedProduct EntityType,并添加 Condition of Discontinued = 1。
使用此技术创建派生实体确实特别方便。如果需要,除基类的属性之外,您还可以向派生实体添加其他属性。在 .NET 代码中创建 DiscontinuedProduct 的实例并保存之后,实体框架就会知道必须将数据库中 Discontinued 列的值设置为 0,这是因为其中存在 Conditions。
Conditions 是在 MSL 的 EntityTypeMapping 部分创建的,在检索行时只需实施筛选器即可。保存数据时,Conditions 用于根据派生类型确定写入基础数据库列的值。

结束语
使用 Visual Studio 2008 中的设计工具和 XML 文件,您可以设计使用继承、调用存储过程和对业务建模的实体数据模型,而不用直接针对您的关系数据库架构编写代码。设计完成可靠的实体模型之后,通过 API(例如对象服务)与其进行交互就非常简单了;对数据模型的修改也不需要在数据库中进行,因为映射可以将概念模型与存储模型隔离开来。如果您对此处介绍的有关实体框架的一些概念不太熟悉,强烈建议您参阅我的实体框架概述,网址是:msdn.microsoft.com/msdnmag/issues/07/07/DataPoints

请将您想向 John 询问的问题和提出的意见发送至 mmdata@microsoft.com。 mmdata@microsoft.com.


John Papa 是 ASPSOFT (aspsoft.com) 的一位资深 .NET 顾问,同时也是一位狂热的棒球迷,在夏季的大多数夜晚,他都与家人以及忠实的狗 Kadi 一起为洋基队加油。John 是 C# 领域的一位 MVP,撰写过多本有关 ADO、XML 和 SQL Server 方面的书籍。他经常在行业会议(如 VSLive)上发表演讲,或者在 codebetter.com/blogs/john.papa 上撰写博客文章。

Page view tracker