前言

  今天我们来谈谈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建立二级缓存的支持。

  注意:据多位园友经验,此方案不适用于EF6,请使用EF6的朋友另辟蹊径。

缓存设计

引用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项目中新建两个文件夹,把以上源代码中的两个工程以文件夹的形式包含到项目中。

  这样,似乎更干净利落,如图:

  

缓存代码分析及整合

关键代码简介

  在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的两个属性,分别用于接收上面据说的两个扩展点。

应用缓存扩展

  EF的DbContext上下文类有一个重载

public DbContext(DbConnection existingConnection, bool contextOwnsConnection) { }

  需要的是DbConnection参数,而EFCachingConnection正好是派生自DbConnection的,我们只需要构建一个EFCachingConnection对象作为参数去构造DbContext派生类的对象,即可完成缓存功能的注入(如本篇第一张图所示)。这里,缓存专用的DbContext派生类只需要派生自原项目中定义的EFDbContext类。

 namespace GMF.Component.Data
{
/// <summary>
/// 启用缓存的自定义EntityFramework数据访问上下文
/// </summary>
[Export("EFCaching", typeof (DbContext))]
public class EFCachingDbContext : EFDbContext
{
private static readonly InMemoryCache InMemoryCache = new InMemoryCache(); public EFCachingDbContext()
: base(CreateConnectionWrapper("default")) { } public EFCachingDbContext(string connectionStringName)
: base(CreateConnectionWrapper(connectionStringName)) { } /// <summary>
/// 由数据库连接串名称创建连接对象
/// </summary>
/// <param name="connectionStringName">数据库连接串名称</param>
/// <returns></returns>
private static DbConnection CreateConnectionWrapper(string connectionStringName)
{
PublicHelper.CheckArgument(connectionStringName, "connectionStringName"); string providerInvariantName = "System.Data.SqlClient";
string connectionString = null;
ConnectionStringSettings connectionStringSetting = ConfigurationManager.ConnectionStrings[connectionStringName];
if (connectionStringSetting != null)
{
providerInvariantName = connectionStringSetting.ProviderName;
connectionString = connectionStringSetting.ConnectionString;
}
if (connectionString == null)
{
throw PublicHelper.ThrowComponentException("名称为“" + connectionStringName + "”数据库连接串的ConnectionString值为空。");
}
string wrappedConnectionString = "wrappedProvider=" + providerInvariantName + ";" + connectionString;
EFCachingConnection connection = new EFCachingConnection
{
ConnectionString = wrappedConnectionString,
CachingPolicy = CachingPolicy.CacheAll,
Cache = InMemoryCache
}; return connection;
}
}
}

  这里缓存策略使用了缓存所有数据(CacheAllPolicy)的策略,在实际项目中,最好自定义缓存策略,而不要使用这个策略,以免服务器内存被撑爆。

  我们在应用程序配置(Web.Config或App.Config)中,添加一个名为“EntityFrameworkCachingEnabled”的AppSettings节点,用来进行启用/禁用缓存的开关配置。

  <appSettings>
...
<add key="EntityFrameworkCachingEnabled" value="true" />
...
</appSettings>

  另外,缓存扩展还需要我们在配置文件中添加如下节点的配置:

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

  再来看看,怎样使用“EntityFrameworkCachingEnabled”配置来控制缓存功能的开关。我们的设计中,DbContext对象的注入点为如下所示的Context属性:

  

  所以,我们只需要在UnitOfWorkContextBase的派生类中读取 EntityFrameworkCachingEnabled 进行切换即可。

 namespace GMF.Component.Data
{
/// <summary>
/// 数据单元操作类
/// </summary>
[Export(typeof (IUnitOfWork))]
public class EFRepositoryContext : UnitOfWorkContextBase
{
/// <summary>
/// 获取 当前使用的数据访问上下文对象
/// </summary>
protected override DbContext Context
{
get
{
bool secondCachingEnabled = ConfigurationManager.AppSettings["EntityFrameworkCachingEnabled"].CastTo(false);
return secondCachingEnabled ? EFCachingDbContext.Value : EFDbContext.Value;
}
} [Import("EF", typeof (DbContext))]
private Lazy<EFDbContext> EFDbContext { get; set; } [Import("EFCaching", typeof(DbContext))]
private Lazy<EFCachingDbContext> EFCachingDbContext { get; set; }
}
}

  注意,因为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(群发言仅限技术讨论,拒绝闲聊,拒绝酱油,拒绝广告)
  • 如果你想与我共同来完成这个开源项目,可以随时联系我。

扩展阅读

  1. Tracing and Caching Provider Wrappers for Entity Framework
  2. Entity Framework - Second Level Caching with DbContext
  3. Entity Framework 缓存处理与日志监控
  4. 数据层扩展包EFCachingProvider 总结

系列导航

  1. MVC实用架构设计(〇)——总体设计
  2. MVC实用架构设计(一)——项目结构搭建
  3. MVC实用架构设计(二)——使用MEF实用IOC
  4. MVC实用架构设计(三)——EF-Code First(1):Repository,UnitOfWork,DbContext
  5. MVC实用架构设计(三)——EF-Code First(2):实体映射、数据迁移,重构
  6. MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码
  7. MVC实用架构设计(三)——EF-Code First(4):数据查询
  8. MVC实用架构设计(三)——EF-Code First(5):二级缓存
  9. MVC实体架构设计(三)——EF-Code First(6):数据更新
  10. 未完待续。。。

MVC实用架构设计(三)——EF-Code First(5):二级缓存的更多相关文章

  1. MVC实用架构设计(三)——EF-Code First(1):Repository,UnitOfWork,DbContext

    前言 终于到EF了,实在不好意思,最近有点忙,本篇离上一篇发布已经一个多星期了,工作中的小迭代告一段落,终于有点时间来继续我们的架构设计了,在这里先对大家表示歉意. 其实这段时间我并不是把这个系列给忘 ...

  2. MVC实用架构设计(三)——EF-Code First(4):数据查询

    前言 首先对大家表示抱歉,这个系列已经将近一个月没有更新了,相信大家等本篇更新都等得快失望了.实在没办法,由于本人水平有限,写篇博客基本上要大半天的时间,最近实在是抽不出这么长段的空闲时间来写.另外也 ...

  3. MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码

    前言 经过前面EF的<第一篇>与<第二篇>,我们的数据层功能已经较为完善了,但有不少代码相似度较高,比如负责实体映射的 EntityConfiguration,负责仓储操作的I ...

  4. [转]MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码

    本文转自:http://www.cnblogs.com/guomingfeng/p/mvc-ef-t4.html 〇.目录 一.前言 二.工具准备 三.T4代码生成预热 (一) 单文件生成:Hello ...

  5. MVC 实用架构设计(三)——EF-Code First(5):二级缓存

    一.前言 今天我们来谈谈EF的缓存问题. 缓存对于一个系统来说至关重要,但是是EF到版本6了仍然没有见到有支持查询结果缓存机制的迹象.EF4开始会把查询语句编译成存储过程缓存在Sql Server中, ...

  6. MVC 实用架构设计(〇)——总体设计

    〇.目录 一.前言 二.结构图 三.结构说明 一.前言 一直以来都想写这个系列,但基于各种理由(主要是懒惰),迟迟没有动手.今天,趁着周末的空档,终于把系列的目录公布出来了,算是开个头,也给自己一个坚 ...

  7. MVC实用架构设计:总体设计

    http://developer.51cto.com/art/201309/410166.htm

  8. MVC实用构架设计(三)——EF-Code First(6):数据更新最佳实践

    前言 最近在整理EntityFramework数据更新的代码,颇有体会,觉得有分享的价值,于是记录下来,让需要的人少走些弯路也是好的. 为方便起见,先创建一个控制台工程,使用using(var db ...

  9. EF和MVC系列文章导航:EF Code First、DbContext、MVC

    对于之前一直使用webForm服务器控件.手写ado.net操作数据库的同学,突然来了EF和MVC,好多新概念泉涌而出,的确犹如当头一棒不知所措.本系列文章可以帮助新手入门并熟练使用EF和MVC,有了 ...

随机推荐

  1. 使用IHTMLDocument2解决弹出"为了让该网站给你提供个人化信息,是否允许在你计算机放置cookie?"

    mshtml可以说是一个不错的解析html利器,对于像我这样一直都是不用webbrowser,直接用socket或者WebRequest进行HTTP通讯 然后再用IHTMLDocument2.writ ...

  2. LintCode 77: 最长公共子序列

    public class Solution { /** * @param A, B: Two string. * @return: the length of the longest common s ...

  3. Linux文件系统

    今天学习了Linux文件系统,现在来做个小总结. 首先Linux中一切都是文件,下面这个清单是Linux系统的顶层目录结构. 清单 1. Linux 系统的顶层目录结构 / 根目录 ├── bin 存 ...

  4. 安装zabbix-3.0.3+nginx-1.10.1+php-5.6.22

    好久没有接触监控类的软件了,今天抽空搭建了下最新的版本 首先系统环境 zabbix-server-1 192.168.11.11   centos6.7 mysql-server    192.168 ...

  5. ThreadLocal类详解:原理、源码、用法

    以下是本文目录: 1.从数据库连接探究 ThreadLocal 2.剖析 ThreadLocal 源码 3. ThreadLocal 应用场景 4. 通过面试题理解 ThreadLocal 1.从数据 ...

  6. MYSQL MHA

    MYSQL MHA 简介: MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于 Face ...

  7. 通过反射获取DLL的类实现加载窗体

    1.创建一个DLL 类库,并新建一个窗体类,这个直接在vs上操作就好 2. 建立一个Testassembly工程 新建一个测试类 namespace Testassembly { public par ...

  8. adb devices出现no permissions

    在做Android开发的时候,有些设备插入USB调试线,无法调试,出现以下错误: joey 09:58 $ adb devices List of devices attached 20080411 ...

  9. poj1002-487-3279(字符串处理)

    一,题意: 中文题,不解释!二,思路: 1,处理输入的电话号码 2,排序num[]数组 3,输出三,步骤: 1,消除 -.Q.Z 三种字符,将一个电话号码转化为一个整数存如num[]数组 如:num[ ...

  10. 剑指Offer面试题:17.树的子结构

    一.题目:树的子结构 题目:输入两棵二叉树A和B,判断B是不是A的子结构.例如下图中的两棵二叉树,由于A中有一部分子树的结构和B是一样的,因此B是A的子结构. 该二叉树的节点定义如下,这里使用C#语言 ...