2024年1月1日发(作者:)
[hystar整理]EntityFramework教程目录预备知识2222233444555677LINQ技术LINQ技术的基础-C#3.0自动属性隐式类型匿名类扩展方法.NET中的数据访问DataSet方案对象初始化器与集合初始化器Lambda表达式改进的的DataSet方案ORM–LINQtoSQL深入了解EntityFrameworkEDM概述771112手写代码通过2.0连接类与数据库交互EntityFramework的核心–EDM(EntityDataModel)EDM之CSDLEDM之SSDLEDM之MSLEDM中存储过程的设计实体数据模型映射方案各种使用方式总结1815161817EDM中ComplexType的设计EntityFramework的原理及使用方式使用技巧及需要注意的问题其它操作EDM的方式EDM中的DML232221几种方法的性能分析及使用选择2123为什么要使用EntityFramework,限制条件及当前版本框架的问题含有Association的EDM的使用23本文档主要介绍.NET开发中两项新技术,.NET平台语言中的语言集成查询技术-LINQ,与中新增的数据访问层设计技术ityFramework。的LINQtoEntity部分以LINQ为基础,为了完整性本文档首先介绍LINQ技术。预备知识LINQ技术LINQ是.NET3.5中新增的一种技术,这个技术扩展了.NET平台上的编程语言,使其可以更加方便的进行数据查询,单纯的LINQ技术主要完成对集合对象(如tion下或c
命名空间下的对象)的查询。结合LINQProvider可以实现对XML文件(使用LINQtoXML–位于命名空间下的类),数据库(可以使用LINQtoSQL或下文要详细介绍的LINQtoEntity)等对象的操作。LINQ是一种运行时无关的技术,其运行于CLR2.0之上,微软对C#3.0与VB9.0的编译器进性扩展,从而使其可以将LINQ编写的程序编译为可以被CLR2.0的JIT所理解的MSIL。LINQ技术的基础-C#3.01.2.3.4.5.6.自动属性隐式类型对象集合初始化器匿名类扩展方法Lambda表达式自动属性这个概念很简单,其简化了我们在.NET的时候手写一堆私有成员+属性的编程方式,我们只需要使用如下方式声明一个属性,编译器会自动生成所需的成员变量。publicclassCustomer{publicintId{get;set;}publicstringName{get;set;}}在我使用LINQ完成的项目中,使我了解到自动属性方便的一个用途如下:在使用LINQ获取数据的过程中,我们常常需要使用selectnew语句查询出一个对象(往往是IEnumerable类型的)用于数据绑定。在一般情况下如果是直接绑定(如直接将查询结果赋给一个Gridview控件的DataSource属性)我们可以直接selectnew来返回一个匿名类的对象。如果我们还需要对这个集合对象进行进一步操作,我们将必须使用selectnewclass-name这样的语言返回一个类的对象,大部分情况下这个类只作为实体的一个结构而不需要完成一些操作操作,这时候使用自动属性来完成这个类将是非常简洁高效的。隐式类型这个名称可能对你很陌生,但是var这个关键字应该都用过,在C#中使用var声明一个对象时,编译器会自动根据其赋值语句推断这个局部变量的类型。赋值以后,这个变量的类型也就确定而不可以再进行更改。另外var关键字也用于匿名类的声明。应用场合:var主要用途是表示一个LINQ查询的结果。这个结果可能是ObjectQuery<>或IQueryable<>类型的对象,也可能是一个简单的实体类型的对象。这时使用var声明这个对象可以节省很多代码书写上的时间。对象初始化器与集合初始化器在.NET2.0中构造一个对象的方法一是提供一个重载的构造函数,二是用默认的构造函数生成一个对象,然后对其属性进行赋值。在.NET3.5/C#3.0中我们有一种更好的方式来进行对象的初始化。那就是使用对象初始化器。这个特性也是匿名类的一个基础,所以放在匿名类之前介绍。还是那就话,好的代码强于注释,下面用几个代码段说明初始化器:(代码出自:李永京的博客)
基本用法:Useruser=newUser{Id=1,Name="YJingLee",Age=22};嵌套使用:Useruser=newUser{Id=1,Name="YJingLee",Age=22,Address=newAddress{City="NanJing",Zip=21000}};类似于对象初始化器初始化一个对象,集合初始化器初始化一个集合,一句话,有了它你就不用在将元素通过Add逐个添加了。仍然给出代码示例:基本使用:List
publicstaticboolIsValidEmailAddress(thisstrings){Regexregex=newRegex(@"^[w-.]+@([w-]+.)+[w-]{2,4}$");h(s);}}如上代码所示,扩展方法为一静态方法,声明于一个静态类,其参数前加上一个this关键字,参数的类型表示这个扩展方法要对这个类型进行扩展。如上述代码表示其要对字符串类型进行扩展。在应用上扩展方法被作为其扩展的类型的静态方法来调用。如下:if(dEmailAddress()){("YJingLee提示:这是一个正确的邮件地址");}Lambda表达式Lambda表达式是对.NET2.0中匿名方法在语法形式上的进一步改进,仍然以代码说明:varinString=l(delegate(strings){f("YJingLee")>=0;});使用Lambda表达式代码将更自然易懂。varinString=l(s=>f("YJingLee")>=0);可以看出,Lambda表达式格式为:(参数列表)=>表达式或语句块另外我对于Lambda表达式树的概念还不是很明白,有明白的指点一下。.NET中的数据访问这一部分介绍.NET中不同的数据访问层的使用方式,由此得出EntityFramework在一个.NET系统中的应用及其在原有设计基础上的改变。从大的方面来看数据访问的设计方案基本有如下几类:•••DataSet手写代码通过2.0连接类与数据库交互ORM组件DataSet方案最基本的Dataset数据访问的实现使用下图表示:
图1如图所示,DataSet与数据源之间通过DataAdapter连接,逻辑中直接访问DataSet获取数据,或是通过2.0的非连接类,或者通过强类型DataSet以一种类型安全的方式访问数据。缺点逻辑代码与数据访问代码耦合高。改进的的DataSet方案
图2这种设计方式将业务所需的实体抽象出来,并把对DataSet的操作封装在其中,这样一定程序上解除业务逻辑与数据访问间的耦合。手写代码通过2.0连接类与数据库交互这种方式是我使用的最多的一种方式,其可以提供最大的控制能力,且效率最高,唯一的不足是当业务变化时修改数据访问代码的工作量比较大,通过代码生成器也能一定程度上解决这个问题ORM–LINQtoSQL在.NET平台下ORM的解决方案有不少,本文只讨论两个微软官方的解决方案。先是LINQtoSQL技术。LINQtoSQL是一个将不再更新的技术。其有很多不足之处,如,不能灵活的定义对象模型与数据表之间的映射、无法扩展提供程序只能支持SQLServer等。这样数据访问层的设计如下所示:
图3ORM–ityFramework作为下一代数据访问的技术领导者。EntityFramework的设计很多地方都保留了高扩展性。其最重要的一个改进在于其映射定义的灵活性。先来看下图:
图4由图可以看出,使用EntityFramework可以充分的定义与数据库表映射的实体,并将这个实体直接用于业务逻辑层或作为服务的数据契约。实体设计较其他技术的优势体现在以下几方面:••创建ComplexType(CSDL部分有讨论)EntitySet的继承使用EntityFramework后,可以将实体类的设计工作完全放在EDM的设计过程中,而不再需要手工写一些大同小异的代码,并且对这个实体模型(包含于EDM中)可以在运行时修改并生效。另外,开发人员与数据库直接打交道的次数将大大减少,大部分时间开发人员只需操作实体模型,框架会自动完成对数据库的操作。下文将详细讨论上图所示的EDM。深入了解EntityFrameworkEntityFramework的核心–EDM(EntityDataModel)EDM概述实体数据模型,简称EDM,由三个概念组成。概念模型由概念架构定义语言文件(.csdl)来定义,映射由映射规范语言文件(.msl),存储模型(又称逻辑模型)由存储架构定义语言文件(.ssdl)来定义。这三者合在一起就是EDM模式。EDM模式在项目中的表现形式就是扩展名为.edmx的文件。这个包含EDM的文件可以使用VisualStudio中的EDM设计器来设计。由于这个文件本质是一个xml文件,可以手工编辑此文件来自定义CSDL、MSL与SSDL这三部分。下面详细分析一下这个xml文件及三个其重要组成部分:这个文件展示了示例项目完整的EDM文件的XML形式:文件这个设计器生成的文件的注释可以使你很清楚的明白这个EDM文件的组成。一点点分析一下,第一行表明这是一个xml文件。
以下这一行是EDM的根节点,定义了一个表明版本的属性及这个EDM使用的命名空间:
EndRole有两个End子节点,分别描述建立此关系的两个EntitySet对应到Association中End节的Role属性,起到将AssociationSet与Association相关连的作用。FunctionImport详见存储过程设计部分可以看出,Entity与Assciation都被分开定义与两个部分,这样设计是出于当有多个EntityContainer时,其中的EntitySet或AssociationSet可以共享Entity或Association的定义。接下来看一下CSDL中最后一部分,Entity与Association的定义。首先是Entity:
TypeNullableMaxLengthFixLength属性类型是否允许null属性最大长度是否固定长度NavigationProperty关系属性NameRelationshipFromRole、ToRole属性名对应的Association区别关系两方的父与子最后Association节,这是真正定义关系的地方。首先看示例:
RolePropertyRefName对应于End中的Role外键属性属性名另外上面示例未涉及的概念,如下:视图在EDM设计器中添加视图基本与添加实体表一致,所生成的xml自行对照。某些环境下可能无法添加视图,原因未知,另外对于没有主键的表目前版本EntityFramework支持不好,在设计器中无法添加,及时通过手工编辑xml的方式强行添加,在使用过程中也会出现问题。ComplexType(复杂类型)按MSDN中的例子,先描述如下场景。在一个销售系统中我们需要在一个订单中包含一个描述客户地址的实体,而这个实体又能良好的与存储模型映射起来,由于数据库不支持地址这种类型,所以我们可以将地址的每个字段与数据库相映射。且在概念模型中,及在C#代码可以控制的范围内,地址仍然作为一个独立的类型存在。由于EDM设计器不支持以可视化方式创建ComplexType,我们需要手动编辑CSDL与MSL来完成复杂类型的创建与映射。这部分示例将在介绍MSL后给出。EDM之SSDL这个文件中描述了表、列、关系、主键及索引等数据库中存在的概念。
EntitySQLFunctionImportMapping用于描述CSDL与SSDL间函数及函数参数的对应(详见下文存储过程部分)说明1:以上表中很重要的一个属性是MappingFragment中的StoreEntitySet属性,就像这个属性的说明中所说,其描述了CSDL的Entity对应到的SSDL的Entity的名称。这是实现下文EDM映射方案中第二条将一个概念模型的实体映射到多个存储模型的实体的关键设置。说明2:Contain这个元素及其属性的作用是,当多个概念模型实体映射到一个存储模型实体时,该元素的属性决定了在什么情况下一个概念模型实体映射到指定的存储模型实体。说明3:QueryView元素定义概念模型中的实体与存储模型中的实体之间的只读映射。使用根据存储模型计算的EntitySQL查询定义此查询视图映射,并以概念模型中的实体表达结果集。同DefiningQuery定义的查询。此映射也是只读的。就是说如果想要更新此类EntitySet,也需要使用下文介绍存储过程时提到的定义更新实体的存储过程的方法,使用定义的存储过程来更新这样的EntitySet。当多对多关联在存储模型中所映射到的实体表示关系架构中的链接表时,必须为此链接表在AssociationSetMapping元素中定义一个QueryView元素。定义查询视图时,不能在AssociactionSetMapping元素上指定StorageSetName属性。定义查询视图时,AssociationSetMapping元素不能同时包含EndProperty映射。EDM中存储过程的设计目前版本(VS2008SP1)的实体设计器对存储过程支持不完善,只能手工编辑这三个文件中的存储过程部分,包括:中的FunctionImport元素,其属性及说明如下所示:FunctionImportNameEntitySetReturnTypeParameterNameTypeModeMaxLengthPrecisionScale(在程序中调用的)函数的名称当函数返回实体对象时,需使用此属性指定对应的EntitySet函数返回值的类型以下用于参数子节点中的属性定义参数的名称参数的类型参数的传递方式(In,Out或InOut)参数值最大长度参数值的精确度,用于数字类型浮点数小数位中的Function节FunctionNameAggregateBuiltInNiladicFunctionIsComposableParameterTypeSemanticsReturnType存储过程名称是否为聚合函数(存储过程)是否为内建存储过程是否为无参数存储过程True为自定义函数,False为存储过程参数类型转换方式存储过程返回类型中的FunctionImportMapping节FunctionImportMapping
中FunctionImport的名称SSDL中FunctionElement的名称这面总结的是用于返回实体对象的查询(存储过程)。使用插入、更新或删除实体数据的存储过程,需要修改如下两个文件:下面分别描述一下有关修改操作的存储过程的使用:,对其的修改要求与上文表中列出的一致:MSL,需要对一下节点进行定义:EntityContainerMappingEntitySetMappingEntityTypeMappingEntityContainer中每个EntitySet的对应描述CSDL中EntityType与SSDL中EntityType的对应ModificationFunctionMappingCUD对应的存储过程InsertFunction/UpdateFunction/DeleteFunctionFunctionName1.使用创建或删除在数据源中使用链接表实现的实体类型之间的多对多关系的存储过程需要修改如下两个文件:SSDL,对其的修改要求与上文表中列出的一致:MSL,需要对一下节点进行定义:EntityContainerMappingAssociationSetMapping描述CSDL中的AssociationSet与SSDL中的EntitySet的对应关系ModificationFunctionMappingC/D对应的存储过程InsertFunction/DeleteFunctionFunctionNameEDM中ComplexType的设计再谈ComplexType,上文大致介绍了复杂类型的概念及作用,现在开始看一下具体怎样实现。前文已经提到实现复杂类型关键是在CSDL与MSL,而与SSDL无关。首先应该在CSDL中怎加这样一节,此节与
复杂类型在上文有单独介绍在此方案中,存储模型中的存储过程映射到概念模型中的FunctionImport元素。10函数导入映射执行此函数可使用映射的存储过程返回实体数据。见上文存储过程部分在此方案中,在存储模型中定义用于插入、更新和删除数据的存储过程。这些函11修改函数映射数是为实体类型定义的,以便为特定实体类型提供更新功能。见上文存储过程部分在此方案中,在存储模型中定义表示数据源中的表的查询。在映射到SQLServer数据库时,查询以数据源的本机查询语言(如Transact-SQL)表示。12定义查询映射此DefiningQuery元素映射到概念模型中的实体类型。查询以特定于存储的查询语言进行定义。上文EDM之SSDL部分最后详细介绍了这种设计的相关问题在此方案中,会在概念模型中的实体类型与存储模型中的关系表之间定义只读映13查询视图映射射。此映射基于对存储模型进行的EntitySQL查询定义。上文EDM之MSL中说明三对这种设计的相关问题有介绍。关联定义实体之间的关系。在具有一对一或一对多关联的简单映射中,在概念模型中定义关系的关联会映射到存储模型中的关联。还支持以下更高级的关联集映射:14AssociationSet映射多对多关联。关联的两端都映射到存储模型中的链接表。自关联。此映射支持具有相同类型的两个实体之间的关联,如一个Employee与另一个Employee之间的关联。派生类型之间的关联。此映射支持一个层次结构中的派生类型与另一个层次结构中的派生类型之间的关联。EntityFramework的原理及使用方式ityFramework操作数据库的过程对用户是透明的(当然我们可以通过一些工具或方法了解发送到数据库的SQL语句等)。我们唯一能做的是操作EDM,EDM会将这个操作请求发往数据库。EntityFramework实现了一套类似于2.0中连接类(它们使用方式相同,均基于Provider模式)的被称作EntityClient的类用来操作EDM。2.0的连接类是向数据库发送SQL命令操作表或视图,而EntityClient是向EDM发送EntitySQL操作Entity。EntityClient在EntityFramework中的作用是相当重要的,所有发往EDM的操作都是经过EntityClient,包括使用LINQtoEntity进行的操作。各种使用方式总结上文提到对EDM的操作,首先通过一个图来展现一下目前我们可用的操作的EDM的方式:
这几种访问方式使用介绍如下:(部分示例代码来源MSDNMagzine)Client+EntitySQL示例代码:stringcity="London";using(EntityConnectioncn=newEntityConnection("Name=Entities")){();EntityCommandcmd=Command();dText=@"=@city";hValue("city",city);DbDataReaderrdr=eReader(tialAccess);while(())
ine(rdr["CompanyName"].ToString());();}Service+EntitySQL在有EntityClient+EntitySQL这种使用方式下,使用ObjectService+EntitySQL的方式是多此一举,不会得到任何编辑时或运行时的好处。在ObjectContext下使用EntitySQL的真正作用是将其与LINQtoEntity结合使用。具体可见下文所示。示例代码:stringcity="London";using(Entitiesentities=newEntities()){ObjectQuery
这两段示例代码中的er的写法隐式调用了2中示例的ObjectQuery
图中所示表达的意思已经非常清楚,稍加解释的是,无论是通过EntityClient直接提供给EntityClientDataProvider的EntitySQL还是通过ObjectService传递的EntitySQL(或是LINQtoEntity),都在EntityClientDataProvider中被解释为相应的CommandTree,并进一步解释为对应数据库的SQL。这样来看使用LINQtoEntity与EntitySQL的效率应该差不多,但是还有一个问题,那就是EntitySQL所转换的最终SQL可能要比LINQtoEntity生成的SQL效率高,这在一定程度上使两者效率差增大,但是LINQtoEntity有其它技术无法比拟的好处,那就是它的强类型特性,编辑时智能感知提醒,编译时发现错误,这都是在一个大型项目中所需要的。虽然现在也有了调试EntitySQL的工具,但其与强类型的LINQtoEntity还是有很大差距。另外在ObjectService与直接使用EntityClient问题的选择上。如果你想更灵活的控制查询过程,或者进行临时查询建议选择EntityCLient,如果是操作数据那只能采用ObjectService。上文总结了各种操作EDM的方式,下面引用MSDN的一个对这几种技术进行比较的表格:EntityClient和实体SQL对象服务和实体SQL对象服务和LINQ定向到EntityClient提供程序是适合临时查询可直接发出DML强类型化可将实体作为结果返回是否否否否是否否是否否否是是通过这个表可以很好对某一场合下应该选择的技术进行判断。EntityClient和实体SQL可以进行最大的控制,而使用LINQtoEntity可以获得最佳的编辑时支持。其它操作EDM的方式通过EdmGen更灵活的控制EDM在.NETFramework3.5的文件夹下有一个名为EdmGen的工具,VisualStudio的实体设计器就是调用这个工具来完成EDM的生成等操作。通过直接使用这个工具的命令行选项我们可以进行更多的控制。这个命令的参数及作用如下:EdmGen选项/mode:EntityClassGeneration从csdl文件生成对象/mode:FromSsdlGeneration从ssdl文件生成msl、csdl和对象
/mode:ValidateArtifacts验证ssdl、msl和csdl文件/mode:ViewGeneration从ssdl、msl和csdl文件生成映射视图/mode:FullGeneration从数据库生成ssdl、msl、csdl和对象/project:<字符串>用于所有项目文件的基名称(短格式:/p)/provider:<字符串>用于ssdl生成的数据提供程序的名称。(短格式:/prov)/connectionstring:<连接字符串>您要连接到的数据库的连接字符串(短格式:/c)/incsdl:<文件>从中读取概念模型的文件/refcsdl:<文件>包含/incsdl文件所依赖的类型的csdl文件/inmsl:<文件>从中读取映射的文件/inssdl:<文件>从中读取存储模型的文件/outcsdl:<文件>将生成的概念模型写入到其中的文件/outmsl:<文件>将生成的映射写入到其中的文件/outssdl:<文件>将生成的存储模型写入到其中的文件/outobjectlayer:<文件>将生成的对象层写入到其中的文件/outviews:<文件>将预生成的视图对象写入到其中的文件/language:CSharp使用C#语言生成代码/language:VB使用VB语言生成代码/namespace:<字符串>用于概念模型类型的命名空间名称/entitycontainer:<字符串>用于概念模型中的EntityContainer的名称/help显示用法信息(短格式:/?)/nologo取消显示版权消息使用示例:从Northwind示例数据库生成完整EntityModel。EdmGen/mode:FullGeneration/project:Northwind/provider:ent/connectionstring:"server=.sqlexpress;integratedsecurity=true;database=northwind"从ssdl文件开始生成EntityModel。EdmGen/mode:FromSSDLGeneration/inssdl:/project:Northwind验证EntityModel。EdmGen/mode:ValidateArtifacts/inssdl:/inmsl:/incsdl:为什么要使用EntityFramework,限制条件及当前版本框架的问题•优势通过对比上面图4与图2、图3我们可以很清楚的看到使用EntityFramework一个很大的好处,我们可以把实体类的定义由一个单独的项目使用C#class完成这样一种设计方式转变为使用xml文件定义并集成到数据访问层。在以往要在一个项目中动态创建实体,我所知的方法是把要添加的实体放入一个程序集,然后通过反射加载程序集。现在可以通过动态更改EDM的方法来增加实体并将其映射到数据库,后者是以前无法实现的。便于更改数据库,当更换数据库后,只需修改SSDL的定义,(如果数据库的表明有变动,也只需多修改MSL),对CSDL没有任何影响,从而也不需要对程序的BLL等上层部分做任何改动。•条件
要想让一个数据库支持EntityFramework,一个必要条件就是该数据库需提供相应的EntityClientDataProvider,这样才能将EntitySQL转换为针对此数据此数据库的SQL并交由来执行。当然该数据库还需要提供aProvider。•缺陷EntityFramework技术的效率问题是其几乎唯一一个稍有不足之处。首先其将EntitySQL转换为SQL的方式属于解释性转换,性能较差。另外EntityFramework在每次应用启动时需要读取EDM,这个过程较慢(但在后续操作时,就不再存在这个问题)。EDM中的DML由于当前的EntitySQL不支持DML操作,所以当前版本的EntityFramework的插入、更新及删除操作需要通过ObjectService来完成。在EDM的设计器文件中自动生成了一些签名为voidAddToEntity(EntityTypeentity)的方法。我们只需要新建一个实体对象并调用这个方法添加实体即可。注意这个函数内部调用ect("EntitySetName",entity);最后调用anges()方法将修改保存回数据库,这是所有三种更新操作所需的。更新与删除操作都需要先使用ObjectService定位操作的实体对象,更新操作直接使用赋值运算符,删除操作则调用Object(objecto);方法。之后调用anges()方法保存,这个过程简单,不再赘述。含有Association的EDM的使用当前版本的EntityFramework不支持自动延迟加载,所有当前未使用的关系中的相关实体默认按不加载处理,当我们需要通过关系获取一个实体对象时,我们可以采用两种方法:1.显示加载实体框架针对EntityReference类的每个实例提供一个Load方法。此方法可用于显式加载与另一实体相关的一个集合。我们只需在访问关系中实体之前调用其Load即可,当然提前判断该实体是否已经加载是一种比较好的实践。如下代码所示using(Entitiesentities=newEntities()){varquery=(erID=="ALFKI"selecto);foreach(Ordersorderinquery){if(!ed)();ine(D+"---"+yName);}}1.预先加载先看代码示例using(Entitiesentities=newEntities()){
varquery=(e("Customers")untry=="USA"selecto);foreach(Ordersorderinquery)ine(D+"---"+yName);}查询中针对Orders实体调用的Include方法接受了一个参数,该参数在本示例中将要求查询不仅要检索Orders,而且还要检索相关的Customers。这将生成单个SQL语句,它会加载满足LINQ查询条件的所有Order和Customer。两种加载关系实体的方式的选择根据:如果针对关系数据你只需做一到两次查询,则使用显示加载更高效,如果要持续访问关系实体中数据,则使用预先加载。关系下的添加,更新与删除与上述操作基本相同,唯一需要注意的是删除操作不支持级联删除,需要手工遍历所有的相关项并将其一一删除。注意这里删除操作不能使用foreach来遍历需要删除的关系实体。取而代之的有两种方法:法while(_>0){//删除操作…}法(以非联机方式操作)varitems=_();foreach(variteminitems){//删除操作…}最新补充EntityFramework在开发中的应用–EntityFramework与控件.NETFramework提供了许多xxxDataSource控件,如SqlDataSource,ObjectDataSource等,这些数据源控件大大方便了我们的数据绑定操作。不幸的是目前还没有针对EntityFramework的数据源控件发布,但是将数据绑定到诸如ListBox,Grrdview或DetailsView控件也是很简单的。这源于使用ObjectContext操作返回的IQueryable


发布评论