Entity Framework相关介绍

2016-12-30 09:55:54来源:oschina作者:深圳大道人点击

在深入学习某项技术之前,应该努力形成对此技术的总体印象,并了解其基本原理,本文的目的就在于此。


一、理解EF数据模型

EF本质上是一个ORM框架,它需要把对象映射到底层数据库中的表,为此,它使用了三个模型来描述这种映射关系。


(1)概念模型(Conceptual Model):主要体现为一组可以被应用程序直接使用的类。这些类也是我们在程序中直接使用的类,通常称之为“实体(Entity)”


(2)存储模型(Storage Model):主要体现为一组与底层数据存储介质(比如数据库系统)直接对应的类。


(3)概念-存储模型映射(Conceptual- Storage Mapping),解决“概念模型”中的类如何与“存储模型”中的类相互对应的问题。


(2)和(3)中的类型由EF内部使用,在实际开发中通常触及不到。


所有这三种模型都集中放在名为edmx文件中,以XML方式表达。


VisualStudio提供了一个向导,完成从现有数据库到EF数据模型间的映射转换工作:





当此向导完成之后,EF成功地在数据库与程序中使用的对象之间建立了以下对应关系:




关系数据库的世界


数据库应用程序的世界




数据库


DbContext类





DbContext中的DbSet<实体类名>




表间的关联


实体类之间的关联




表中的字段


实体类的公有属性




表中的单条记录


单个实体类的对象




视图


DbContext中的DbSet<视图名称>




存储过程


DbContext中的公有方法




可以在Visual Studio 的模型浏览面板中看到EF数据模型的概念模型和存储模型,如果直接以xml格式打开.edmx文件,可以看到“原汁原味”的全部三大数据模型。





Visual Studio提供的EF向导不仅生成了上述三大数据模型,还使用T4代码模板(其文件扩展名为.tt)直接生成了相应实体类,还有一个派生自DbContext的子类,在其中包容了所有实体集合属性和导入的存储过程等数据库元素。这一DbContext子类是我们使用EF开发程序的核心类型。


在解决方案资源管理器中双击“edmx”文件将打开EF设计器,这是一个很强悍的工具,几乎所有的调整数据映射关系的工作都可以使用它来完成。


如果基于Database First或Model First方式开发,那么EF设计器是天天要打交道的东西。其操作方式很简单:右击设计器,从弹出菜单中选择相应命令。


EF设计器提供的各种命令和具体使用方法有很多资料介绍,在此就不废话了。


二、使用EF访问数据库的基本方式

EF中可以使用以下四种方式访问数据库:




Entity SQL是专门为EF设计的一种查询语言,非常类似于通用的关系型数据库查询语言SQL,但它返回的数据都是在EF“概念模型”中所定义的,而非数据库中数据的“真实模样”。


LINQ to Entities可以看成是LINQ to Objects的一个“变种”,通过LINQ来查询EF数据模型。它在底层使用“对象服务(Object services)”来完成其功能。对象服务是一组用于查询实体数据模型的类,它可以将这些查询结果转换为强类型的CLR对象。


不管是使用Entity SQL还是LINQ to Entities,最终都是依赖“Entity Client”来完成其工作的。


Entity Client包容一组类,比如EntityConnection、EntityDataReader等,与ADO.NET对象模型非常类似,其功能也类似。Entity Client会将对数据的CRUD请求转发给ADO.NET数据提供者(ADO.NET Provider)组件,由其将相关SQL命令直接地发送给数据库。


在实际开发中,大家都使用LINQ to Entities和针对IEnumerable/IQueryable的一组扩展方法完成数据查询工作,几乎不会有人直接使用Entity SQL和Entity Client。


下图展示了数应用程序运行过程中EF查询的内部处理流程:





可以看到,使用EF的数据查询从发出到真正执行要经过两次“命令树(Command Tree)转换”,而从数据库中读取的数据,也要经历从DbDataReader-->EntityDataReader-->IEnmerable的转换。虽然EF采用了缓存、预编译等手段提升性能,但与ADO.NET相比,由于中间处理环节更多,整个查询处理流程中参与的对象也更多,因此总体性能一般比不上ADO.NET,并且会占用更多的内存,这也我们是在享用EF带来的方便的同时,所需要付出的代价。


另外,由于所有查询最终还是要转换为SQL命令,因此,有些LINQ to Objects可用的扩展方法,比如Last(),在EF中将不能用,因为EF不知道如何把它们翻译成底层数据库支持的SQL命令。


三、三种开发模式的PK

EF支持三种开发模式:


Code First、Database First和Model First。


到底应该用那一种模式是令人纠结的问题。


方式一:Code First

对于初次接触的人,EF的Code First实在很有点魔幻色彩。下面就让我们来体会一下。


创建两个类:Book(书)和BookReview(书评)。一本书可以有多条书评,因此,它们是一对多的关系:


public class Book


{


public virtual int Id {get;set;}


public virtual string Name { get; set; }


public virtual List Reviews { get; set; }


}


public class BookReview


{


public int Id{get;set;}


public int BookId { get; set; }


public virtual string Content { get; set; }


public virtual Book AssoicationWithBook { get; set; }


}


好了,现在创建一个派生自DbContext的子类:


public class BookDb : DbContext


{


public DbSet Books { get; set; }


public DbSet Reviews { get; set; }


}


现在可以在程序中随意写几行代码从数据库中提取数据:


static void Main(string[] args)


{


using (var context = new BookDb())


{


Console.WriteLine("数据库中有{0}本书",context.Books.Count());


}


}


运行一下,如果计算机上安装有SqlExpress,那么或者是在应用程序文件夹,或者是打开SQL Server Management Studio(SSME)查看本机SQLServer,你就会发现,数据库己经创建好,其中的表及表的关联也帮助你完成了:





貌似我什么也没干,一切就OK了,神奇啊!


现在修改Book类,给它添加一个Authors属性,代表书的作者:


public class Book


{


public virtual int Id {get;set;}


public virtual string Name { get; set; }


public virtual string Authors { get; set; }


public virtual List Reviews { get; set; }


}



有了前面良好的第一印象,你一定以为只要再次运行程序,底层数据库就会自动更新,然而,EF会给你当头一棒让你清醒:





很明显,因为你修改了实体类,数据库结构也需要修改,比较郁闷的是,你不能打开创建好的数据库直接修改,而需要使用一个名为“数据库迁移(Database Migration)”的功能,采用两种方式(自动迁移和手动迁移)之一完成。


以自动迁移为例:


首先从从Tools菜单中打开Package Manager Console,然后键入:


enable-migrations –EnableAutomaticMigrations


上述命令会在项目中添加一个Migrations文件夹,其中会有一个Configuration类,为了方便,你需要在其构造函数中添加“AutomaticMigrationDataLossAllowed = true;”一句,让其自动重建数据库时不理会可能的数据丢失:



internal sealed class Configuration :DbMigrationsConfiguration


{


public Configuration()


{


AutomaticMigrationsEnabled = true;


AutomaticMigrationDataLossAllowed = true;


}


……


}



好了,现在运行update-database命令更新数据库:





再次运行程序,现在将一切OK。


以后每次更改实体类,都必须手动运行update-database命令更新数据库。


手动迁移方式与自动迁移基本一致,不同之处在于它会记录每次更新的情况,从而允许回滚数据库到某个“较老”的版本。


自动方式比较适合于单个人写的应用。而手动方式可以控制数据库结构的更新,比较适合于团队开发。


Code First模式的优点。

从上面的介绍,可以看到Code First有着突出的优点。


(1)是代码清洁,添加自定义逻辑容易。


这是使用Code First最大的好处


特别是在诸如WPF这种可以保持长连接的桌面应用中,可以让实体类实现INotifyProperty接口,将它们放入ObservableCollection中作为UI界面的绑定数据源,便可以充分利用WPF的数据绑定和EF自动维持实体状态的特点,大幅度地削减代码。


(2)易于实现继承


Code First在实现继承上非常方便,如果数据实体中有大量的继承的情况,使用Code First很方便,但需要注意的是Code First在生成数据库表时,默认使用“TPH:Table Per Hierarchy”方式,把父类子类塞到同一张表中,并在表中添加一个Discriminator字段,表明此记录所属的具体类型。


以下是CodeFirst为拥有继承关系的两个Parent/Child类生成的数据库表,可以看到,Discriminator字段保存了具体的数据类型。




有些朋友问能否把Discriminator字段名给改掉,很遗憾,我没发现可以修改它的方法。


很明显,Code First采用的这种实现策略违反了关系数据库设计范式,对大项目来说,这不易于维护数据的一致性。


实现继承的另外一种方式是TPT(table pertype ),不管子类父类,每个类型一张表。


比如有三个类,Instruct和Student派生自Person,若采用TPT策略,EF将生成三个表,并创建以下的关联:





对于TPH,手写SQL代码很容易,性能高,但对于TPT,EF在生成SQL命令时会产生许多Inner Joint,性能低,另外,对于这种方式存储的数据,手写SQL代码比较麻烦。


Database First和Model First默认情况下都是采用TPH的。


我的建议:除非两实体间确实是IS_A关系,并且在中间层需要使用多态,在“数据存取层(DAL)”尽量少用继承,别自找麻烦。


(3)一些开发高手们还为EF提供了一个EntityFramework Power Tools,这一工具增强了Code First的不少功能,比如它可以从现有数据库直接逆向生成实体类代码,之后就可以修改这些代码,为Code First方式进行开发省去了不少编码工作。同时,它还能为编写的实体类代码生成只读的数据模型视图,以图形的方式展示出实体类间的关联。


Code First存在的问题

CodeFirst试图“用代码搞掂一切”,其问题在于以下几点:


(1)当Model改变时,往往需要编写代码实现数据库的更改,远不如直接使用数据库所提供的工具修改数据库安全和直观,至少丢失数据的可能性小了很多。


(2)当数据实体间有复杂的关联时,需要使用Fluent API手动编写不少代码定义类之间的关联,这实在麻烦。


(3)对数据库表和关联属性的一些微调(比如改改字段名字,修改字段长度限制等),使用数据库设计工具能轻易实现,但Code First只能通过代码来完成,而且必须使用EF的数据迁移特性,这实在麻烦,整个过程还容易出错。


简而言之,Code First应用的场景是:快速开发,迅速迭代。


方式二:Database First

这是EF从1.0开始就支持的特性,其思路是:先设计并建好数据库,然后使用Visual Studio的向导创建EF数据模型并生成实体类代码。


这是最成熟稳定的方式,其设计器相当地完善,基本上能满足实际开发中的各种需求。


我个人认为这是开发正式项目最合适的方式。


当然,DatabaseFirst也有一些问题,主要是需要定制时会有些麻烦。比如:


(1)要想给实体类或生成的DbContext子类添加一些自定义的逻辑,需应用分部类,因为每次更新数据模型,这些代码都会被设计器覆盖并重写。


(2)生成的实体类中不包括任何Data Annotation(所谓“Data Annotation”就是附在实体类代码上的诸如[Required]之类的东东),因此它需要被转换为另一个类,才能方便地在诸如ASP.NET MVC之类的项目中使用(比如ASP.NET MVC项目中的视图模型(ViewModel)类往往需要有Data Annotation,以配合jQueryValidation插件生成网页上的数据验证代码)。


(3)默认情况下实体类与edmx文件放在同一个项目中,想将实体类分离到独立的项目,需要完成一些额外的配置工作(主要是把T4模板文件移到另一个项目,这需要适当地修改T4模板文件中的代码以保证文件路径引用正确)。


方式三:Model First

这种模式是先在可视化设计器中创建实体和它们间的关联,然后设计器生成SQL命令并保存于一个SQL文件中,通过执行这一SQL文件完成数据库的创建和修改工作。


我个人感觉:


这种方式最适合于全新开发的项目,从系统分析开始,逐步分析建立和完善领域模型,之后可以立即创建数据库,如果模型有修改,重新生成一个SQL文件,再执行一次即可,非常适合于新项目OOAD阶段的需要。


当进入OOP阶段时,由于数据库己经存在,就可以很方便地转用Database First方式,整个过程流畅自然。


三种模式PK结果:

Code First:对于小的或用于试验的项目,特别是像我经常要讲课的,教学实例使用Code First开发就比较合适,当程序运行时数据库自动生成,比较省事。


DB First:最为成熟,是正规项目的首选方式,因为是由开发者自己(而不是通过一堆“不太可靠”的代码)直接操作数据库,整个过程高度可控,能很好地保证数据安全,贯彻了“数据比代码重要”的理念。


Model First:当开始一个全新的项目,既没有DB,也没有代码时则非常好,是OOAD的好工具,需要时甚至可以直接生成创建各种不同类型数据库(比如MySQL)的SQL代码!


==============================================================


本篇文章介绍了一些EF相关的通用话题,下一篇文章将讨论一下EF实现CRUD的内部原理。

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台