什么是Entity Framework
在.NET 3.5之前,开发者通常使用ADO.NET获取或保存数据到底层数据库中。即通过ADO.NET连接到数据库,然后使用DateSet对象与数据库交互。这是一个麻烦且容易出错的处理过程,为了解决这个问题,微软提供了Entity Framework的开源框架,处理程序与数据库相关的操作。
EF是一个开源的ORM框架,开发者使用DO(Domain Object)来处理数据,而不需要关注数据库中表和列。使用EF相对于传统的应用程序,开发者可以用更少的代码,在更高的层次处理数据,以及创建和维护面向数据的应用。
官方定义
Entity Framework is an object-relational mapper (O/RM) that enables .NET developers to work with a database using .NET objects. It eliminates the need for most of the data-access code that developers usually need to write.
EF是一个ORM,让开发者使用.NET对象操作数据库。它解决了开发者需要编写大量数据访问代码的疼点。
下图说明了EF在应用中的位置:
从上图可知,EF在业务层(Domain classes)和数据库之间,它保存DO对象属性中的数据,同时也能从数据库中检索数据,然后自动转换为DO对象。
EF特征
- Cross-platform: EF Core 是跨平台的框架,可以运行在Windows,Linux和Mac上。
- 实体模型:EF创建一个基于POCO(普通CLR对象)对象的EDM(Entity Data Model 实体数据模型),使用这个模型可以查询和保存实体数据到底层数据库。
- 查询:EF可以使用LINQ去获取数据库中的数据,database provider将转换LINQ语句到特定的数据库语言。EF也可使用原生的SQL语句。
- 变更跟踪 Change Tracking:EF会跟踪这个实体状态的更改。
- 保存 当调用SaveChanges()方法,EF会根据实体的变更执行insert,update,delete等命令。EF也提供了一个异步保存方法SaveChangeAsync()。
- 并发:EF默认使用Optimistic Concurrency来保护其他用户的变更覆盖。
- 事务:当查询或保存数据时,EF自动执行事务管理。它也提供了自定义事务处理。
- 缓存:EF提供了缓存机制,所以,重复的查询将返回缓存中的数据,而不是去访问数据库。
- 配置:EF是可配置的,并包含了默认配置规则集合,用户可使用注释特征或Fluent API去配置EF模型。
- 迁移:EF提供了迁移命令集合,可在NuGet包管理器控制台或CLI上,执行创建或管理数据库方案。
EF版本
微软在2008年伴随.NET Framework 3.5一起发布了EF。从哪以后,微软又发布了很多的EF版本。到目前为止,最新的EF版本有两个:EF 6 和 EF Core。下表列举了EF6和EF Core的不同之处:
EF6 | EF Core |
---|---|
2008年和.NET Framework 3.5一起发布 | 2016年与.NET Core 1.0 一起发布 |
稳定,功能多 | 新,且是EF的进化版 |
支持Windows | 跨平台 |
工作在.NET Framework 3.5+ | .NET Framework 4.5+和.NET Core |
开源 | 开源 |
EF 6 的历史版本
EF 版本 | 发布时间 | .NET Framework |
---|---|---|
EF 6 | 2013 | .NET 4.0&.NET 4.5,vs2012 |
EF 5 | 2012 | .NET 4.0,vs2012 |
EF 4.3 | 2011 | .NET 4.0,vs2012 |
EF 4 | 2010 | .NET 4.0,vs2010 |
EF 1 | 2008 | .NET 3.5 SP1,vs2008 |
EF Core 历史版本
EF Core 版本 | 发布时间 | .NET Framework |
---|---|---|
EF Core 2.0 | 2017 8月 | .NET Core 2.0,vs2017 |
EF Core 1.1 | 2016 11月 | .NET Core 1.1 |
EF Core 1.0 | 2016 7月 | .NET Core 1.0 |
EF 基本工作流程
下图说明了EF基本的CRUD工作流:
- 首先,定义模型,包括领域类,context类(继承自DbContext),配置信息。EF是基于这些模型对象执行CRUD操作的。
- 插入数据时,添加领域对象到context中,调用SaveChanges()方法,EF API会构建并执行一个合适Insert语句。
- 读取数据时,执行Linq-to-Entities查询语句,EF API将其转化为对应数据库的SQL查询语句并执行它,结果将转化为DO对象。
- 当context中更新或删除对象时,调用SaveChanges()方法,EF API会构建并执行合适的Update或Delete语句。
EF工作原理
EF API(EF 6 & EF Core)具有这些能力,包括:
- 将DO(领域模型对象)映射到数据库集合
- 翻译并执行Linq语句
- 跟踪实体在其生命周期变更,以及保存实体的变更
EF API的首要任务是构建Entity Data Model(EDM),EDM在内存中的结构可以用来表示:概念模型,存储模型以及者两者两者之间的映射。
Conceptual Model 概念模型由EF通过你定义的领域模型,context和配置生成的。
Storage Model EF从数据库集合中生成存储模型。在code-first方案中,存储模型是由概念模型推断出来的。在database-first方案中,它是从数据库中推断出来的。
Mappings 映射信息包含了概念模型和存储模型之间的映射。
EF通过EDM执行CRUD操作。它通过EDM从Linq语句中构建SQL,以及将执行结果转变为实体对象。
EF 总体架构
下图展示了EF的总体架构
我们分别看看架构中的组件
EDM(Entity Data Model):EDM由概念模型,映射和存储模型三部分构成(见上文)。
Linq to Entities:L2E是一个针对对象模型编写的查询语言,它返回定义在概念模型中的实体。
Entity SQL:Entity SQL是以一种查询语言有点像L2E,但是只能运行在EF6中。相对于L2E,Entity SQL稍难,而且需要开发者单独学习。
Object Service:Object Service是访问数据库的主要入口点。它负责将下一层返回的数据转换为实体对象的结构。
Entity Client Data Provider:这一次主要负责将Linq语句转化为数据的SQL语句,它与ADO.Net进行通信,ADO.Net又向数据库发送或检索数据。
ADO.Net Data Provider:这一层使用标准的ADO.Net库与数据库通讯。
Context类
在EF中context非常重要,它类继承自DbContext,它表示一个与数据库的会话。下面的代码是context类的示例:
public class SchoolContext:DbConext{
public SchoolContext(){
}
public DbSet<Student> Students {get;set;}
public DbSet<StudentAddress> StudentAddresses {get;set;}
public DbSet<Grade> Grades {get;set;}
}
上面的示例中,SchoolContext继承自DbContext,使得他成为了一个context类。它包含了Student,StudentAddress以及Grade的实体集合。
context类用于从数据库中查询或保存数据。也可以用来配置领域模型,数据库关系映射,变更追踪变更设置,缓存,事务等。
EF中的实体
EF中的实体是应用程序的领域类,他被包含在context类的DbSet<TEntity>类型属性中。EF API将TEntity映射到数据库中的表,TEntity的属性映射为表中的列。比如,在school应用程序中,Student,StudentAddress和Grade的领域模型如下:
public class Student{
public int StudentID {get;set;}
public string StudentName {get;set;}
public DateTime? DateOfBirth {get;set;}
public byte[] Photo {get;set;}
public decimal Height {get; set}
public float Weight {get; set}
//引用导航属性
public StudentAddress StudentAddress {get; set;}
public Grade Grade {get; set;}
}
public partial class StudentAddress{
public int StudentID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
//引用导航属性
public Student Student { get; set; }
}
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public string Section { get; set; }
//集合导航属性
public ICollection<Student> Students { get; set; }
}
当他们包含在context类中的DbSet<TEntity>属性中时,它们就是实体。而Students, StudentAddresses和Grades被称为实体集。
实体包含两种类型的属性:标量属性(Scalar Properties)和导航属性(Navigation Properties)。基本类型的属性称为标量属性,标量属性存储实际的数据,标量属性映射到数据中表的列。导航属性表示该属性关联到另一个实体,这里又有两种类型的导航属性:引用导航和集合导航。
- 引用导航属性表示,一个实体的属性的类型是另一个实体类型。
- 集合导航属性表示,实体属性的类型是集合类型。
EF中实体的类型
EF中有两种实体类型:POCO实体和动态代理实体(Dynamic Proxy Entity)。
- POCO实体:POCO就是一个基本类,不依赖任何框架,EF 6和EF Core都支持POCO实体。POCO实体通过EDM生成实体类型,支持CRUD操作。
- 动态代理(POCO Proxy)是运行时的代理类,它包裹了POCO实体,动态代理实体可以lazy loading。动态代理类只支持EF6(EF Core 2.0 不支持该类型)。
实体的状态
EF API维护实体的状态。context类执行实体操作时,会影响实体的状态。实体的状态用枚举来表示,EF 6中是System.Data.Entity.EntityState枚举类型,而EF Core中是Microsoft.EntityFrameworkCore.EntityState枚举类型。他们有如下的枚举值:
- Added 执行insert命令
- Modified 执行update命令
- Deleted 执行delete命令
- Unchanged 无任何改变
- Detached 不追踪实体的状态
context不仅保存所有实体的引用(从数据库中获取),而且还能追踪和维护实体的状态(这个功能叫变更追踪)。context仅会自动处理实体从Unchanged状态到Modified状态。其他状态的改变必须使用明确的DbContext或DbSet方法。在调用context.SaveChanges()方法时,EF API是基于实体的state创建和执行增删改语句的。
开发模式
EF有三种不同的模式供开发者选择:
- Database-First
- Code-First
- Model-First
在数据库优先模式中,可以使用Visual Studio集成EDM向导或执行EF命令来存在的数据库生成context和实体。EF 6对该中模式支持的非常好,EF Core也提供了有限的支持。
当应用程序还没有数据库时,可以使用代码优先模式。在这种模式下,需要先写DO和context,然后使用迁移命令创建数据库。开发者如果遵循领域驱动设计(Domain-Driven Design DDD)原则,喜欢先编写领域模型然后在生成数据库。EF 6和EF Core都支持这种模式。
模型优先模式下,Visual Studio中集成了的Visual Designer工具,可以在上面创建实体,关系和继承层次结构,然后生成实体,context和数据库脚本。这种方式仅EF 6支持。
EF中的持久方式
当使用EF保存实体到数据库中时,有两种场景供选择:连接场景和非连接场景。
连接场景
在连接场景下,检索和保存实体都是使用的同一个context对象,这样,它可以在对象的生命周期中保持对其状态的追踪。这种场景常常用在Windows应用程序或数据库在本地的情况。
上图可以看到,检索和保存都是使用的同一个context对象。这项做的好处是,执行效率高,context对象保持对实体状态的跟踪。缺点是需要对数据库保持长连接,比较耗资源。
非连接场景
检索和保存使用不同的context对象,使用完后context对象会被自动释放。
非连接场景要相对复杂些,因为context对象不能追踪实体的状态,所以开发者在调用SaveChanges()方法前,必须要设置实体的状态。在上图中,程序使用context1检索数据,使用context2执行CUD操作。这种模式常用在web应用程序或远程数据库的情况。优点,使用较少的资源,不需要长连接。缺点,在保存数据之前需要设置实体的状态,执行效率稍慢。