Entity Framework 全面教程详解 转
2018-11-06 17:46:56 主要是钱不够呀 阅读数 2676
目录
Entity Framework的核心 – EDM(Entity Data Model) 7
为什么要使用Entity Framework,限制条件及当前版本框架的问题 23
本文档主要介绍.NET开发中两项新技术,.NET平台语言中的语言集成查询技术 - LINQ,与ADO.NET中新增的数据访问层设计技术ADO.NET Entity Framework。ADO.NET的LINQ to Entity部分以LINQ为基础,为了完整性本文档首先介绍LINQ技术。
预备知识
LINQ技术
LINQ是.NET 3.5中新增的一种技术,这个技术扩展了.NET平台上的编程语言,使其可以更加方便的进行数据查询,单纯的LINQ技术主要完成对集合对象(如System.Collection下或System.Collection.Generic命名空间下的对象)的查询。
结合LINQ Provider可以实现对XML文件(使用LINQ to XML – 位于System.Xml.Linq命名空间下的类),
数据库(可以使用LINQ to SQL或下文要详细介绍的LINQ to Entity)等对象的操作。
LINQ是一种运行时无关的技术,其运行于CLR2.0之上,微软对C#3.0与VB9.0的编译器进性扩展,从而使其可以将LINQ编写的程序编译为可以被CLR2.0的JIT所理解的MSIL。
LINQ技术的基础 - C#3.0
-
自动属性
-
隐式类型
-
对象集合初始化器
-
匿名类
-
扩展方法
-
Lambda表达式
自动属性
这个概念很简单,其简化了我们在.NET的时候手写一堆私有成员+属性的编程方式,
我们只需要使用如下方式声明一个属性,编译器会自动生成所需的成员变量。
publicclassCustomer{
publicint Id {get; set; }
publicstring Name {get;set; }
}
在我使用LINQ完成的项目中,使我了解到自动属性方便的一个用途如下:
在使用LINQ获取数据的过程中,我们常常需要使用select new语句查询出一个对象(往往是IEnumerable类型的)用于数据绑定。在一般情况下如果是直接绑定(如直接将查询结果赋给一个Gridview控件的DataSource属性)我们可以直接select new来返回一个匿名类的对象。如果我们还需要对这个集合对象进行进一步操作,我们将必须使用select newclass-name这样的语言返回一个类的对象,大部分情况下这个类只作为实体的一个结构而不需要完成一些操作操作,这时候使用自动属性来完成这个类将是非常简洁高效的。
隐式类型
这个名称可能对你很陌生,但是var这个关键字应该都用过,在C#中使用var声明一个对象时,编译器会自动根据其赋值语句推断这个局部变量的类型。赋值以后,这个变量的类型也就确定而不可以再进行更改。另外var关键字也用于匿名类的声明。
应用场合:var主要用途是表示一个LINQ查询的结果。这个结果可能是ObjectQuery<>或IQueryable<>类型的对象,也可能是一个简单的实体类型的对象。这时使用var声明这个对象可以节省很多代码书写上的时间。
对象初始化器与集合初始化器
在.NET2.0中构造一个对象的方法一是提供一个重载的构造函数,二是用默认的构造函数生成一个对象,然后对其属性进行赋值。在.NET3.5/C#3.0中我们有一种更好的方式来进行对象的初始化。那就是使用对象初始化器。这个特性也是匿名类的一个基础,所以放在匿名类之前介绍。
还是那就话,好的代码强于注释,下面用几个代码段说明初始化器:
(代码出自:李永京的博客 http://lyj.cnblogs.com)
基本用法:
User user =newUser { Id = 1, Name ="YJingLee", Age = 22 };
嵌套使用:
User user = new User {
Id = 1,
Name ="YJingLee",
Age = 22,
Address = new Address {
City ="NanJing",
Zip = 21000
}
};
类似于对象初始化器初始化一个对象,集合初始化器初始化一个集合,一句话,有了它你就不用在将元素通过Add逐个添加了。仍然给出代码示例:
基本使用:
List<int> num =newList<int> { 0, 1, 2, 6, 7, 8, 9 };
结合对象初始化器,我们可以写出如下简洁的代码:
List<User> user = new List<User>{
new User {Id=1,Name="YJingLee",Age=22},
new User {Id=2,Name="XieQing",Age=25},
};
应用场合:
还是前文提到的select new class-name语法,后面可以直接接一个初始化器来将查询结果返回到这个对象。
匿名类
有了前文初始化器的介绍,匿名类就很简单了。我们可以使用new {object initializer }或new[]{ object, …}来初始化一个匿名类或不确定类型的数组。匿名类的对象需要使用var关键字声明。示例代码:
var p1 =new { Id = 1, Name ="YJingLee", Age = 22 };
应用场合:
还是同上面的例子提到的当直接使用select new { object initializer }这样的语法就是将一个LINQ查询的结果返回到一个匿名类中。
扩展方法
扩展方法是C#中新增的很重要的特性之一。其对于LINQ的实现起着关键的作用。在.NET2.0时代是没有LINQ的,所以.NET2.0以及之前版本中的集合类在设计的时候没有预留用于LINQ的方法。为了在不破坏这个类现有封装的前提下又可以为其添加LINQ的支持就需要用到扩展方法。
扩展方法使用上类似于静态方法,但在本质上其是实例方法。这是由于.NET3.5的运行环境仍然为CLR2.0所以语言不可能做很大的变革,这一切都是语法糖。
下面仍然通过一段代码来说明扩展方法的实现:
(代码出自:李永京 http://lyj.cnblogs.com)
public static class Extensions {
public static bool IsValidEmailAddress(thisstring s){
Regex regex =newRegex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");
return regex.IsMatch(s);
}
}
如上代码所示,扩展方法为一静态方法,声明于一个静态类,其参数前加上一个this关键字,参数的类型表示这个扩展方法要对这个类型进行扩展。如上述代码表示其要对字符串类型进行扩展。
在应用上扩展方法被作为其扩展的类型的静态方法来调用。如下:
if (email.IsValidEmailAddress())
{
Response.Write("YJingLee提示:这是一个正确的邮件地址");
}
Lambda表达式
Lambda表达式是对.NET2.0中匿名方法在语法形式上的进一步改进,仍然以代码说明:
var inString = list.FindAll(delegate(string s) {return s.Indexof("YJingLee") >= 0; });
使用Lambda表达式代码将更自然易懂。
var inString = list.FindAll(s => s.Indexof("YJingLee") >= 0);
可以看出,Lambda表达式格式为:(参数列表)=>表达式或语句块
另外我对于Lambda表达式树的概念还不是很明白,有明白的指点一下。
.NET中的数据访问
这一部分介绍.NET中不同的数据访问层的使用方式,由此得出Entity Framework在一个.NET系统中的应用及其在原有设计基础上的改变。从大的方面来看数据访问的设计方案基本有如下几类:
-
DataSet
-
手写代码通过ADO.NET2.0连接类与数据库交互
-
ORM组件
DataSet方案
最基本的Dataset数据访问的实现使用下图表示:
图1
如图所示,DataSet与数据源之间通过DataAdapter连接,逻辑中直接访问DataSet获取数据,或是通过ADO.NET2.0的非连接类,或者通过强类型DataSet以一种类型安全的方式访问数据。
缺点逻辑代码与数据访问代码耦合高。
改进的的DataSet方案
图2
这种设计方式将业务所需的实体抽象出来,并把对DataSet的操作封装在其中,这样一定程序上解除业务逻辑与数据访问间的耦合。
手写代码通过ADO.NET2.0连接类与数据库交互
这种方式是我使用的最多的一种方式,其可以提供最大的控制能力,且效率最高,唯一的不足是当业务变化时修改数据访问代码的工作量比较大,通过代码生成器也能一定程度上解决这个问题
ORM – LINQ to SQL
在.NET平台下ORM的解决方案有不少,本文只讨论两个微软官方的解决方案。先是LINQ to SQL技术。LINQ to SQL是一个将不再更新的技术。其有很多不足之处,如,不能灵活的定义对象模型与数据表之间的映射、无法扩展提供程序只能支持SQL Server等。
这样数据访问层的设计如下所示:
图3
ORM – ADO.NET Entity Framework
作为下一代数据访问的技术领导者。Entity Framework的设计很多地方都保留了高扩展性。其最重要的一个改进在于其映射定义的灵活性。先来看下图:
图4
由图可以看出,使用Entity Framework可以充分的定义与数据库表映射的实体,并将这个实体直接用于业务逻辑层或作为服务的数据契约。实体设计较其他技术的优势体现在以下几方面:
-
创建ComplexType(CSDL部分有讨论)
-
EntitySet的继承
使用Entity Framework后,可以将实体类的设计工作完全放在EDM的设计过程中,而不再需要手工写一些大同小异的代码,并且对这个实体模型(包含于EDM中)可以在运行时修改并生效。另外,开发人员与数据库直接打交道的次数将大大减少,大部分时间开发人员只需操作实体模型,框架会自动完成对数据库的操作。下文将详细讨论上图所示的EDM。
深入了解Entity Framework
Entity Framework的核心 – EDM(Entity Data Model)
EDM概述
实体数据模型,简称EDM,由三个概念组成。概念模型由概念架构定义语言文件 (.csdl)来定义,映射由映射规范语言文件 (.msl),存储模型(又称逻辑模型)由存储架构定义语言文件 (.ssdl)来定义。这三者合在一起就是EDM模式。EDM模式在项目中的表现形式就是扩展名为.edmx的文件。这个包含EDM的文件可以使用Visual Studio中的EDM设计器来设计。由于这个文件本质是一个xml文件,可以手工编辑此文件来自定义CSDL、MSL与SSDL这三部分。下面详细分析一下这个xml文件及三个其重要组成部分:
这个文件展示了示例项目完整的EDM文件的XML形式:
这个设计器生成的文件的注释可以使你很清楚的明白这个EDM文件的组成。一点点分析一下,第一行表明这是一个xml文件。
<?xmlversion="1.0"encoding="utf-8"?> |
以下这一行是EDM的根节点,定义了一个表明版本的属性及这个EDM使用的命名空间:
<edmx:EdmxVersion="1.0"xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"> |
接下来由注释可以看到EDM被分为两部分,第一部分是EDM的核心,第二部分用于实体设计器,这一部分不用研究。
第一部分中节点<edmx:Runtime>下定义了以下三部分:
EDM之CSDL
CSDL定义了EDM或者说是整个程序的灵魂部分 – 概念模型。当前流行的软件设计方法通常都是由设计其概念模型起步。说概念模型可能比较抽象一个更容易接受的名字就是实体类。实体类是面向对象设计中一个最根本的组成部分,其体现了现实世界中对象作为一种计算中可以表示的对象设计方法。而EDM的CSDL就是要达到这样一个目的。这个在下文介绍Entity Framework优点时另有说明。
这个文件完全以程序语言的角度来定义模型的概念。即其中定义的实体、主键、属性、关联等都是对应于.NET Framework中的类型。下面xml element来自作业提交系统(有删节):
<!-- CSDL content--> <edmx:ConceptualModels> <SchemaNamespace="ASSModel"Alias="Self"xmlns="http://schemas.microsoft.com/ado/2006/04/edm"> <EntityContainerName="ASSEntities"> <FunctionImportName="GETHOUSEWORKDONE"EntitySet="UpAssignments"ReturnType="Collection(Self.UpAssignments)"> <ParameterName="StuID"Type="Int32"Mode="In" /> <ParameterName="ClassID"Type="Int32"Mode="In" /> <ParameterName="Semester"Type="String"Mode="In" /> </FunctionImport> <!--以上删节 – 5个存储过程--> <EntitySetName="Assignments"EntityType="ASSModel.Assignments" /> <EntitySetName="Classes"EntityType="ASSModel.Classes" /> <EntitySetName="Courses"EntityType="ASSModel.Courses" /> <EntitySetName="SetCourses"EntityType="ASSModel.SetCourses" /> <EntitySetName="Students"EntityType="ASSModel.Students" /> <EntitySetName="Teachers"EntityType="ASSModel.Teachers" /> <EntitySetName="UpAssignments"EntityType="ASSModel.UpAssignments" /> <AssociationSetName="FK_SetCourses_Classes"Association="ASSModel.FK_SetCourses_Classes"> <EndRole="Classes"EntitySet="Classes" /> <EndRole="SetCourses"EntitySet="SetCourses" /> </AssociationSet> <!--以上删节 – 6个关系集--> </EntityContainer> <!--以下保留一个EntityType作为示例--> <EntityTypeName="Students"> <Key> <PropertyRefName="StuID" /> </Key> <PropertyName="StuID"Type="Int32"Nullable="false" /> <PropertyName="StuName"Type="String"Nullable="false"MaxLength="10"Unicode="true"FixedLength="true" /> <PropertyName="Pswd"Type="String"Nullable="false"MaxLength="50"Unicode="false"FixedLength="true" /> <NavigationPropertyName="Classes"Relationship="ASSModel.FK_Students_Classes"FromRole="Students"ToRole="Classes" /> <NavigationPropertyName="UpAssignments"Relationship="ASSModel.FK_UpAssignments_Students"FromRole="Students"ToRole="UpAssignments"/> </EntityType> <!--仅保留与上文AssociationSet对应的Association--> <AssociationName="FK_SetCourses_Classes"> <EndRole="Classes"Type="ASSModel.Classes"Multiplicity="1" /> <EndRole="SetCourses"Type="ASSModel.SetCourses"Multiplicity="*" /> </Association> </Schema> </edmx:ConceptualModels> |
这部分XML文档,Schema是CSDL的根元素,其中定义的Namespace是用于ObjectContext与EntityClass的命名空间,Alias-别名为此命名空间Namespace指定一个易记的名称,在定义Alias之后,在此Schema内的Element均可以该Alias作为Namespace的别名。Alias的使用可以参考如下xml element:
<FunctionImportName="GETHOUSEWORKDONE"EntitySet="UpAssignments"ReturnType="Collection(Self.UpAssignments)"> |
在这个根元素的内部的文档结构第一部分 – 实体容器大致如下:
<EntityContainer/> <FunctionImport /> <EntitySet /> <AssociationSet /> </EntityContainer> |
下面的表格说明了这些节点及其属性的作用
EntityContainer |
|||
Name |
EntityContainer的名称,其将作为产生的ObjectContext类的名称 |
||
EntitySet |
|||
Name |
ObjectContext内与此Entity类型对应的属性名 |
||
EntityType |
ObjectContext内与此Entity类型对应的属性的类型 |
||
AssociationSet |
|||
End |
有两个End子节点,分别描述建立此关系的两个EntitySet |
||
Role |
对应到Association中End节的Role属性,起到将AssociationSet与Association相关连的作用。 |
||
FunctionImport |
详见存储过程设计部分 |
可以看出,Entity与Assciation都被分开定义与两个部分,这样设计是出于当有多个EntityContainer时,其中的EntitySet或AssociationSet可以共享Entity或Association的定义。
接下来看一下CSDL中最后一部分,Entity与Association的定义。
首先是Entity:
<EntityTypeName="Students">
<Key>
<PropertyRefName="StuID" />
</Key>
<PropertyName="StuID"Type="Int32"Nullable="false" />
<PropertyName="StuName"Type="String"Nullable="false"MaxLength="10"Unicode="true"FixedLength="true" />
<PropertyName="Pswd"Type="String"Nullable="false"MaxLength="50"Unicode="false"FixedLength="true" />
<NavigationPropertyName="Classes"Relationship="ASSModel.FK_Students_Classes"FromRole="Students"ToRole="Classes" />
<NavigationPropertyName="UpAssignments"Relationship="ASSModel.FK_UpAssignments_Students"FromRole="Students"ToRole="UpAssignments" />
</EntityType>
下表说明了其属性及其子节点与子节点的属性的含义:
EntityType |
|||
Name |
Entity Class的名称 |
||
Abstract |
是否为抽象类 |
||
BaseType |
父类 |
||
Key |
主键 |
||
Property |
主键之属性 |
||
Name |
属性名 |
||
Property |
属性 |
||
Name |
属性名 |
||
Type |
属性类型 |
||
Nullable |
是否允许null |
||
MaxLength |
属性最大长度 |
||
FixLength |
是否固定长度 |
||
NavigationProperty |
关系属性 |
||
Name |
属性名 |
||
Relationship |
对应的Association |
||
FromRole、ToRole |
区别关系两方的父与子 |
最后Association节,这是真正定义关系的地方。首先看示例:
<!--仅保留与上文AssociationSet对应的Association-->
<AssociationName="FK_SetCourses_Classes">
<EndRole="Classes"Type="ASSModel.Classes"Multiplicity="1" />
<EndRole="SetCourses"Type="ASSModel.SetCourses"Multiplicity="*" />
</Association>
这一节符合以下结构:
<Association> <End /> <ReferentialConstraint> <Principal> <PropertyRef /> </Principal> <Dependent> <PropertyRef /> </Dependent> </ReferentialConstraint> </Association> |
属性及其子元素属性的说明:
Association |
||||
Name |
Association的名称 |
|||
End |
类似于AssociationSet,Association也有两个End节点。 |
|||
Name |
End名称 |
|||
Type |
EntityType的名称 |
|||
Role |
此End的Role,与AssociationSet的End的Role属性相联系 |
|||
Multiplicity |
关联多重性,值为0、1或* |
|||
ReferentialConstraint |
外键条件限制 |
|||
Principal |
主要条件 |
|||
Role |
对应于End中的Role |
|||
PropertyRef |
外键属性 |
|||
Name |
属性名称 |
|||
Dependent |
依存条件 |
|||
Role |
对应于End中的Role |
|||
PropertyRef |
外键属性 |
|||
Name |
属性名 |
另外上面示例未涉及的概念,如下:
视图
在EDM设计器中添加视图基本与添加实体表一致,所生成的xml自行对照。某些环境下可能无法添加视图,原因未知,另外对于没有主键的表目前版本EntityFramework支持不好,在设计器中无法添加,及时通过手工编辑xml的方式强行添加,在使用过程中也会出现问题。
ComplexType(复杂类型)
按MSDN中的例子,先描述如下场景。在一个销售系统中我们需要在一个订单中包含一个描述客户地址的实体,而这个实体又能良好的与存储模型映射起来,由于数据库不支持地址这种类型,所以我们可以将地址的每个字段与数据库相映射。且在概念模型中,及在C#代码可以控制的范围内,地址仍然作为一个独立的类型存在。由于EDM设计器不支持以可视化方式创建Complex Type,我们需要手动编辑CSDL与MSL来完成复杂类型的创建与映射。这部分示例将在介绍MSL后给出。
EDM之SSDL
这个文件中描述了表、列、关系、主键及索引等数据库中存在的概念。
<!-- SSDL content--> <edmx:StorageModels> <SchemaNamespace="ASSModel.Store"Alias="Self"Provider="System.Data.SqlClient"ProviderManifestToken="2008"xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl"> <EntityContainerName="ASSModelStoreContainer"> <EntitySetName="Assignments"EntityType="ASSModel.Store.Assignments"store:Type="Tables"Schema="dbo" /> <!--省略7个EntitySet的定义--> </EntityContainer> <!--以下省略7个EntityType的定义--> <EntityTypeName="Assignments"> <Key> <PropertyRefName="AssID" /> </Key> <PropertyName="AssID"Type="int"Nullable="false"StoreGeneratedPattern="Identity" /> <PropertyName="AssName"Type="nchar"Nullable="false"MaxLength="20" /> <PropertyName="AssDes"Type="nvarchar"MaxLength="500" /> <PropertyName="SCID"Type="int"Nullable="false" /> <PropertyName="Deadline"Type="datetime"Nullable="false" /> <PropertyName="QuesFileName"Type="nvarchar"MaxLength="500" /> <PropertyName="QuesFileUrl"Type="nvarchar"Nullable="false"MaxLength="500" /> </EntityType> <!--保留与CSDL中对应的Function--> <FunctionName="GETHOUSEWORKDONE"Aggregate="false"BuiltIn="false"NiladicFunction="false"IsComposable="false"ParameterTypeSemantics="AllowImplicitConversion"Schema=" |