EF-Code First(5):二级缓存
EF-Code First(5):二级缓存
〇、目录
一、前言
二、缓存设计
(二) 缓存代码分析及整合
1. 关键代码简介
2. 应用缓存扩展
三、源码获取
四、扩展阅读
一、前言
今天我们来谈谈EF的缓存问题。
缓存对于一个系统来说至关重要,但是是EF到版本6了仍然没有见到有支持查询结果缓存机制的迹象。EF4开始会把查询语句编译成存储过程缓存在Sql Server中,据说EF6中对此做了改进,会把Linq To Entities 的查询条件直接编译缓存在EF中。但是这些都是只是对查询条件做了缓存,而不是缓存查询的结果集(DbSet.Find(object key)那个虽然走了DbSet.Local数据集,但也仅支持通过主键查找单个实体的情况,很有局限性),没有达到我们想要的效果。
EF不加缓存功能,可能也有另外的考虑吧,这里不去猜测。虽然EF团队没有在EF中加入缓存功能,但已经给出的缓存功能的扩展,这就是Community Entity Framework Provider Wrappers,这个扩展的工作原理由下图可以清晰的了解:
该扩展提供了跟踪SQL运行日志与SQJ结果集缓存的功能,这里,我们只用到它的缓存功能来为EF建立二级缓存的支持。
二、缓存设计
(一) 引用EFProviderWrappers
如下图,在NuGet中只提供了Entity Framework Provider Wrapper Toolkit(基础类库)与Entity Framework Tracing Provider(日志跟踪)的下载,很遗憾的并没有提供 Entity Framework Caching Provider(缓存)。
我们只能自己动手来引用了,这里提供几种思路:
- 到 http://code.msdn.microsoft.com/EFProviderWrappers 下载代码,自行编译,然后在项目GMF.Component.Data项目中手动引用EFProviderWrapperToolkit.dll与EFCachingProvider.dll文件。
- EFProviderWrapperToolkit由NuGet下载,EFCachingProvider手动引用。
我是觉得两种思路都挺麻烦的,这个扩展的代码貌似已经不更新了(3/18/2011),而且在GMF.Component.Data中额外的引用两个程序集也是个麻烦事,于是我用下面的方法来引用:
在GMF.Component.Data项目中新建两个文件夹,把以上源代码中的两个工程以文件夹的形式包含到项目中。
这样,似乎更干净利落,如图:
(二) 缓存代码分析及整合
1. 关键代码简介
在EFCachingProvider中,我们要用到的核心类有三个:
- ICache:缓存缓存基类,系统中实现了一个内存缓存类(InMemoryCache),适用于单台服务器的缓存实现,如果要实现分布式缓存,可以从这个基类进行扩展。
- InMemoryCache:内存缓存实现类,内部使用了一个Dictionary<string, CacheEntry>作为缓存容器,以查询的SQL语句及参数的连接字符串(或其MD5值)为键(EFCachingCommands.cs类中定义)。还包含了缓存命中、缓存项数量等数据的统计及缓存清理功能。
- CachingPolicy:缓存策略基类,定义了当前实体是否可缓存(CanBeCached)、定义缓存缓存数(GetCacheableRows)、缓存项滑动过期与绝对过期时间(GetExpirationTimeout)等功能,并默认了绝对过期时间为永不过期(DateTime.MaxValue)。
- NoCachingPolicy:不缓存策略,禁用缓存功能。
- CacheAllPolicy:缓存所有数据策略,缓存项最大数量为int.MaxValue。
- CustomCachingPolicy:自定义缓存策略,使用了CacheableTables与NonCacheableTables两个集合来表示数据类型是否可缓存的白名单与黑名单,这两个名单将在重写的CanBeCached方法中作为类型是否可缓存的验证依据。
- EFCachingConnection:此类定义了类型为ICache,CachingPolicy的两个属性,分别用于接收上面据说的两个扩展点。
2. 应用缓存扩展
EF的DbContext上下文类有一个重载
public DbContext(DbConnection existingConnection, bool contextOwnsConnection) { }
需要的是DbConnection参数,而EFCachingConnection正好是派生自DbConnection的,我们只需要构建一个EFCachingConnection对象作为参数去构造DbContext派生类的对象,即可完成缓存功能的注入(如本篇第一张图所示)。这里,缓存专用的DbContext派生类只需要派生自原项目中定义的EFDbContext类。

- 1 namespace GMF.Component.Data
- 2 {
- 3 /// <summary>
- 4 /// 启用缓存的自定义EntityFramework数据访问上下文
- 5 /// </summary>
- 6 [Export("EFCaching", typeof (DbContext))]
- 7 public class EFCachingDbContext : EFDbContext
- 8 {
- 9 private static readonly InMemoryCache InMemoryCache = new InMemoryCache();
- 10
- 11 public EFCachingDbContext()
- 12 : base(CreateConnectionWrapper("default")) { }
- 13
- 14 public EFCachingDbContext(string connectionStringName)
- 15 : base(CreateConnectionWrapper(connectionStringName)) { }
- 16
- 17 /// <summary>
- 18 /// 由数据库连接串名称创建连接对象
- 19 /// </summary>
- 20 /// <param name="connectionStringName">数据库连接串名称</param>
- 21 /// <returns></returns>
- 22 private static DbConnection CreateConnectionWrapper(string connectionStringName)
- 23 {
- 24 PublicHelper.CheckArgument(connectionStringName, "connectionStringName");
- 25
- 26 string providerInvariantName = "System.Data.SqlClient";
- 27 string connectionString = null;
- 28 ConnectionStringSettings connectionStringSetting = ConfigurationManager.ConnectionStrings[connectionStringName];
- 29 if (connectionStringSetting != null)
- 30 {
- 31 providerInvariantName = connectionStringSetting.ProviderName;
- 32 connectionString = connectionStringSetting.ConnectionString;
- 33 }
- 34 if (connectionString == null)
- 35 {
- 36 throw PublicHelper.ThrowComponentException("名称为“" + connectionStringName + "”数据库连接串的ConnectionString值为空。");
- 37 }
- 38 string wrappedConnectionString = "wrappedProvider=" + providerInvariantName + ";" + connectionString;
- 39 EFCachingConnection connection = new EFCachingConnection
- 40 {
- 41 ConnectionString = wrappedConnectionString,
- 42 CachingPolicy = CachingPolicy.CacheAll,
- 43 Cache = InMemoryCache
- 44 };
- 45
- 46 return connection;
- 47 }
- 48 }
- 49 }

这里缓存策略使用了缓存所有数据(CacheAllPolicy)的策略,在实际项目中,最好自定义缓存策略,而不要使用这个策略,以免服务器内存被撑爆。
我们在应用程序配置(Web.Config或App.Config)中,添加一个名为“EntityFrameworkCachingEnabled”的AppSettings节点,用来进行启用/禁用缓存的开关配置。
- <appSettings>
- ...
- <add key="EntityFrameworkCachingEnabled" value="true" />
- ...
- </appSettings>
另外,缓存扩展还需要我们在配置文件中添加如下节点的配置:

- 1 <system.data>
- 2 <DbProviderFactories>
- 3 <add name="EF Caching Data Provider" invariant="EFCachingProvider" description="Caching Provider Wrapper" type="EFCachingProvider.EFCachingProviderFactory, GMF.Component.Data" />
- 4 <add name="EF Generic Provider Wrapper" invariant="EFProviderWrapper" description="Generic Provider Wrapper" type="EFProviderWrapperToolkit.EFProviderWrapperFactory, GMF.Component.Data" />
- 5 </DbProviderFactories>
- 6 </system.data>

再来看看,怎样使用“EntityFrameworkCachingEnabled”配置来控制缓存功能的开关。我们的设计中,DbContext对象的注入点为如下所示的Context属性:
所以,我们只需要在UnitOfWorkContextBase的派生类中读取 EntityFrameworkCachingEnabled 进行切换即可。

- 1 namespace GMF.Component.Data
- 2 {
- 3 /// <summary>
- 4 /// 数据单元操作类
- 5 /// </summary>
- 6 [Export(typeof (IUnitOfWork))]
- 7 public class EFRepositoryContext : UnitOfWorkContextBase
- 8 {
- 9 /// <summary>
- 10 /// 获取 当前使用的数据访问上下文对象
- 11 /// </summary>
- 12 protected override DbContext Context
- 13 {
- 14 get
- 15 {
- 16 bool secondCachingEnabled = ConfigurationManager.AppSettings["EntityFrameworkCachingEnabled"].CastTo(false);
- 17 return secondCachingEnabled ? EFCachingDbContext.Value : EFDbContext.Value;
- 18 }
- 19 }
- 20
- 21 [Import("EF", typeof (DbContext))]
- 22 private Lazy<EFDbContext> EFDbContext { get; set; }
- 23
- 24 [Import("EFCaching", typeof(DbContext))]
- 25 private Lazy<EFCachingDbContext> EFCachingDbContext { get; set; }
- 26 }
- 27 }

注意,因为EFDbContext与EFCachingDbContext两个属性只能同时用到其中之一,导入需要使用Lazy<>类型来包装,这样没用到的属性就不会实例化了。
下面,我们来测试一下缓存功能是否生效,就用上篇的那个翻页列表吧。判断标准为SQL Server Profiler是否有SQL语句执行。为方便演示,这里在列表的下方显示当前的时间,以便与SQL Server Profiler中的时间进行匹配。
第1页不计。
点击第2页,执行了查询:
点击第3页,执行了查询:
再回到第2页,没有执行查询:
点击第4页,执行了查询:
结论:重复第2页的时候,数据已经缓存了,没有读数据库查询数据,说明缓存已经生效了。
最后要提示的一点:
带缓存的上下文不能担当生成数据库的职责,因此在第一次运行生成数据库的时候,必须关闭缓存。
三、源码获取
为了让大家能第一时间获取到本架构的最新代码,也为了方便我对代码的管理,本系列的源码已加入微软的开源项目网站 http://www.codeplex.com,地址为:
https://gmframework.codeplex.com/
可以通过下列途径获取到最新代码:
- 如果你是本项目的参与者,可以通过VS自带的团队TFS直接连接到 https://tfs.codeplex.com:443/tfs/TFS17 获取最新代码
- 如果你安装有SVN客户端(亲测TortoiseSVN 1.6.7可用),可以连接到 https://gmframework.svn.codeplex.com/svn 获取最新代码
- 如果以上条件都不满足,你可以进入页面 https://gmframework.codeplex.com/SourceControl/latest 查看最新代码,也可以点击页面上的 Download 链接进行压缩包的下载,你还可以点击页面上的 History 链接获取到历史版本的源代码
- 如果你想和大家一起学习MVC,学习EF,欢迎加入群:5008599(群发言仅限技术讨论,拒绝闲聊,拒绝酱油,拒绝广告)
- 如果你想与我共同来完成这个开源项目,可以随时联系我。
四、扩展阅读
- Tracing and Caching Provider Wrappers for Entity Framework
- Entity Framework - Second Level Caching with DbContext
- Entity Framework 缓存处理与日志监控
- 数据层扩展包EFCachingProvider 总结
系列导航
- MVC实用架构设计(〇)——总体设计
- MVC实用架构设计(一)——项目结构搭建
- MVC实用架构设计(二)——使用MEF实用IOC
- MVC实用架构设计(三)——EF-Code First(1):Repository,UnitOfWork,DbContext
- MVC实用架构设计(三)——EF-Code First(2):实体映射、数据迁移,重构
- MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码
- MVC实用架构设计(三)——EF-Code First(4):数据查询
- MVC实用架构设计(三)——EF-Code First(5):二级缓存
- MVC实体架构设计(四)——日志记录
- 未完待续。。。
EF-Code First(5):二级缓存的更多相关文章
- EF 二级缓存 EFSecondLevelCache
EFSecondLevelCache ======= Entity Framework .x Second Level Caching Library. 二级缓存是一个查询缓存.EF命令的结果将存储在 ...
- MVC实用架构设计(三)——EF-Code First(5):二级缓存
前言 今天我们来谈谈EF的缓存问题. 缓存对于一个系统来说至关重要,但是是EF到版本6了仍然没有见到有支持查询结果缓存机制的迹象.EF4开始会把查询语句编译成存储过程缓存在Sql Server中,据说 ...
- MVC 实用架构设计(三)——EF-Code First(5):二级缓存
一.前言 今天我们来谈谈EF的缓存问题. 缓存对于一个系统来说至关重要,但是是EF到版本6了仍然没有见到有支持查询结果缓存机制的迹象.EF4开始会把查询语句编译成存储过程缓存在Sql Server中, ...
- MVC项目实践,在三层架构下实现SportsStore-01,EF Code First建模、DAL层等
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- SSH2中memcached作为hibernate二级缓存
一.安装memcached服务端 1. 下载memcached的windows稳定版,解压放某个盘下面,比如在c:\memcached2. 在CMD下输入 "c:\memcached\mem ...
- SSH整合缓存之-Memcached作为hibernate的二级缓存
Hibernate本身不提供二级缓存,所以需要使用第三方插件来作为二级缓存:本次使用memcached作为Hiberbate的二级缓存:添加步骤如下: 一.需要安装memcached服务端 1. 下载 ...
- mybatis一级缓存二级缓存
一级缓存 Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言.所以在参数和SQL完全一样的情况下,我们使用同一个SqlSess ...
- hibernate设置二级缓存时报错java.lang.NoClassDefFoundError: org/hibernate/engine/jndi/JndiNameException
错误提示大概意思是,没有类定义错误,就是找不到要使用的hibernate二级缓存管理引擎类.我在这用的是ehcache二级轻量级缓存,报错原因可能是导入的jar包版本和使用的hibernate框架核心 ...
- SpringMVC + MyBatis + Mysql + Redis(作为二级缓存) 配置
2016年03月03日 10:37:47 标签: mysql / redis / mybatis / spring mvc / spring 33805 项目环境: 在SpringMVC + MyBa ...
随机推荐
- iOS_22自定义键盘工具栏
最后效果图: Main.storyboard KeyboardTool.xib watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcHJlX2VtaW5lbnQ ...
- Socket 学习(二)
1对1的发送和接收 Client 端 void ThreadReceive() { try { ...
- 【Leetcode】Remove Duplicates from Sorted List in JAVA
Given a sorted linked list, delete all duplicates such that each element appear only once. For examp ...
- 浙江大学PAT考试1009~1012(1010上帝是冠军。。)
哎,pat1010即使java书面,只有java书面,还增加了两个点,,.啊,智商捉佳,主要pat有些不给明确的范围.造成遐想空间.. 还是按顺序介绍.. 题目地址:http://pat.zju.ed ...
- 基于jsoup的Java服务端http(s)代理程序-代理服务器Demo
亲爱的开发者朋友们,知道百度网址翻译么?他们为何能够翻译源网页呢,iframe可是不能跨域操作的哦,那么可以用代理实现.直接上代码: 本Demo基于MVC写的,灰常简单,copy过去,简单改改就可以用 ...
- C语言 链表
原文:C语言 链表 最近在复习数据结构,想把数据结构里面涉及的都自己实现一下,完全是用C语言实现的. 自己编写的不是很好,大家可以参考,有错误希望帮忙指正,现在正处于编写阶段,一共将要实现19个功能. ...
- .NET开源项目 TOP 25
.NET开源项目 TOP 25 如果知道.NET项目在开源中国的git上所占的比重只有5%的话,为什么这个<2014年国人开发的最热门的开源软件TOP 100>榜中.NET项目那么少就是情 ...
- .net4.5的弱事件
.net4.5的弱事件 没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 事件-我的讲法 老师常告诉我,事件是特殊的委托,为委托提供 ...
- windows 7 telnet 开启关闭
win7运行telnet提示:'telnet' 不是内部或外部命令,也不是可运行的程序或批处理文件 原因:win7默认没有打开此功能 解决方案:控制面板->程序和功能->打开或关闭wind ...
- leetcode[71] Sqrt(x)
题目,就是实现一个开方,返回是整数.int sqrt(int x) 用二分法,因为一个数的开方肯定小于 x/2 + 1, 因为小于5的某些数的开方并不一定比x/2小,所以要+1,那么们定义一个left ...