ORM 和 EF

当我们要开发一个应用程序,就要考虑怎样展示数据,怎样持久化数据。考虑这个问题时我们所要关心的东西,最重要的莫过于程序的性能、开发的简易性和代码的可维护、可扩展性。

持久化(Persistence),是指在应用程序中能永久地保存各个处理状态信息的机制。如果没有持久化这个机制,状态只能保存在内存中,机器关机后就会丢失。

在应用程序中,当要永久地保存数据时,我们会选择关系数据库(Relation DataBase);当要临时地保存数据时,我们则使用存储在内存中的对象。目前对于大多数开发人员来说,都是用关系数据库技术来作为持久化机制。虽然现在有些人正在尝试使用对象数据库(Object DataBase)技术,但关系数据库技术在许多年以内依然会是最主要的持久化机制。

为什么会出现对象数据库? SQL语言是一种非过程化的面向集合的解释型语言,而很多高级语言是过程化的面向对象的编译型语言,这使得两种语言之间存在着不匹配,导致效率不如人意。这种不匹配被称为“阻抗失配”,对象数据库的出现就是为了解决“阻抗失配”。

我们知道,在关系数据库技术中是用Table以行和列的结构来存放和组织数据的。在.NET 2.0以前,C#还没有泛型的时候,人们基本上用填充在DataSet中的DataTable来映射并存放从关系数据库中查询出来的数据,正如下面代码所示:

using (SqlConnection conn = new SqlConnection(connString)) {
using (SqlDataAdapter da = new SqlDataAdapter("Select * from order", conn)) {
DataTable dt = new DataTable();
da.Fill(dt);
...
}
}

这种方式虽然能让面向对象语言匹配关系数据库,但它有一些明显的缺点,如非类型安全、难操控、低性能等。从.NET 2.0开始,人们开始通过泛型技术用实体模型对象的集合来匹配关系数据库中的数据,这种方式解决了DataTable方式所面临的缺点,且它有强类型、在VS中自动完成、编译时检查等特点,厂受.Net开发人员的喜爱。

为了让开发人员不用手动去做这种“匹配”工作,人们研发了很多ORM工具(如Entity Framework、NHibernate等)。ORM(Object Relation Mapping)工具,顾名思义,它的角色就是为了解决“关系”和“面向对象”之间的“失配”,它可以使得开发人员不用过多关心持久层而可以花更多的时间专注于业务。

Entity Framework(EF)是微软以ADO.NET为基础所发展出来的ORM解决方案,以Entity Data Model(EDM) 为主。EF利用了抽象化数据结构的方式,将每个数据库对象都转换成应用程序中的类对象(Entity),而数据字段都转换为属性 (Property),关系则转换为结合属性 (Association),让数据库的 E/R 模型完全的转成对象模型,如此让开发人员就能用熟悉的面向对象编程语言来调用访问。EF 4.0 以后支持Database First、Model First、Code First三种生成模式,Code First模式用的人比较多。

概念和理论性的东西就不多讲了,我也是带着疑问去查找网络资源根据自己的理解半摘半归纳出来的,而且鄙人不才,很多东西想描述但都不知道怎么组织语言。

本文目的主要是让读者对EF有个感性的认识,然后了解EF在ASP.NET MVC中的应用,不会去研究原理性的东西,下次有空再单独介绍EF吧。

使用Entity Framework

接着上一篇博文 使用Ninject,我们现在要把代码中手工造的数据改成从数据库读取。为此,我们先准备下数据库。本示例使用的是MS SQL Server,使用其他数据库也是一样的。先创建一个名为BookShop的数据库,然后执行下面的脚本创建一个Books表:

CREATE TABLE Books
(
[ID] INT NOT NULL PRIMARY KEY IDENTITY,
[Title] NVARCHAR(100) NOT NULL,
[Isbn] VARCHAR(20) NOT NULL,
[Summary] NVARCHAR(1000) NOT NULL,
[Author] NVARCHAR(50) NOT NULL,
[Thumbnail] VARBINARY(MAX),
[Price] DECIMAL(16, 2) NOT NULL,
[Published] DATE NOT NULL,
)

然后随便在表中加几条用于测试的数据:

接下来我们就要让应用程序连接数据库了。由于上一篇博文是我在公司用休息的时间写的,公司的电脑装的是VS2010,家里的笔记本装的是VS2012,所以得重新把上篇博文的示例移到VS2012上,对于本示例,VS2010和VS2012都是一样的。上一篇示例项目的目录结构如下:

本文的示例将在上篇的这个示例基础上继续。

用NuGet在BookShop.Domain工程中安装Entity Framework包,方法请参考本系列的上一篇文章。

在BookShop.Domain工程的Concrete文件夹中添加一个名为EFDbContext的类,代码如下:

public class EFDbContext : DbContext {
public DbSet<Book> Books { get; set; }
}

使用EF Code First第一步就是创建一个继承自System.Data.Entity.DbContext的类,这个类将为数据库中的每个表定义一个属性,属性的名称代表数据库中的表名。DbSet作为返回类型,它是用于生成CRUD(Create、Read、Update和Delete)操作的装置,映射数据库表的行。

我们需要在BookShop.WebUI工程中的web.config配置文件中添加数据库的连接字符串,来告诉EF怎样连接数据库。根据自己机器上的数据库配置连接字符串如下:

<connectionStrings>
<add name="EFDbContext" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=BookShop;User ID=sa;Password=sa" providerName="System.Data.SqlClient" />
</connectionStrings>

接下来,我们把BookShop.Domain工程下Concrete文件中的BookRepository类文件改造一下,把代码中手工造的数据改成从数据库读取,以测试应用程序是否可以正常连接数据库。修改后的BookRepository类如下:

public class BookRepository : IBookRepository {
private EFDbContext context = new EFDbContext(); public IQueryable<Book> Books {
get { return context.Books; }
}
}

在我们的这个仓储类中,我们改使用EF,通过创建一个EFDbContext类的实例来获取数据库中的数据。如你所见,我们不需要自己写ADO.NET代码去连接和读取数据库,非常简洁明了,我们就是这样使用Entity Framework的。我们来看一下运行效果吧:

到这我们已经成功使用EF连接上了数据库,并从数据库中读取出来了数据。我们还可以通过Linq进行非常灵活的查询,就像写SQL一样。比如要查询价格在100元以下的前10条记录,并且按价格从低到高显示,那么我们可以在BookShop.WebUI工程下的BookController中的List方法中这样写:

public ViewResult List() {
return View(repository.Books
.OrderBy(b => b.Price)
.Where(b => b.Price < 100)
.Take(10));
}

或许你很快就会对EF获取数据库的方式产生这样的疑问:EF从数据库中读取整个Books表的数据到内存,然后返回给调用者(上面代码中的repository.Books)用Linq语句过滤用户想要的前10条数据,如果Books表中有几百万条数据,那内存岂不是完蛋了,EF不会这么傻吧?EF会不会根据Linq查询语句智能地生成SQL文本再到数据库中去查询数据呢?这里就要讲讲IQueryable和IEnumerable了。

IQueryable 和 IEnumerable

其实,对于上面的即有过虑又有排序的条件查询Linq语句,EF是读取数据库中整个Books表中的数据到内存,还是根据Linq查询语句智能的生成SQL再执行查询,完全编码者来决定的。我们打开BookShop.Domain工程的BookRepository类文件,请注意该类中Books属性的返回类型:

...
public IQueryable<Book> Books {
get { return context.Books; }
}

上篇博文中,我们对使用IQueryable作为返回类型提了个疑问:为什么用IQueryable而不用IEnumerable作为返回类型?答案是:使用IQueryable,EF会根据调用者的Linq表达式先生成相应的SQL查询语句,然后到数据库中执行查询,查询出来的数据即是用户想要的数据;而使用IEnumerable,Linq表达式的过滤、排序等操作都是在内存中发生的,即EF会先从数据库中把整个表的数据查询出来放在内存中,然后由调用者使用Linq语句进行过滤、排序等操作。是不是这样呢?我们来监视一下两种情况EF生成的SQL语句就知道了。

我们先来看看使用IQueryable的情况。重新运行一下程序,然后使用SQL Server Management Studio的活动和监视器查看一下我们的BookShop应用程序所执行的SQL语句,结果如下:

结果证明使用IQueryable,EF是先根据Linq表达式生成相应的SQL语句再执行查询的。

我们再稍稍修改一下代码来看看用IEnumerable的情况。把BookRepository类修改如下:

public class BookRepository : IBookRepository {
private EFDbContext context = new EFDbContext(); public IEnumerable<Book> Books {
get { return context.Books; }
}
}

当然BookRepository类所实现的IBookRepository接口(在BookShop.Domain工程的Abstract文件夹中)也要改一下:

public interface IBookRepository {
IEnumerable<Book> Books { get; }
}

再重新运行一下应用程序,用活动和监视器查看最后执行的SQL语句如下图:

我们看到改用IEnumerable后,EF生成的SQL没有任何过滤、排序等的操作,它一次把表中的所有数据都Select出来,和上面写的Linq表达式一点都没关系。

IQueryable虽然可以很智能地根据Linq表达式生成相应的SQL语句,但毕竟有一个分析Linq表达式的过程,相对来说性能比IEnumerable要差。那么我们什么时候用IEnumerable,什么时候用IQueryable呢?我想,对于少量的数据(比如从数据库中读取应用程序相关的系统信息)和不需要对数据进行过滤操作的情况,用IEnumerable比较适合;对于数据量较大需要对数据进行过滤(比如分页查询)的情况,则用IQueryable比较合适。

参考:  《Pro ASP.NET MVC 4》
   http://www.entityframeworktutorial.net/

使用 Entity Framework的更多相关文章

  1. ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第二章:利用模型类创建视图、控制器和数据库

    在这一章中,我们将直接进入项目,并且为产品和分类添加一些基本的模型类.我们将在Entity Framework的代码优先模式下,利用这些模型类创建一个数据库.我们还将学习如何在代码中创建数据库上下文类 ...

  2. Entity Framework Core 1.1 升级通告

    原文地址:https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-entity-framework-core-1-1/ 翻译:杨晓东 ...

  3. Entity Framework Core 实现MySQL 的TimeStamp/RowVersion 并发控制

    将通用的序列号生成器库 从SQL Server迁移到Mysql 遇到的一个问题,就是TimeStamp/RowVersion并发控制类型在非Microsoft SQL Server数据库中的实现.SQ ...

  4. 采用MiniProfiler监控EF与.NET MVC项目(Entity Framework 延伸系列1)

    前言 Entity Framework 延伸系列目录 今天来说说EF与MVC项目的性能检测和监控 首先,先介绍一下今天我们使用的工具吧. MiniProfiler~ 这个东西的介绍如下: MVC Mi ...

  5. 来,给Entity Framework热热身

    先来看一下Entity Framework缓慢的初始化速度给我们更新程序带来的一种痛苦. 我们手动更新程序时通常的操作步骤如下: 1)把Web服务器从负载均衡中摘下来 2)更新程序 3)预热(发出一个 ...

  6. 采用EntityFramework.Extended 对EF进行扩展(Entity Framework 延伸系列2)

    前言 Entity Framework 延伸系列目录 今天我们来讲讲EntityFramework.Extended 首先科普一下这个EntityFramework.Extended是什么,如下: 这 ...

  7. Entity Framework教程(第二版)

    源起 很多年前刚毕业那阵写过一篇关于Entity Framework的文章,没发首页却得到100+的推荐.可能是当时Entity Framework刚刚发布介绍EF的文章比较少.一晃这么多年过去了,E ...

  8. Entity Framework 6 Recipes 2nd Edition 译 -> 目录 -持续更新

    因为看了<Entity Framework 6 Recipes 2nd Edition>这本书前面8章的翻译,感谢china_fucan. 从第九章开始,我是边看边译的,没有通读,加之英语 ...

  9. ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第一章:创建基本的MVC Web站点

    在这一章中,我们将学习如何使用基架快速搭建和运行一个简单的Microsoft ASP.NET MVC Web站点.在我们马上投入学习和编码之前,我们首先了解一些有关ASP.NET MVC和Entity ...

  10. ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之目录导航

    ASP.NET MVC with Entity Framework and CSS是2016年出版的一本比较新的.关于ASP.NET MVC.EF以及CSS技术的图书,我将尝试着翻译本书以供日后查阅. ...

随机推荐

  1. leetcode 124. Binary Tree Maximum Path Sum ----- java

    Given a binary tree, find the maximum path sum. For this problem, a path is defined as any sequence ...

  2. 硬链接与软连接[转自vbird]

    前言 在 Linux 底下的连结档有两种,一种是类似 Windows 的快捷方式功能的文件,可以让你快速的链接到目标文件(或目录),这种是软链接: 另一种则是透过文件系统的 inode 连结来产生新档 ...

  3. 常见半监督方法 (SSL) 代码总结

    经典以及最新的半监督方法 (SSL) 代码总结 最近因为做实验需要,收集了一些半监督方法的代码,列出了一个清单: 1. NIPS 2015 Semi-Supervised Learning with ...

  4. 写EXCEL(csv 可以用EXECEL打开,逗号分列隔符)

    FILE *file = NULL; char path[]="D:\\Data\\Pos.csv"; CTime m_tDateTime; m_tDateTime = m_tDa ...

  5. oracle闪回使用以及删除存储过程恢复

    oracle恢复删除的数据  恢复删除的存储过程 SELECT * FROM dba_source as of timestamp (systimestamp -interval'600'second ...

  6. 清除PDF里的元数据和机密信息的方法

    相信很多人都知道,PDF文档的表现形式可以大不相同,它们可能包含某些数据,乍一看根本看不见,那些数据可能是不适合共享的信息-比如元数据(作者.主题.关键词).书签.扫描文档里的文本层等,通过ABBYY ...

  7. Java 编程实践

    创建一个54个元素的整数数组,并将其元素值依次赋值为:1~54,用于表示一副牌的54张.再创建一个12个元素的整数数组,用于表示某玩家手中的牌,然后从前一数组中随机抽取12个元素赋值给该数组.打印后一 ...

  8. unity, 保存prefab时material丢失问题

    在程序运行时用replacePrefab(gameObj,prefab)或createPrefab(gameObj,prefab)保存prefab,遇到保存出来的prefab中material丢失的问 ...

  9. Q6: Binary Tree Preorder Traversal

    问题描述 Given a binary tree, return the preorder traversal of its nodes' values. For example:Given bina ...

  10. SQL Server取系统当前时间【转】

    getdate //获得系统当前日期 datepart //获取日期指定部分(年月日时分表) getdate()函数:取得系统当前的日期和时间.返回值为datetime类型的. 用法:getdate( ...