第12章 ADO.NET实体框架

对于“实体框架”这个词语,相信有过ASP.NET编程经验的人员对此并不陌生。ADO.NET实体框架支持以数据为中心的应用程序和服务,并提供平台用于对数据进行编程,该平台将抽象级别从逻辑关系级别提升为概念级别。通过使开发人员可以在更高的抽象级别上使用数据,实体框架支持独立于任何特定数据存储引擎或关系架构的代码。也就是说,实体框架可以使你采用特定于域的对象和属性(如客户和客户地址)的形式使用数据,而不必自己考虑存储这些数据的基础数据库表和列。借助实体框架,在处理数据时能够以更高的抽象级别进行工作,并且能够以相比传统应用程序更少的代码创建和维护面向数据的应用程序。

12.1 理解ADO.NET实体框架

通常,在构建一些数据库应用程序或服务时,将应用程序或服务分为三部分:域模型、逻辑模型和物理模型。

其中,域模型(在实体框架中称为“概念”模型)定义要建模的系统中的实体和关系,它通常用作捕获和沟通应用程序要求的工具,常常以静态关系图形式提供,用于在项目早期阶段查看和讨论之用,用完之后会被弃用或作为系统文档保存起来;关系数据库的逻辑模型则是通过外键约束将实体和关系规范化到表中,而编写应用程序代码的程序员的工作主要限制为通过编写SQL查询和调用存储过程来处理逻辑模型;而物理模型则是通过指定分区和索引等存储详细信息实现特定数据引擎的功能,它由数据库管理员进行优化以改善性能。

在ADO.NET实体框架中,大大地简化了概念模型的创建方法,从而可以让你快速地创建出概念模型。你可以随意查询概念模型中的实体和关系,同时依靠实体框架将这些操作转换为特定于数据源的命令,从而赋予模型生命。这使应用程序不再对特定数据源具有硬编码的依赖性。概念模型、存储模型以及这两者之间的映射以基于XML的架构表示,并在具有对应扩展名的文件中定义,如图12-1所示。

如图12-1所示,应用程序的实体模型是使用概念架构定义语言((Cnceptual Schema Definition Language, CSDL)描述的,CSDL是一种用于定义实体及实体之间关联的XML格式,开发人员通过API(例如LINQ to Entities)可与实体进行交互;而存储架构定义语言((Sore Schema Definition Language, SSDL)是一种用于定义关系数据库的存储架构的XML格式,即它定义了存储模型,也称为“逻辑模型”;映射规范语言((Mpping Specification Language, MSL)则定义存储模型与概念模型之间的映射。

figure_0430_0321

图 12-1 实体框架将应用程序连接到其数据库

其中,可以将一些实体直接映射到数据库中的单个表或者多个表中。此实体是由开发团队根据业务模型来决定的。业务模型通常对数据库中多个物理表中存在的单个实体进行操作。除此之外,实体还可以映射到数据库的视图中,也可以获取用于调用存储过程的方法。同时,实体还可以使用概念模型中的继承从其他实体派生而来。

12.1.1 生成模型和映射

实体框架的核心是实体数据模型((Etity Data Model, EDM)。EDM定义开发人员通过代码进行交互的实体类型、关系和容器。实体框架将这些元素映射到关系数据库公开的存储架构上。EDM通过用于定义概念应用程序模型的XML向实体框架公开。概念模型可单独定义,也可与用于定义实际存储架构的XML以及用于定义两者之间映射的XML一起定义。尽管可以(有时也有必要)手动编辑XML,但使用可视化实体数据模型设计器工具来创建和修改实体模型和映射会更加容易。

ADO. NET实体数据模型设计器(实体设计器)是支持通过单击鼠标修改.edmx文件的工具。通过使用实体设计器,可以直观地创建和修改实体、关联、映射和继承关系。当然,还可以验证.edmx文件。使用ADO.NET实体数据模型设计器创建实体模型的步骤如下:

1)右击鼠标选择“Add”|“New Item”命令,在弹出的“Add New Item”对话框里选择“ADO.NET Entity Data Model”,并将该文件命名为“Employees.edmx”,如图12-2所示。

figure_0430_0322

图 12-2 选择ADO.NET Entity Data Model

2)完成此操作后,实体数据模型向导将提示你是从现有数据库表中生成模型还是从空模型开始构建。

其中,从现有数据库表中生成模型是一种不错的开始方法,只要有权访问数据库即可。当使用实体数据模型工具从现有数据库生成概念模型时,需要考虑如下注意事项:

1)所有实体都必须具有键。如果数据库中有一个未设置主键的表,那么实体数据模型工具会尝试为相应的实体推断一个键。此外,实体数据模型工具会在存储架构中生成一个DefiningQuery元素,使此实体的数据为只读。若要使此实体数据成为可更新数据,必须确认所生成的键是有效键,然后删除DefiningQuery元素。

2)仅包含外键、表示数据库中两个表之间的多对多关系的表(有时称为纯连接表)在概念模型中没有对应的实体。当实体数据模型工具遇到此类表时,会在概念模型中将该表表示为一个多对多关联,而不是实体。

除了从现有数据库表中生成模型之外,还有某些开发方法提倡在设计数据库之前设计实体域模型,例如域驱动的设计方法。如果计划采用此类方法,则可能首先需要通过EDM可视化设计器创建一个空模型,然后创建实体。

在这里,为了能够让读者加速对实体数据模型的理解,选择从现有数据库表中生成实例模型,如图12-3所示。

选择好从数据库中生成实例模型之后,向导将提示要输入数据库连接信息与选择模型中要包含的数据库对象。在这里,选择数据库“ASPNET4”,并将数据库连接字符串命名为“DataConnect String”,如图12-4所示。

figure_0431_0323

图 12-3 选择是从数据库中生成模型还是从空模型开始构建

figure_0431_0324

图 12-4 设置数据库连接信息与选择模型中要包含的数据库对象

这样,系统将会自动在Web.config文件里生成数据库连接信息。如下面的代码所示:


<connectionStrings>

<add name="DataConnectString"

connectionString="metadata=res://*/Employees.csdl

|res:///Employees.ssdl|res:///Employees.msl;

provider=System.Data.SqlClient;

provider connection string="

Data Source=MAWEI-2EE0C5B1A;Initial Catalog=ASPNET4;

Integrated Security=True;MultipleActiveResultSets=True"

"providerName="System.Data.EntityClient"/>

</connectionStrings>


设置好数据库连接信息与选择模型中要包含的数据库对象之后,单击“Next”按钮,就可以进入数据库对象选择操作了,如图12-5所示。

在图12-5中,可以任意选择要处理的数据库对象,其选择对象可以是数据表、存储过程或者数据库视图。指定了要在模型中包含的数据库对象之后,EDM向导会生成用于定义模型和映射的.edmx文件,并向实体框架需要的项目添加相应的引用,如图12-6所示。

figure_0432_0325

图 12-5 选择数据库对象

figure_0432_0326

图 12-6 Employees.edmx

最后需要注意的是,目前不受实体设计器支持的实体框架功能如下:

1)每种类型多个实体集。

2)为非根类型创建实体集。

3)每个具体类一个表映射。

4)在映射条件中使用EntityType属性。

5)未映射的抽象类型。使用实体设计器创建抽象实体类型时,必须将该类型映射到某个表或视图。

6)对关联映射创建条件。

7)将关联直接映射到存储过程。不支持多对多关联的映射。通过将适当的导航属性映射到存储过程参数,可以将其他关联以及实体类型间接映射到存储过程。

8)对Function Import映射创建条件。

9)批注。

10)查询视图。

11)包含对其他模型的引用的模型。

12)创建没有相应导航属性的关联。

13)添加或编辑存储模型对象(支持删除存储模型对象)。

14)在概念模型中定义的添加、编辑或删除功能。

如果尝试将这些功能与实体设计器结合使用或对.edmx文件进行手动编辑可能会导致出现错误,该错误会阻止实体设计器显示.edmx文件。在这种情况下,会提示使用XML编辑器打开文件。

12.1.2 将概念模型映射到存储模型

上面已经阐述过,.edmx文件包含三个元数据文件:概念架构定义语言((CDL)、存储架构定义语言((SDL)以及映射规范语言((ML)文件。

其中,应用程序的概念模型表示概念架构定义语言((CDL)中的实体和关系,是实体数据模型的实现。CSDL是基于XML的语言的,它定义的每个实体类型都具有一个名称、一个用于唯一标识实例的键和一组属性。分配给属性的数据类型可以指定为简单类型(标量属性)或复杂类型(由一个或多个标量或复杂属性组成的类型)。XML特性还可以指定是否可为null值或分配默认值。

下面的XML代码片段展示了上面定义的Employees概念模型。在该XML中,不仅定义了Employee和Salary实体类型,而且还通过FK_Salary_Employee关联相关的Employee和Salary实体类型。


<!——CSDL content——>

<edmx:ConceptualModels>

<Schema Namespace="EmployeeModel"Alias="Self"

xmlns:annotation="http://schemas.microsoft.com/ado/2009

/02/edm/annotation"

xmlns="http://schemas.microsoft.com/ado/2008/09/edm">

<EntityContainer Name="DataConnectString"

annotation:LazyLoadingEnabled="true">

<EntitySet Name="Employee"

EntityType="EmployeeModel.Employee"/>

<EntitySet Name="Salary"

EntityType="EmployeeModel.Salary"/>

<AssociationSet Name="FK_Salary_Employee"

Association="EmployeeModel.FK_Salary_Employee">

<End Role="Employee"EntitySet="Employee"/>

<End Role="Salary"EntitySet="Salary"/>

</AssociationSet>

</EntityContainer>

<EntityType Name="Employee">

<Key>

<PropertyRef Name="employeeid"/>

</Key>

<Property Name="employeeid"Type="Decimal"

Nullable="false"Precision="18"Scale="0"/>

<Property Name="employeename"Type="String"

Nullable="false"MaxLength="100"Unicode="false"

FixedLength="false"/>

<Property Name="department"Type="String"MaxLength="100"

Unicode="false"FixedLength="false"/>

<Property Name="address"Type="String"MaxLength="200"

Unicode="false"FixedLength="false"/>

<Property Name="email"Type="String"MaxLength="200"

Unicode="false"FixedLength="false"/>

<Property Name="workdate"Type="DateTime"/>

<NavigationProperty Name="Salary"

Relationship="EmployeeModel.FK_Salary_Employee"

FromRole="Employee"ToRole="Salary"/>

</EntityType>

<EntityType Name="Salary">

<Key>

<PropertyRef Name="salaryid"/>

</Key>

<Property Name="salaryid"Type="Decimal"Nullable="false"

Precision="18"Scale="0"/>

<Property Name="employeeid"Type="Decimal"

Nullable="false"Precision="18"Scale="0"/>

<Property Name="total"Type="Decimal"Nullable="false"

Precision="18"Scale="4"/>

<Property Name="salestax"Type="Decimal"

Nullable="false"Precision="18"Scale="4"/>

<Property Name="salary1"Type="Decimal"

Nullable="false"Precision="18"Scale="4"/>

<NavigationProperty Name="Employee"

Relationship="EmployeeModel.FK_Salary_Employee"

FromRole="Salary"ToRole="Employee"/>

</EntityType>

<Association Name="FK_Salary_Employee">

<End Role="Employee"Type="EmployeeModel.Employee"

Multiplicity="1"/>

<End Role="Salary"Type="EmployeeModel.Salary"

Multiplicity="*"/>

<ReferentialConstraint>

<Principal Role="Employee">

<PropertyRef Name="employeeid"/>

</Principal>

<Dependent Role="Salary">

<PropertyRef Name="employeeid"/>

</Dependent>

</ReferentialConstraint>

</Association>

</Schema>

</edmx:ConceptualModels>


定义好概念架构定义语言((CDL)之后,在存储架构定义语言((SDL)中需要做的工作就是声明属性的数据类型为存储模型的数据类型。下面的XML代码片段展示了上面定义的Employees存储模型。


<!——SSDL content——>

<edmx:StorageModels>

<Schema Namespace="EmployeeModel.Store"Alias="Self"

Provider="System.Data.SqlClient"ProviderManifestToken="2005"

xmlns:store="http://schemas.microsoft.com/ado/2007/12

/edm/EntityStoreSchemaGenerator"

xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">

<EntityContainer Name="EmployeeModelStoreContainer">

<EntitySet Name="Employee"

EntityType="EmployeeModel.Store.Employee"

store:Type="Tables"Schema="dbo"/>

<EntitySet Name="Salary"

EntityType="EmployeeModel.Store.Salary"

store:Type="Tables"Schema="dbo"/>

<AssociationSet Name="FK_Salary_Employee"

Association="EmployeeModel.Store.FK_Salary_Employee">

<End Role="Employee"EntitySet="Employee"/>

<End Role="Salary"EntitySet="Salary"/>

</AssociationSet>

</EntityContainer>

<EntityType Name="Employee">

<Key>

<PropertyRef Name="employeeid"/>

</Key>

<Property Name="employeeid"Type="numeric"

Nullable="false"/>

<Property Name="employeename"Type="varchar"

Nullable="false"MaxLength="100"/>

<Property Name="department"Type="varchar"

MaxLength="100"/>

<Property Name="address"Type="varchar"MaxLength="200"/>

<Property Name="email"Type="varchar"MaxLength="200"/>

<Property Name="workdate"Type="datetime"/>

</EntityType>

<EntityType Name="Salary">

<Key>

<PropertyRef Name="salaryid"/>

</Key>

<Property Name="salaryid"Type="numeric"Nullable="false"/>

<Property Name="employeeid"Type="numeric"

Nullable="false"/>

<Property Name="total"Type="numeric"

Nullable="false"Scale="4"/>

<Property Name="salestax"Type="numeric"

Nullable="false"Scale="4"/>

<Property Name="salary"Type="numeric"

Nullable="false"Scale="4"/>

</EntityType>

<Association Name="FK_Salary_Employee">

<End Role="Employee"Type="EmployeeModel.Store.Employee"

Multiplicity="1"/>

<End Role="Salary"Type="EmployeeModel.Store.Salary"

Multiplicity="*"/>

<ReferentialConstraint>

<Principal Role="Employee">

<PropertyRef Name="employeeid"/>

</Principal>

<Dependent Role="Salary">

<PropertyRef Name="employeeid"/>

</Dependent>

</ReferentialConstraint>

</Association>

</Schema>

</edmx:StorageModels>


定义好概念架构定义语言((CDL)与存储架构定义语言((SDL)之后,还需要一种语言将两者关联起来,即根据其需求将概念模型映射到存储模型。而这种映射规范则使用映射规范语言((ML)将概念模型映射到存储模型。下面的XML代码片段展示了Employees模型中的Employee和Salary实体的概念模型与存储模型之间的一对一映射。


<edmx:Mappings>

<Mapping Space="C-S"

xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">

<EntityContainerMapping

StorageEntityContainer="EmployeeModelStoreContainer"

CdmEntityContainer="DataConnectString">

<EntitySetMapping Name="Employee">

<EntityTypeMapping TypeName="EmployeeModel.Employee">

<MappingFragment StoreEntitySet="Employee">

<ScalarProperty Name="employeeid"

ColumnName="employeeid"/>

<ScalarProperty Name="employeename"

ColumnName="employeename"/>

<ScalarProperty Name="department"

ColumnName="department"/>

<ScalarProperty Name="address"ColumnName="address"/>

<ScalarProperty Name="email"ColumnName="email"/>

<ScalarProperty Name="workdate"

ColumnName="workdate"/>

</MappingFragment>

</EntityTypeMapping>

</EntitySetMapping>

<EntitySetMapping Name="Salary">

<EntityTypeMapping TypeName="EmployeeModel.Salary">

<MappingFragment StoreEntitySet="Salary">

<ScalarProperty Name="salaryid"

ColumnName="salaryid"/>

<ScalarProperty Name="employeeid"

ColumnName="employeeid"/>

<ScalarProperty Name="total"ColumnName="total"/>

<ScalarProperty Name="salestax"

ColumnName="salestax"/>

<ScalarProperty Name="salary1"ColumnName="salary"/>

</MappingFragment>

</EntityTypeMapping>

</EntitySetMapping>

</EntityContainerMapping>

</Mapping>

</edmx:Mappings>


12.1.3 使用实体数据

前面已经提到过,实体框架使用概念模型提供以对象为中心的数据视图(以实体类型和关联表示)。作为应用程序开发人员,只需要考虑对从概念模型生成的类进行编程,而不必去考虑存储架构以及如何访问数据存储中的对象并将这些对象转换为编程对象。

也就是说,实体框架将概念模型、存储模型元数据以及这两个模型之间的映射都编译成称为“客户端视图”的双向Entity SQL语句对,而这些视图驱动运行时引擎中的查询和更新处理。当针对概念模型执行第一个查询时,可以在设计时或运行时调用生成视图的映射编译器。

实体框架通过提供到基础数据提供程序和数据源的EntityConnection,建立在特定于存储的ADO.NET数据提供程序的基础之上。在执行查询时,查询将被解析并转换为规范化命令目录树,该规范化命令目录树是查询的对象模型表示形式。规范命令目录树表示选择、更新、插入和删除命令。所有后续处理将在命令目录树上执行,命令目录树是System.Data.EntityClient提供程序和基础.NET Framework数据提供程序(如System.Data.SqlClient)的通信途经。图12-7展示了用于访问数据的实体框架体系结构。

figure_0437_0327

图 12-7 实体框架体系结构(来自MSDN)

ADO. NET实体数据模型工具除了生成上面的概念架构定义语言((CDL)、存储架构定义语言((SDL)以及映射规范语言((ML)文件之外,还生成了一个类,该类派生自表示概念模型中定义的实体容器的ObjectContext。如下面的代码所示:


public partial class DataConnectString:ObjectContext

{

……

}


其中,ObjectContext类支持针对概念模型的查询,这些查询以对象的形式返回实体,该类还支持创建、更新和删除实体对象。实体框架支持针对概念模型的对象查询。可以使用Entity SQL、语言集成查询((LNQ)和对象查询生成器方法来编写查询。如下面的代码所示:


DataConnectString employeeContext=new DataConnectString();

var result=from emp in employeeContext.Employee

where emp.department=="软件研发部"

select emp;


下面的示例展示了一个简单的查询,并将其查询结果绑定到GridView控件上。


protected void Page_Load(object sender, EventArgs e)

{

DataConnectString employeeContext=new DataConnectString();

var result=from emp in employeeContext.Employee

join sal in employeeContext.Salary

on emp.employeeid

equals sal.employeeid

where emp.department=="软件研发部"

select new

{

姓名=emp.employeename,

部门=emp.department,

邮箱=emp.email,

总工资=sal.total,

应交税款=sal.salestax,

实际工资=sal.salary1

};

GridView1.DataSource=result;

GridView1.DataBind();

}


示例运行结果如图12-8所示。

figure_0438_0328

图 12-8 示例运行结果

12.1.4 ADO.NET实体框架的优点

从上面的阐述中可以看出,ADO.NET实体框架使开发人员能够通过对概念应用程序模型编程(而不是直接对关系存储架构编程)来创建数据访问应用程序。目标是降低面向数据的应用程序所需的代码量并减轻维护工作。因此,它具有以下优点:

1)应用程序可以通过更加以应用程序为中心的概念模型(包括具有继承性、复杂成员和关系的类型)来工作。

2)应用程序不再对特定的数据引擎或存储架构具有硬编码依赖性。

3)可以在不更改应用程序代码的情况下更改概念模型与特定于存储的架构之间的映射。

4)开发人员可以使用可映射到各种存储架构(可能在不同的数据库管理系统中实现)的一致的应用程序对象模型。

5)多个概念模型可以映射到同一个存储架构。

6)语言集成查询((LNQ)支持可为针对概念模型的查询提供编译时语法验证。