高校网上报名:Data Points

来源:百度文库 编辑:九乡新闻网 时间:2024/10/02 17:44:50

Second-Level Caching in the Entity Framework and AppFabric

Julie Lerman

Download the Code Sample

TheEntity Framework (EF) ObjectContext and DbContext maintain stateinformation about entities they’re managing. But once the context goesout of scope, that state information is gone. This type of caching isreferred to as first-level caching and is only available for thelifetime of a transaction. If you’re writing distributed applicationsusing the EF where the context doesn’t stick around—and therefore yourstate information isn’t continuously available—the first-level cachelikely won’t suffice to support your demands. This is typically the casewith Web applications and services—or even when you’re using some typeof repository pattern implementation where a long-running context isn’tavailable.

Why the EF Can Benefit from Second-Level Caching

Whyshould you care about having access to a representation of the originalstate across processes? One of the great benefits of the EF is itsability to automatically generate database persistence commands(inserts, updates and deletes) based on the state information found inthe context. But if that state information is unavailable, the EF hasnothing to do when it’s time to call SaveChanges. Developers, includingmyself, have been trying to work around this limitation since the EF wasfirst introduced in 2006.

Second-level caches are instrumental insolving this type of problem. These caches exist outside of thetransaction—often outside of the application—and therefore are availableto any context instance. And second-level caching is a commonly usedcoding pattern for caching data for various uses.

Rather than write your own way of caching data, there are caching mechanisms available such as memcached (memcached.org),and even caching support in Microsoft AppFabric (available in WindowsServer as well as in Windows Azure). These services provide theinfrastructure for caching so you don’t have to sweat the details. Andthey expose APIs that make it easy for programmers to read, store andexpire data in the cache.

If you have a highly transactionalsystem that can benefit from a cache to avoid repeatedly hitting thedatabase for commonly queried data, you’ll also find yourself lookingfor a caching solution. This is a great way to enhance performance whenusing data that’s modified infrequently—for example, reference data or alist of players on a sports team.

Figure 1 shows the first-level cache maintained within an EF context, as well as various contexts accessing a common second-level cache.


Figure 1 First-Level Caching Happens Inside a Transactional Context and Second-Level Caching Is External

Using the EF Caching Provider to Add Second-Level Caching

Designingthe logic for reading, storing and expiring cache data takes a bit ofwork. You’d want to do this when working with the EF when you’requerying for data or storing data. When executing a query against thecontext, you’ll want to first see if that data exists in the cache soyou don’t have to waste resources on a database call. When updating databy using a context’s SaveChanges method, you’ll want to expire andpossibly refresh the data in the cache. And working with the cache ismore complex than simply reading and writing data. There are plenty ofother considerations to take into account. There’s an in-depth articlefrom the Association for Computing Machinery (ACM) that lays out thecomplexities of ORM caching, “Exposing the ORM Cache: Familiarity withORM caching issues can help prevent performance problems and bugs” (bit.ly/k5bzd1). I won’t attempt to repeat the pros, cons and hot points outlined in the article. Instead, I’ll focus on implementation.

TheEF doesn’t have built-in support for working with second-level caches.That functionality would make the most sense as part of theObjectContext and DbContext logic when they’re about to interact withthe database. But implementing the caching while taking into account thevarious issues discussed in the ACM article is non-trivial, especiallywith the lack of obvious extensibility points in the EF. One of thefeatures that’s frequently highlighted as a big difference between theEF and NHibernate is the fact that NHibernate has built-in support forimplementing second-level caching.

But all is not lost! Enter the EF providers and the brainy Jarek Kowalski (j.mp/jkefblog), former member of the EF team.

TheEF provider model is the key to how the EF is able to support anyrelational database—as long as there’s a provider written for thatdatabase that includes EF support. There are a slew of third-partyproviders allowing you to use the EF with a growing array of databases(SQL Server, Oracle, Sybase, MySQL and Firebird are just some examples).

Inthe EF, the ObjectContext talks to the lower-level EntityClient API,which communicates with the database provider to work outdatabase-specific commands and then interacts with the database. Whenthe database is returning data (as a result of queries or commands thatupdate store-generated values), the path is reversed, as shown in Figure 2 .


Figure 2 Flow from the EF Context Through an ADO.NET Provider to Get to the Database

Thespot where the provider lives is pliable, enabling you to injectadditional providers between the EntityClient and the database. Theseare referred to as provider wrappers. You can learn more about writingADO.NET providers for the EF or other types of providers on the EF teamblog post, “Writing an EF-Enabled ADO.NET Provider” (bit.ly/etavcJ).

Afew years ago, Kowalski used his deep knowledge of the EF providers towrite a provider that captures messages between the Entity Client andthe ADO.NET provider of choice (whether that’s SqlClient, MySQLConnector or another) and injects logic to interact with a second-levelcaching mechanism. The wrapper is extensible. It provides underlyinglogic for any type of caching solution, but then you need to implement aclass that bridges between this wrapper and the caching solution. Theprovider sample works with an in-memory cache, and the solution has asample adapter to use “Velocity,” the code name for Microsoftdistributed caching. Velocity eventually became the caching mechanism inthe Microsoft Windows Server AppFabric.

Building an EFCachingProvider Adapter for Windows Server AppFabric

The EFCachingProvider was recently updated for the EF 4. The Tracing and Caching Provider Wrappers for Entity Framework page (bit.ly/zlpIb)includes great samples and documentation, so there’s no need to repeatall of that here. However, the Velocity adapter was removed and therewas no replacement to use the caching in AppFabric.

AppFabriclives in two places: Windows Server and Windows Azure. I’ve recreatedthe provider class that worked with Velocity so that it will now workwith the caching in Windows Server AppFabric, and I’ll share how toaccomplish this yourself.

First, be sure you’ve installed the EF provider wrappers from bit.ly/zlpIb.I’ve worked in the example solution, which contains the projects forthe provider wrappers (EFCachingProvider, EFTracingProvider andEFProviderWrapperToolkit). There are also some client projects that testout the final caching functionality. The InMemoryCache provider is thedefault caching strategy and is built into the EFCachingProvider. Alsohighlighted in that project in Figure 3 is ICache.cs.The InMemoryCache inherits from this, and so should any other adapteryou want to create to use other caching mechanisms—such as theAppFabricCache adapter that I created.


Figure 3 ICache and InMemoryCache Are Core Classes in the EFCachingProvider

Inorder to develop for AppFabric, you’ll need the AppFabric cache clientassemblies and a minimal installation of AppFabric on your developmentmachine. See the MSDN Library topic, “Walkthrough: Deploying WindowsServer AppFabric in a Single-Node Development Environment,” at bit.ly/lwsolW, for help with this task. Be warned that it’s a bit involved. I’ve done it myself on two development machines.

Nowyou can create an adapter for Windows Server AppFabric. This is veryclose to the original Velocity3 adapter, but I did spend a bit of timelearning how to work with the AppFabric client API in order to get thesestars aligned. If you’re creating an adapter for a different cachingmechanism, you’ll need to adjust accordingly to that cache’s API.

Anothercritical piece to the puzzle is to extend your ObjectContext class. Ihope to try this out with an EF 4.1 DbContext soon, but this willnecessitate modifying the underlying logic of the EFCachingProvider.

Youuse the same code to extend the context regardless of whichimplementation of ICache you’re working with. The extended classinherits from your context class (which, in turn, inherits fromObjectContext) and then exposes some extension methods from theEFCachingProvider. These extension methods enable the context tointeract directly (and automatically) with the caching provider. Figure 4 shows an example in the solution that extends NorthwindEFEntities, a context for a model built against the Northwind database.

Figure 4 Extending an Existing Class that Inherits from ObjectContext, NorthwindEFEntities

  1. using EFCachingProvider;
  2. using EFCachingProvider.Caching;
  3. using EFProviderWrapperToolkit;
  4.  
  5. namespace NorthwindModelDbFirst
  6. {
  7.   public partial class ExtendedNorthwindEntities : NorthwindEFEntities
  8.   {
  9.  
  10.   public ExtendedNorthwindEntities()
  11.     : this("name=NorthwindEFEntities")
  12.   {
  13.   }
  14.  
  15.   public ExtendedNorthwindEntities(string connectionString)
  16.     : base(EntityConnectionWrapperUtils.
  17.     CreateEntityConnectionWithWrappers(
  18.     connectionString,"EFCachingProvider"))
  19.   {
  20.   }
  21.   
  22.   private EFCachingConnection CachingConnection
  23.   {
  24.     get { return this.UnwrapConnection(); }
  25.   }
  26.  
  27.   public ICache Cache
  28.   {
  29.     get { return CachingConnection.Cache; }
  30.     set { CachingConnection.Cache = value; }
  31.   }
  32.  
  33.   public CachingPolicy CachingPolicy
  34.   {
  35.     get { return CachingConnection.CachingPolicy; }
  36.     set { CachingConnection.CachingPolicy = value; }
  37.   }
  38.  
  39.   #endregion
  40.   }
  41. }

I’veadded a Class Library project to the solution calledEFAppFabricCacheAdapter. That project needs references to theEFCachingProvider as well as two of the AppFabric assemblies:Microsoft.ApplicationServer.Caching.Core andMicrosoft.ApplicationServer.Caching.Client. Figure 5 shows my adapter class, AppFabricCache, which emulates the original VelocityCache.

Figure 5 Adapter that Interacts with Windows Server AppFabric

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Security.Cryptography;
  5. using System.Text;
  6. using EFCachingProvider.Caching;
  7. using Microsoft.ApplicationServer.Caching;
  8.  
  9. namespace EFAppFabricCacheAdapter
  10. {
  11.   public class AppFabricCache : ICache
  12.   {
  13.     private DataCache _cache;
  14.  
  15.     public AppFabricCache(DataCache cache)
  16.     {
  17.       _cache = cache;
  18.     }
  19.  
  20.     public bool GetItem(string key, out object value)
  21.     {
  22.       key = GetCacheKey(key);
  23.       value = _cache.Get(key);
  24.  
  25.       return value != null;
  26.     }
  27.  
  28.     public void PutItem(string key, object value,
  29.       IEnumerable dependentEntitySets,
  30.       TimeSpan slidingExpiration, DateTime absoluteExpiration)
  31.     {
  32.       key = GetCacheKey(key);
  33.       _cache.Put(key, value, absoluteExpiration - DateTime.Now,
  34.         dependentEntitySets.Select(c => new DataCacheTag(c)).ToList());
  35.  
  36.       foreach (var dep in dependentEntitySets)
  37.       {
  38.         CreateRegionIfNeeded(dep);
  39.         _cache.Put(key, " ", dep);
  40.       }
  41.  
  42.     }
  43.  
  44.     public void InvalidateSets(IEnumerable entitySets)
  45.     {
  46.       // Go through the list of objects in each of the sets.
  47.       foreach (var dep in entitySets)
  48.       {
  49.         foreach (var val in _cache.GetObjectsInRegion(dep))
  50.         {
  51.           _cache.Remove(val.Key);
  52.         }
  53.       }
  54.     }
  55.  
  56.     public void InvalidateItem(string key)
  57.     {
  58.       key = GetCacheKey(key);
  59.  
  60.       DataCacheItem item = _cache.GetCacheItem(key);
  61.       _cache.Remove(key);
  62.  
  63.       foreach (var tag in item.Tags)
  64.       {
  65.         _cache.Remove(key, tag.ToString());
  66.       }
  67.     }
  68.  
  69.     // Creates a hash of the query to store as the key 
  70.     private static string GetCacheKey(string query)
  71.     {
  72.       byte[] bytes = Encoding.UTF8.GetBytes(query);
  73.       string hashString = Convert
  74.         .ToBase64String(MD5.Create().ComputeHash(bytes));
  75.       return hashString;
  76.     }
  77.  
  78.     private void CreateRegionIfNeeded(string regionName)
  79.     {
  80.       try
  81.       {
  82.         _cache.CreateRegion(regionName);
  83.       }
  84.       catch (DataCacheException de)
  85.       {
  86.         if (de.ErrorCode != DataCacheErrorCode.RegionAlreadyExists)
  87.         {
  88.           throw;
  89.         }
  90.       }
  91.     }
  92.   }
  93. }

Theclass uses the Microsoft.ApplicationServer.Caching.DataCache to fulfillthe required implementation of ICache. Most notable is the use ofAppFabric regions in the PutItem and InvalidateSets. When a new item isstored in the cache, the adapter also adds it to a region, or group,that’s defined by all entities in a particular entity set. In otherwords, if you have a model with Customer, Order and LineItem, then yourCustomer instances will be cached in a Customers region, Order instancesin a region called Orders and so on. When a particular item isinvalidated, rather than looking for that particular item andinvalidating it, all of the items in the region are invalidated.

It’sthis use of regions that caused me to set aside my attempt to implementWindows Azure AppFabric support. At the time that I’m writing thiscolumn, Windows Azure AppFabric is still a CTP and doesn’t supportregions. Because underlying code of the caching provider is dependent onthe regions and these methods, I was unable to easily create a providerimplementation that would just work for Windows Azure AppFabric. Youcan, of course, call the InvalidateItem method yourself, but that wouldeliminate the benefit of the automated behavior of the provider.

Using the AppFabric Cache Adapter

There’sone last project to add, and that’s the project that exercises theadapter. The EFCachingProvider demo that’s part of the original solutionuses a console app with a number of methods to test out the caching:SimpleCachingDemo, CacheInvalidationDemo andNonDeterministicQueryCachingDemo. In my added console app for testingout the AppFabricCache, you can use the same three methods with the sameimplementation. What’s interesting about this test is the code forinstantiating and configuring the AppFabricCache that will be used bythe extended context in those three methods.

An AppFabricDataCache needs to be created by first identifying an AppFabric serverEndpoint, then using its DataCacheFactory to create the DataCache.Here’s the code to do that:

  1. private static ICache CreateAppFabricCache()
  2. {
  3.   var server = new List();
  4.   server.Add(new DataCacheServerEndpoint("localhost", 22233));
  5.   var conf = new DataCacheFactoryConfiguration();
  6.   conf.Servers = server;
  7.   DataCacheFactory fac = new DataCacheFactory(conf);
  8.   return new AppFabricCache(fac.GetDefaultCache());
  9. }

Notethat I’m hardcoding the Endpoint details for the simplicity of thisexample, but you probably won’t want do that in a productionapplication. Once you’ve created the DataCache, you then use it toinstantiate an AppFabricCache.

With this cache in hand, I can pass it into the EFCachingProvider and apply configurations, such as a DefaultCachingPolicy:

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

Finally,when I instantiate my extended context, it will automatically look for acaching provider, finding the AppFabricCache instance that I just setas the default. This will cause caching to be active using whateverconfiguration settings you applied. All you need to do is go about yourbusiness with the context—querying, working with objects and callingSaveChanges. Thanks to the extension methods that bind your context tothe EFProviderCache and the DataCache instance that you attached, all ofthe caching will happen automatically in the background. Note that theCacheAll CachingPolicy is fine for demos, but you should consider using amore fine-tuned policy so that you aren’t caching data unnecessarily.

TheEFProviderCache has been designed with extensibility in mind. As longas the target caching mechanism you want to use supports the standardimplementations to store, retrieve, expire and group data, you should beable to follow the pattern of this adapter to provide a shared cachefor your applications that use the EF for data access.


Julie Lerman   isa Microsoft MVP, .NET mentor and consultant who lives in the hills ofVermont. You can find her presenting on data access and other Microsoft.NET topics at user groups and conferences around the world. She blogsat thedatafarm.com/blogand is the author of the highly acclaimed book, “Programming EntityFramework” (O’Reilly Media, 2010). Follow her on Twitter at twitter.com/julielerman.

Thanks to the following technical experts for reviewing this article:  Jarek Kowalski and   S