译 - 第 1 章:EF入门
章节信息
Entity Framework 6 Recipes 第二版第一章:
Chapter 1: Getting Started with Entity Framework
-------------------------------------------------------------------------
阅读说明:
1 术语第一次出现时用中文(原文)表示,如EntityType将表示成实体类型(EntityType)
2 菜单名用粗体表示,如File将表示成文件
3 右击,即鼠标右键点击
第 1 章:EF入门
使用关系型数据库是根据表及其行列去考虑问题的。表是高度结构化并且擅长基于集合的处理。在面向对象编程思想广泛应用前,我们都是过程式思考问题并且通过编写结构化、自顶向下、一个接一个函数的方式解决这些问题。它们的世界都是排好的:表、行和列在我们的代码中紧密地匹配结构化和过程化模式。在相当长的时间内,这种方式工作的非常好……
在代码的世界里已经发生了相当大的变革。现在我们从对象和领域模型方面考虑问题。我们对现在世界的事物如客户和订单做架构、设计和编程。我们在白板上描绘我们问题集中的词语。我们在它们之间连线、描述关系和交互。我们根据这些草图制订规范和指派任务给开发团队。总之,我们在一个概念水平上做架构、设计和编码,这个概念水平离数据库的逻辑和物理组织相差是比较大的。
当软件开发方法已经相当成熟并且我们推理和解决问题的方式也已经进化时,相对之下,数据库变化不大。数据仍旧停留在表、行和列的范式中,这些东西许多年前就已经面世了。不幸的是,这种情况产生了一种失配(一位微软同事Anders Hejlsberg可能称之为阻抗失配):面向对象的类继承与高度规范化的数据库结构之间的失配。
为了解决这个困难,软件工程引进一个叫“数据库层”的概念,用以翻译程序领域类到表的行和列中。这个方法催生了许多商业和开源的数据访问框架。所有的一切都意图填补这个日益加宽的发展中的开发进程和结构化数据之间的鸿沟。有趣的是,这也诞生了一个新的领域,叫对象关系映射(Object Relational Mapping, ORM)。
EF加上LINQ框架,从源头上解决了这种失配。使用EF,为程序在设计表面可直接编码对实体类建模。接着,对实体类之间的关系(关联)建模。在代码中对实体类及其关联编写LINQ查询。当按照实体类型和关联编码时,LINQ允许在代码中直接表示关系数据库集合的概念。所有的一切帮助统一开发体验同时减少总体的工作量。相对于编写大量高度冗余的ADO.NET数据访问构造,我们通过简单的LINQ查询来表示自己的数据需求。相对于对一个高度规范化的数据库架构编程,我们对实体类进行编码。EF为我们映射了实体类到底层数据库上。
注意 术语实体类(entity class)或实体对象(entity object)是指程序中通常代表一个领域项目(domain item)的类。领域类(domain class)表示现实世界中的对象,如员工、部门或经理等程序会表示和跟踪的对象。最终用户及项目关系人看到程序中的领域类会说:对,我们的业务就是这么干的。实体类定义领域类的构架或属性,而不是行为。本质上,实体类公开(expose)了对象的状态。
1 - 1、EF世界的简要参观
实体框架EF是微软为构建软件程序准备的一个数据访问技术战略方法。不像早期的数据访问技术,EF与VS一起,致力于构建一个综合的、基于模型的生态系统,此系统广泛地支持面向数据的程序,包括桌面端、互联网端、云端和基于服务的程序,本书会涉及其中一些程序。
历史
EF并不是一个新技术,它可追溯回VS 2008时代,并且其在特性及功能上已经取得很大进步。图1-1展示了一个图片历史。
EF的第1版功能相当少,基本的ORM支持特性和单一的称作数据库先行的实现,本书将充分展示这种方法。EF第4版带来的另一种使用EF的方式:模型先行,同时完全支持POCO(Plain Old CLR Object,普通CLR对象)和默认迟缓加载行为。在此之后,很快EF团队发布了三个改良版本,从4.1到4.3,也带来了第三种使用EF的方式:代码先行。如上所示,EF 5配合.NET 4.5和VS 2012的发布一起,在对枚举、表的值函数、几何类型、存储过程的批量导入以及深度支持ASP.NET MVC框架情况下,提供了显著的性能改进。
如今,EF 6也发布了。EF 6提供了查询和更新的异步支持,代码先行方式下可使用存储过程进行更新,提高性能,还有一系列新功能,这些都是本书的重点。
注意 EF 5可在VS 2010中使用。EF 6与VS 2013一起发布,同时对VS 2012提供了工具及运行时支持,为VS 2010中提供了运行时支持。
为了有一个直观的感受,下面简要回顾一下EF生态系统的关键成分。下面决不是EF的全面描述,那会花上几百页才能完成。仅关注几个关键部分以便理解本书的核心秘诀。
模型
EF是一项相当关注建模的技术。当用EF建模时,可发现许多老技术及模式的影子。例如,实体关系图和广泛应用的概念、逻辑和物理设计层方法。
在EF创建的模型的特点是一个叫实体数据模型(Entity Data Model, EDM)的构件,这个构件允许你对强类型的实体类进行编码,而不是对数据库架构及对象编码。(图1-2在概念上展示了这个模型)。实体数据模型支持自定义实体类与数据库表之间的映射,超越了经典的、一对一映射或对到表的映射。
在图1-2中,注意左边的表并不直接映射到我们直接对其编码的实体类(在右边)上。相反,实体数据模型内建的映射能力让开发者对一系列的实体类编码,这些实体类更接近问题领域(译注:原文为problem domain),而不是对高度规范化的数据库编码,这种数据库为性能、可扩展性和可维护性而设计。
例如,注意看上图的员工、设备和电话号码是如何物理存储到三个不同的表的,这从数据库管理员的角度看是非常好理解的。但是,开发者对一个单一的包含一系列设备和电话号码的员工实体类进行编程。从开发者和项目关系人的角度看,员工是一个单一的正好包含电话号码和设备的对象。开发者不知道而且也不关心DBA已经规范了它这个员工对象到三个分开的表中。一旦配置完毕,单个类与三个表之间的映射就被EF所抽象并处理。
从单一的部门表上可看到相反情况,它被以编程的方式映射到三个代表一个部门的实体类中。再次,对开发者和项目关系人来说,一个分离的实体对象代表了每个部门(会计部、市场部、财政部等等),但是DBA出于数据存储的目的,优化并分解这三个对象到一个表中。
当然,正如在位置表中所看到的那样,可简单映射一个单一的实体类到一个单一的表中,这是EF的默认行为。
这里关键的方便之处在于,开发者和项目关系人与领域类的表示打交道,这些类在程序的上下文中是容易理解的。为了让数据库的运行效率更好,DBA可对底层的数据库表进行组织。使用EF可容易地连接起这两个领域。
层
最后,映射层定义了概念层与存储层之间的映射关系。此外,此层定义了实体类的属性如何映射到表的列,可在EF的设计器或数据注解及fluent API(若选择了基于代码的方式)包含的映射细节窗口看到该层。映射规范语言(Mapping Specification Language, MSL)定义了映射层的语法。本节译完由三个独立地层构成:概念、存在和映射层。层与层之间相互解耦。
实体类包含在实体数据模型的概念层中,这也是开发者及项目关系人使用的层。概念层可从设计器或从代码中构建,这取决于如何实现EF。一旦作了决定,可从现有的数据库中反向设计模型,可借助设计器及与EF一起发行的工具建模,或者从代码中建模,让EF从模型中生成数据库。概念架构定义语言(Conceptual Sema Defintion Language, CSDL)定义了概念层的语法。
每个实用的程序都需要持久化对象到一些数据存储中。实体数据模型的存储层定义用以映射到底层数据库的表、列、关系和数据类型。存储架构定义语言(Store Schema Difinition Language, SSDL)定义了存储模型的语法。
最后,映射层定义了概念层与存储层之间的映射关系。此外,此层定义了实体类的属性如何映射到表的列,可在EF的设计器或数据注解及fluent API(若选择了基于代码的方式)包含的映射细节窗口看到该层。映射规范语言(Mapping Specification Language, MSL)定义了映射层的语法。
术语
正如预期的那样,EF也有自己的语汇。如果使用过其他流行的ORM工具或熟悉数据库建模,你可能已经看过了一些术语。尽管整个语汇表比较大,为了入门,这里只叙述一些基础词汇。
正如前面讨论的那样,实体类型(EntityType)表示领域模型中的一个类,其实例通常称作实体。若使用EF设计器,实体类型就代表了设计表面一个拥有一些属性的长方体。图1-3展示了两个实体类型:员工和任务。
一个实体类型通常拥有一个以上属性。正如类一样,属性是一个被命名并为指定数据类型的值。属性可设置为简单类型,如整数、字符串等等,或者设置为复杂类型,甚至是集合。导航属性引用了其他相关的实体(典型例子是表的外键)。实体类型中的非导航属性通常称作标量属性。
两个实体之间的关系称作关联。实体类型间的关联在设计表面上以连接实体类型的线来表示。线条注明了关联两端的复杂性。图1-3中的关联是员工和任务之间一对多的关联。一个员工有0个或更多任务。每个任务只被分配给一个员工。
每个实体类型有一个或一组用来表示它的实体键的属性。实体键在EF中唯一地标识了一个实体并且它通常被映射为底层数据库中的主键。
最后,不提及上下文对象(context object)将不是一个完整的讨论。上下对象是EF服务的通道。上下文对象向外公开了实体对象,管理数据库连接,生成参数化的SQL语句,与数据库交换数据,缓存对象,维持变更跟踪并且实现或转换无类型的结果集到一个强类型对象的集合中。
起初还存在一个叫ObjectContext对象的东西。如今,EF支持一个改进的上下文对象,叫DbContext。DbContext极大地简化了开发者使用EF的开发体验。有趣地是,DbContext是ObjectContext的一个包装器或一个外观(译注:即外观模式),以一种直觉的、友好的且利于编码的方式公开底层ObjectContext的功能。
显然,DbContext是使用EF首选的方式并且本书将详尽证明这么做的原由。
代码
尽管极其强调可视化设计时支持,但是EF却全由代码组成。模型、实体类型、关联、映射等等,最终都转换成具体的代码,从而成为程序的一部分。这些代码要么由VS及EF生成,或由开发团队手工编写。可选择多种代码生成方法,或者不使用它,修改项目的不同属性或者修改底层的代码生成模版。
VS使用一个叫文本模版转换工具箱(Text Template Transformation Toolkit)的代码生成技术,该技术简称为T4模版。VS使用T4模版自动生成或修改(译注:原文为scaffold)代码。VS所支持的T4模版最棒的一点是,可以修改模版来裁剪代码生成过程,以便满足额外的需求。这是一项先进的技术,因为在某些场合不得不这么做。在部分秘诀中将描述如何做到这一点。
或者,可以利用更先进的代码先行(Code-First)方法,手工创建实现代码,以便直接控制整个过程。使用代码先行方式,无需设计器的帮助就能创建实体类、映射和上下文对象。这些手工创建的实体类通常称作POCO或普通CLR对象(Plan Old CLR Objects)对EF任何依赖。更有趣的是,开发团队可利用Entity Framework Power Tool工具(可从微软免费下载)从一个现有数据库中反向设计(译注:原文为reverse-engineer)一个代码先行模型,而不用上述手工创建实体类、映射及上下文对象的方法。第8章的秘诀描述创建和使用POCO的基础。本书许多秘诀都会描述如何使用代码先行的方法跨越指定的上下文,如在一个多层架构的程序中怎么做。
Visual Studio
基于Windows的应用程序的主要开发工具是Visual Studio。VS从一个简单的C++编译器及编辑器慢慢地进化成一个高度整合、多编程语言的集成开发环境,它贯穿了整个软件开发周期。VS和其相关的工具及服务为设计、开发、单元测试、调试、软件配置管理、构建管理、持续集成等等提供支持。只有少数开发者都才会经历上述整个过程,因此无需担心开发中没使用到上述全部技术。关键在于,VS是一个全功能的工具集,它在EF程序开发中扮演了一个非常重要的角色。
VS为EF模型提供了一个集成的设计表面(design surface),通过设计表面及VS的其他工具可从草图或从现有的数据库中创建模型。当然,也可以不使用设计表面,手工创建实体类型和配置。
大多数情况下,开发是针对现有数据库进行的,VS同样提供了导入表及表间关系到模型的工具,这是非常体贴的,毕竟很少人拥有机会去开发一个全新的软件。大多数人的工作是拓展、维护及完善现有代码及数据库。
另一个可选的做法是,使用一个空的设计表面从草图创建模型,通过添加新的实体类型到设计表面并为模型配置关联及继承层次。模型创建结束后,在设计表面界面上右击,在弹出的菜单中选择从模型生成数据库(Generate Database from Model)。
若团队更喜欢代码,可以创建一系列的包括领域类(domain class),其中包含了关系及一个上下文类(context class),然后连接它们,到EF引擎及特性中挂钩,整个过程无需使用设计表面。
即便实体创建完,需求变更也是常有之事,这是软件开发的惯例了。同样,VS为从数据库更新模型提供了工具。这将保证模型与数据库变更保持同步。另外,EF团队也提供一个叫代码先行迁移(Code First Migrations)的工具,它被用于保证数据库与模型同步。
1 - 2、使用EF
EF与VS高度集成,添加ADO.NET实体数据模型(ADO.NET Entity Data Model)到项目,即可使用EF。在解决方案面板右击项目,选择添加->新项目。在如下图的对话框中,选择ADO.NET实体数据模型(ADO.NET Entity Data Model)模版,此模版包含在数据模版中。点击添加启动实体数据模型向导(Entity Data Model Wizard)。
实体数据模型向导的第一页有两个选项:从现有数据库开始(其实这个被书写为从数据库生成)或从一个空模型开始,如下图所示。
从现有数据库中生成模型是数据库先行(Database-First)的方式。根据从底层数据库中选择表、视图和存储过程,向导将创建一个模型及实体类,这会在后续的工作中用到。上述操作带来第一个好处是,可以对强类型的实体类进行编码,实体类由EF负责映射到底层数据库对应的表。若包含的表在数据库中是关联的,则表间关系在EF中表示为关联(association)。以上是对已有数据库进行建模的一种操作方式。当然,若数据库已经存在,又想使用代码先行(Code-First)的方式来建模,可以使用The Entity Framework Power Tools(译注:可在扩展工具中获取,如下图所示)逆向数据库到领域实体类,结果如手工编写这些类的代码一样。
若在没有数据库的情况下从头开发一个软件,同样有多种选择。在EF设计器中,可从一个空的设计表面开始,右击设计表面,创建实体类型、关联和继承,或者从工具箱中拖拽它们到设计表面。模型设计完成后,右击设计表面,在弹出的菜单中选择从模型生成数据库(Generate Database from Model),该操作生成一个SQL脚本,使用该脚本可生成模型对应的数据库。
同样,也可在VS中手工创建实体类并注册到DbContext对象中,然后挂钩到EF服务中。EF会映射这些类到底层数据库并在运行时在内存中自动创建对应的模型。
模型先行或数据库先行都可使用EF设计器开发模型。下图展示了设计器中一个模型的关键部分。在该模型中,客户与订单是一对多的关系,每个客户可能拥有多份订单,但每份订单仅与一个客户关联。映射细节(Mapping Detail)窗口显示了Customer表的各个列与Customer实体类型(EntityType)的各个标量属性之间的映射。请记住,代码先行(Code-First)中的数据注解(data annotations)或fluent API也可以使用相同的映射配置。
当然,上图只是展示了设计器及模型的一部分关键知识点。本书的秘诀将覆盖使用设计器创建模型的方方面面。在某些情况中,本书将深入讲解设计器的功能以及修改底层的edmx文件来创建模型。edmx文件包含完整的模型定义,即概念层、存储层和映射层。
因此,无论通过数据库先行、模型先行还是代码先行的方式使用EF,目的都是模型。在程序开发过程中,正如处理其他对象一样,当可对模型(实体类型EntityTypes)中的对象编程时,编码效率将会显著提高。以上图的模型为例,Customer及Order类的使用方式与其他对象的一样。
插入新的客户或订单到数据库中只需要创建一个Customer及Order类型的实例,设置实例的属性,把它们加到内存中代表了模型的上下文对象中,调用其SaveChanges方法即可。剩余的工作由EF完成,如生成必需的SQL语句及向数据库发送命令。可使用LINQ或Entity SQL依据模型中的实体类型及关联关系向数据库检索客户及订单数据。
本书所有的秘诀将逐步展示各种数据库的建模过程,以及如何使用模型进行查询、插入、更新及删除数据,还有如何在多种应用程序中使用EF。