数据点

Entity Framework 和 AppFabric 中的二级缓存

Julie Lerman

下载代码示例

Julie Lerman
实体框架 (EF) ObjectContext 和 DbContext 维护他们的管理有关的实体的状态信息。但一旦上下文超出范围时,该状态信息都丢失了。这种类型的缓存被称为第一级缓存和事务的生存期内才可用。如果您要编写使用 EF 上下文不逗留的分布式应用程序),因此您的状态信息不是连续可用 — 第一级缓存可能是不应该足够支持您的需求。这通常是与 Web 应用程序和服务的情况 — — 或甚至当使用某种类型的存储库模式实现其中一个长时间运行上下文不可用。

为什么 EF 可以受益的二级缓存

为什么您应关注有关跨进程有权访问的表示形式的原始状态?EF 的最大优势之一是它能够自动生成数据库持久性命令 (插入、 更新和删除) 根据上下文中找到的状态信息。但如果该状态信息不可用,EF 具有不执行任何操作时调用 SaveChanges 的时间。开发人员,包括我自己,一直试图解决此限制,因为在 2006 年首次引入 EF。

二级高速缓存是解决此类问题不可或缺的。这些缓存存在外部事务的 — 通常之外的应用程序 — 并因此可供任何上下文实例。和二级缓存是一种常用的编码模式为各种用途的数据缓存。

而是比编写您自己的缓存数据的方式,有可用缓存机制如 memcached (memcached.org),和甚至缓存中 Microsoft AppFabric (适用于 Windows 服务器以及 Windows Azure 所示) 的支持。这些服务提供了基础结构用于缓存,因此您不必在流汗详细信息。然后它们公开便于阅读、 存储和过期缓存中的数据的程序员的 Api。

如果您有一个高事务性系统,它可以受益于高速缓存,以避免重复通常查询得到的数据的数据库,您还会发现自己寻找一个缓存方案。这是为了提高性能,使用不经常修改的数据时的好方法--例如,引用数据或上运动队的队员的列表。

图 1 显示维护 EF 的上下文中,以及各种上下文访问常见的二级高速缓存内的第一层级缓存。

First-Level Caching Happens Inside a Transactional Context and Second-Level Caching Is External
在图 1 第一级缓存发生内部事务上下文和二级缓存外部**

若要添加的二级高速缓存使用 EF 缓存提供程序

设计用于读取、 存储和过期缓存数据的逻辑花费不少时间。您可能希望在查询数据或存储数据时使用 EF 时执行此操作。当执行针对上下文的查询时,您需要第一个请参阅如果数据存在于缓存中,因此您不必在数据库调用上浪费资源。当使用上下文 SaveChanges 方法更新的数据,您需要过期并可能刷新缓存中的数据。并使用缓存是比只是读取和写入数据更加复杂。有很多需要考虑其他注意事项。没有从关联的计算机械 (ACM) 排放的 ORM 缓存,复杂性的详细的文章"公开 ORM 缓存: 熟悉 ORM 缓存存在的问题可以帮助防止性能问题和错误"(bit.ly/k5bzd1)。将不会试图重复优点、 缺点以及文章中概述的热点问题。相反,我将重点实施。

EF 没有对第二级高速缓存使用的内置支持。该功能将最有意义的 ObjectContext 和 DbContext 逻辑的一部分时,其将要与数据库交互。但实现缓存时考虑到 ACM 文章中讨论的各种问题是不常用,特别是在缺乏中 EF 明显扩展性点。作为最大的区别,EF 和 NHibernate 之间经常突出显示的功能之一是 NHibernate 具有用于实现第二级缓存的内置支持。

但并非毫无希望 !输入 EF 提供商和 brainy Jarek Kowalski (j.mp/jkefblog),前者 EF 小组的成员。

EF 提供程序模型是如何 EF 是能够支持任何关系数据库的关键 — — 只要没有写入该数据库包括 EF 支持的提供程序。有许多第三方提供商允许您使用 EF 具有不断增长的数据库 (SQL Server、 Oracle、 Sybase、 MySQL 和 Firebird 是只是一些示例) 数组。

EF ObjectContext 讨论给较低级别的 EntityClient API,在其与数据库提供程序计算出特定于数据库的命令通信,并再与数据库交互。当数据库返回的数据 (作为结果的查询或更新存储生成的值的命令) 时,该路径被反转,如中所示图 2

Flow from the EF Context Through an ADO.NET Provider to Get to the Database
图 2 从通过 ADO EF 上下文的流动。NET 提供商以获取到数据库

提供程序所在的专色是 pliable,从而使您可以插入 EntityClient 和数据库之间的其他提供商。这些被称为提供程序包装。您可以了解有关编写 ADO 的详细信息。NET 提供程序 EF 或其他类型的提供程序 EF 团队博客上张贴内容,"编写 EF 启用 ADO。NET 提供程序"(bit.ly/etavcJ)。

几年前,Kowalski 用于 EF 提供商他深层知识编写捕获实体客户端和 ADO 之间的邮件提供商。NET 所选择的提供程序 (无论是 SqlClient,MySQL 连接器或其他) 和注入逻辑,以与第二级缓存机制进行交互。包装是可扩展的。它为任何类型的缓存解决方案,提供了基础逻辑,但您需要实现一个类,此包装和缓存解决方案之间的桥梁。提供程序示例处理缓存内存中,而解决方案必须使用"速度",这是 Microsoft 分布式缓存代码名称的示例适配器。Velocity 最终成为 Microsoft Windows 服务器 AppFabric 中的缓存机制。

为 Windows 服务器 AppFabric 构建 EFCachingProvider 适配器

EFCachingProvider 最近已更新为 EF 4。跟踪和实体框架页缓存提供程序包装 (bit.ly/zlpIb) 包括极好的示例和文档,这样就不必重复所有该此处。但是,速度适配器已被删除,并且没有使用 AppFabric 中的高速缓存不替换。

AppFabric 居住在两个地方: Windows 服务器和 Windows Azure。我已经重新创建的提供程序类,以便它现在使用在 Windows 服务器 AppFabric,缓存速度与交流,我将介绍如何完成此自己。

首先,确保您已经安装了 EF 提供程序包装从 bit.ly/zlpIb。从事过在示例解决方案中,其中包含提供程序包装 (EFCachingProvider、 EFTracingProvider 和 EFProviderWrapperToolkit) 的项目。还有一些测试出最终的缓存功能的客户端项目。InMemoryCache 提供程序是默认缓存策略和内置 EFCachingProvider。也将突出显示该项目中图 3 是 ICache.cs。InMemoryCache 继承根据这些信息,并因此您要创建用于其他任何其他的适配器应该缓存机制 — 如我创建的 AppFabricCache 适配器。

ICache and InMemoryCache Are Core Classes in the EFCachingProvider
图 3 ICache 和 InMemoryCache 是在 EFCachingProvider 中的核心类

要开发用于 AppFabric,您将需要 AppFabric 缓存客户端程序集和最小安装的 AppFabric,您的开发计算机上。在请参阅 MSDN 库主题,"演练: 部署 Windows 服务器 AppFabric 在单节点开发环境中," bit.ly/lwsolW,有关此任务的帮助。在收到警告,它是一位涉及。我是采取自己开发的两台计算机上。

现在您可以创建一个适配器的 Windows 服务器 AppFabric。这是非常接近于原始的 Velocity3 适配器,但我未花费一些时间来学习如何使用 AppFabric 客户端 API 来获取对齐这些星。如果您正在创建不同的缓存机制的适配器,则需要相应地调整该缓存 api。

测验题的另一个关键一点是要扩展您的 ObjectContext 类。我希望尝试执行此操作与 EF 4.1 DbContext 很快,但这将使得修改 EFCachingProvider 的基本逻辑。

您可以使用相同的代码来扩展不管哪种实施的 ICache 您正在使用的上下文。扩展的类继承从您的上下文类 (进而反过来,继承 ObjectContext),并再公开某些扩展方法,从 EFCachingProvider。这些扩展方法启用要与缓存的提供程序进行交互直接 (和自动) 的上下文。图4的解决方案,扩展了 NorthwindEFEntities,对罗斯文数据库生成模型的上下文中显示了一个示例。

图 4 扩展现有类的继承 ObjectContext,NorthwindEFEntities

using EFCachingProvider;
using EFCachingProvider.Caching;
using EFProviderWrapperToolkit;
 
namespace NorthwindModelDbFirst
{
  public partial class ExtendedNorthwindEntities : NorthwindEFEntities
  {
 
  public ExtendedNorthwindEntities()
    : this("name=NorthwindEFEntities")
  {
  }
 
  public ExtendedNorthwindEntities(string connectionString)
    : base(EntityConnectionWrapperUtils.
CreateEntityConnectionWithWrappers(
    connectionString,"EFCachingProvider"))
  {
  }
  
  private EFCachingConnection CachingConnection
  {
    get { return this.UnwrapConnection<EFCachingConnection>(); }
  }
 
  public ICache Cache
  {
    get { return CachingConnection.Cache; }
    set { CachingConnection.Cache = value; }
  }
 
  public CachingPolicy CachingPolicy
  {
    get { return CachingConnection.CachingPolicy; }
    set { CachingConnection.CachingPolicy = value; }
  }
 
  #endregion
  }
}

我已经向名为 EFAppFabricCacheAdapter 的解决方案中添加一个类库项目。 该项目需要对 EFCachingProvider,以及两个 AppFabric 程序集的引用: Microsoft.ApplicationServer.Caching.Core 和 Microsoft.ApplicationServer.Caching.Client。 图 5 显示我的适配器类,AppFabricCache,模拟原始 VelocityCache。

与 Windows 服务器 AppFabric 进行交互的图 5 适配器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using EFCachingProvider.Caching;
using Microsoft.ApplicationServer.Caching;
 
namespace EFAppFabricCacheAdapter
{
  public class AppFabricCache : ICache
  {
    private DataCache _cache;
 
    public AppFabricCache(DataCache cache)
    {
      _cache = cache;
    }
 
    public bool GetItem(string key, out object value)
    {
      key = GetCacheKey(key);
      value = _cache.Get(key);
 
      return value != null;
    }
 
    public void PutItem(string key, object value,
      IEnumerable<string> dependentEntitySets,
      TimeSpan slidingExpiration, DateTime absoluteExpiration)
    {
      key = GetCacheKey(key);
      _cache.Put(key, value, absoluteExpiration - DateTime.Now,
        dependentEntitySets.Select(c => new DataCacheTag(c)).ToList());
 
      foreach (var dep in dependentEntitySets)
      {
        CreateRegionIfNeeded(dep);
        _cache.Put(key, " ", dep);
      }
 
    }
 
    public void InvalidateSets(IEnumerable<string> entitySets)
    {
      // Go through the list of objects in each of the sets.
foreach (var dep in entitySets)
      {
        foreach (var val in _cache.GetObjectsInRegion(dep))
        {
          _cache.Remove(val.Key);
        }
      }
    }
 
    public void InvalidateItem(string key)
    {
      key = GetCacheKey(key);
 
      DataCacheItem item = _cache.GetCacheItem(key);
      _cache.Remove(key);
 
      foreach (var tag in item.Tags)
      {
        _cache.Remove(key, tag.ToString());
      }
    }
 
    // Creates a hash of the query to store as the key 
    private static string GetCacheKey(string query)
    {
      byte[] bytes = Encoding.UTF8.GetBytes(query);
      string hashString = Convert
        .ToBase64String(MD5.Create().ComputeHash(bytes));
      return hashString;
    }
 
    private void CreateRegionIfNeeded(string regionName)
    {
      try
      {
        _cache.CreateRegion(regionName);
      }
      catch (DataCacheException de)
      {
        if (de.ErrorCode != DataCacheErrorCode.RegionAlreadyExists)
        {
          throw;
        }
      }
    }
  }
}

类使用 Microsoft.ApplicationServer.Caching.DataCache 来满足所需的 ICache 实现。 最值得一提是,使用 AppFabric 中的区域的 PutItem 和 InvalidateSets。 新的项存储在高速缓存中,适配器同时将其添加到区域或由特定实体集中的所有实体定义的组。 换句话说,如果您有一个模型与客户、 订单和行项目,然后将您客户实例缓存在客户区域,一个称作订单等区域中的订单实例中。 当某个特定的项将失效,而不是寻找特定项和作废,所有区域中的项目都将失效。

它是此使用导致我将放在一边我试图实现 Windows Azure AppFabric 支持的区域。 在撰写此专栏时,Windows Azure AppFabric 仍然是 CTP 和不支持区域。 由于缓存的提供程序的基础代码依赖于这些区域和这些方法,我无法轻松地创建只适合 Windows Azure AppFabric 提供程序实现。 可以,当然,调用 InvalidateItem 方法自己,但是,就可以省去自动化提供程序的行为的好处。

使用 AppFabric 缓存适配器

没有一个最后一个项目以添加,并且这就是执行适配器的项目。 原始解决方案一部分的 EFCachingProvider 演示测试出缓存,将控制台应用程序使用几种方法: SimpleCachingDemo,CacheInvalidationDemo 和 NonDeterministicQueryCachingDemo。 在用于测试出 AppFabricCache 我添加的控制台应用程序,您可以将相同的三种方法使用同一实现。 什么是有趣有关此测试是实例化和配置将由这三种方法中扩展的上下文使用 AppFabricCache 的代码。

需要创建的第一个标识 AppFabric 服务器终结点,然后使用其 DataCacheFactory 创建 DataCache AppFabric DataCache。 以下是要做到这一点的代码:

private static ICache CreateAppFabricCache()
{
  var server = new List<DataCacheServerEndpoint>();
  server.Add(new DataCacheServerEndpoint("localhost", 22233));
  var conf = new DataCacheFactoryConfiguration();
  conf.Servers = server;
  DataCacheFactory fac = new DataCacheFactory(conf);
  return new AppFabricCache(fac.GetDefaultCache());
}

请注意我是的 hardcoding 的终结点详细信息,为简单起见,本示例中,但您可能不想在生产应用程序中执行此操作。 一旦创建了 DataCache,然后,使用它来实例化 AppFabricCache。

与此缓存手中,我可以将它传递给 EFCachingProvider,并应用配置如 DefaultCachingPolicy:

ICache dataCache = CreateAppFabricCache();
EFCachingProviderConfiguration.DefaultCache = dataCache;
EFCachingProviderConfiguration.DefaultCachingPolicy = CachingPolicy.CacheAll;

最后,当实例化我扩展的上下文时它会自动查找缓存的提供程序,查找只是设置为默认的 AppFabricCache 实例。 这将导致缓存处于活动状态,使用任何您应用的配置设置。 您需要做的就是关于您的业务与上下文转 — 查询、 使用对象并调用 SaveChanges。 这要归功于将您的上下文绑定到 EFProviderCache 和 DataCache 实例连接的扩展方法,所有的缓存将自动发生在后台。 请注意 CacheAll CachingPolicy 是相当不错的演示,但您应该考虑使用更微调的策略,以便不会不必要地缓存数据。

EFProviderCache 设计了记住的可扩展性。 只要目标高速缓存您要使用的机制支持标准的实现能够存储、 检索、 过期,并对数据进行分组,您应该能够遵循此适配器以 EF 用于数据访问的应用程序提供共享的缓存的模式。

Julie Lerman是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 Microsoft .NET 主题的演示。她是受到广泛称赞的《Programming Entity Framework》(O'Reilly Media,2010)一书的作者,她的博客地址是 thedatafarm.com/blog。请关注她的 Twitter:twitter.com/julielerman

这要归功于以下的技术专家审阅这篇文章: Jarek Kowalski Srikanth Mandadi