Skip to content

Caching

Jeff Treuting edited this page Jun 29, 2013 · 8 revisions

Caching

Overview

SharpRepository has two separate concepts that come into play when using caching: Providers and Stategies. Caching Providers are the technology that is going to do that actual caching like Memcached, Redis or System.Runtime.Caching, while a Caching Strategy decides how and what to cache. SharpRepository has multiple providers and strategies already built with the ability to add your own easily if needed.

Caching Providers

The default Caching Provider is the InMemoryCachingProvider and it comes with the base SharpRepository package. This uses the System.Runtime.Caching implementation and is great for simple single server caching needs.

If you are in a multiple server environment or cloud hosting then you can take advantage of the other providers we offer that are available via NuGet as separate packages. We currently offer a Memcached provider as well as a [http://nuget.org/packages/SharpRepository.Caching.Redis/](Redis provider). The Memcached provider is compatible with Amazon's ElastiCache since it is protocol-compliant with Memcached.

Caching Strategies

There are currently two caching strategies and they are both included in the default SharpRepository library: StandardCachingStrategy and TimeoutCachingStrategy.

StandardCachingStrategy

The standard caching strategy uses a combination of Write-Through caching and Generational caching. Write-Through caching basically says, when you write to the database, write to cache as well. So this is done when you call Add, Update or Delete. Generational caching is a conservative caching approach for query results (i.e. GetAll, Find and FindAll calls) that only keeps a query result in cache until the repository that it is on has a change to the database. So when you Add, Update or Delete using the repository, the queries for that repository are no longer valid. This is a simple conservative approach that can show tremendous benefit in an environment with more reads than writes (which is actually very common). Here is some more information on Write-Through and Generational Caching.

TimeoutCachingStrategy

The timeout caching strategy caches items for a predefined amount of time. There is no fancy logic, just keep this in cache for X seconds no matter what.

Usage

Each repository type has an optional constructor parameter that accepts an ICachingStrategy. If none is provided, then caching is not used for that repository. Here is some code that should give you a good idea of how to use it.

// standard caching strategy with default InMemory provider used
var repos = new Ef5Repository<Order>(new TestObjectEntities("connection string here"),
                                        new StandardCachingStrategy<Order>());

// standard caching strategy using Memcached provider
//  since we use the Enyim.Caching.Memcached library, this default constructor uses the default configuration file for memcached
repos = new Ef5Repository<Order>(new TestObjectEntities("connection string here"),
                                        new StandardCachingStrategy<Order>(new MemcachedCachingProvider()));

// define the connection parameters for Memcached here instead of in the  configuration file
repos = new Ef5Repository<Order>(new TestObjectEntities("connection string here"),
                                    new StandardCachingStrategy<Order>(new MemcachedCachingProvider("192.168.0.1", 11211, "username", "password")));

// you can also disable the write-through part of the generational part if needed
repos = new Ef5Repository<Order>(new TestObjectEntities("connection string here"),
                                        new StandardCachingStrategy<Order>() { GenerationalCachingEnabled = false});

// using the timeout strategy with a 5 minute timeout, defaults to InMemory provider
repos = new Ef5Repository<Order>(new TestObjectEntities("connection string here"),
                                        new TimeoutCachingStrategy<Order>(300));

// same but using Memcached instead
repos = new Ef5Repository<Order>(new TestObjectEntities("connection string here"),
                                        new TimeoutCachingStrategy<Order>(300, new MemcachedCachingProvider()));

Hopefully this gives you an idea of the different combinations you can do with strategies and providers. Each strategy and provider has slightly different options that are specific to them, so you might need to reference the source code, or just follow intellisense to get an idea of what is needed for each.

Here are a few misc. things that can be done involving caching with your repository. You can check to see if cache was used after a query was run, and disable caching for specific queries as needed.

var items = repos.GetAll();

// see if the cache was used for this last query
var wasCacheUsed = repos.CacheUsed;

// disable cache for now
using (repos.DisableCaching())
{
    // make sure to hit the database and not use cache on this query
    items = repos.GetAll();
}

Configuration

If you are using the configuration files for loading your repositories via RepositoryFactory or inheriting from ConfigurationBasedRepository (see the configuration page for more info), here is some info on the caching configuration parts.

When you install SharpRepository via NuGet it will put a basic configuration in place in your Web.config or App.config files for you so that you just need to customize it. Here is an example using Entity Framework with standard caching and the default InMemory provider.

  <sharpRepository>
	<repositories>
	  <repository name="ef5Repository" connectionString="MyEntities" cachingStrategy="standardCachingStrategy" dbContextType="Fairway.Data.MyEntities, Fairway.Data" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository" />
	</repositories>
	<cachingProviders></cachingProviders>
	<cachingStrategies>
	  <cachingStrategy name="standardCachingStrategy" generational="true" writeThrough="true" factory="SharpRepository.Repository.Caching.StandardConfigCachingStrategyFactory, SharpRepository.Repository" />
	  <cachingStrategy name="timeoutCachingStrategy" timeout="300" factory="SharpRepository.Repository.Caching.TimeoutConfigCachingStrategyFactory, SharpRepository.Repository" />
	  <cachingStrategy name="noCaching" factory="SharpRepository.Repository.Caching.NoCachingConfigCachingStrategyFactory, SharpRepository.Repository" />
	</cachingStrategies>
  </sharpRepository>

On the repository element, we have defined a cachingStrategy attribute that uses the name of the cachingStrategy element we want to use for this repository. This allows us to define multiple repositories that point to different cachingStrategies, this allowing for different implementations within our code.

Since no caching provider is defined we are using the default InMemory provider. If we wanted to use a different one, we would set the cachingProvider attribute on the repository element using the name of the cachingProvider we define.

You see that is empty by default. If you install the SharpRepository.Caching.Memcached package via NuGet it will install that cachingProvider element in your configuration file for you that you can then reference.

Clearing Cache

When using caching you may need to clear out the cache when making changes to the database that were not done through the repository. This is especially true when using the write-through caching of the StandardCachingStrategy. Since it keeps an individual entity in cache, if you edit or delete an entity by going directly at the database, SharpRepository will not know that that entity has been updated or deleted and will still have it in cache and therefore serve it up stale data when requested.

If you need to clear out a cache for a specific implementation of a repository (e.g. the repository handling the Contact entity) then all you need to do is call the ClearCache() on that repository like so:

repository.ClearCache();

Now, if you'd like to clear out the cache across all the repository types then things are a little more complicated (but not much).

A little background first. SharpRepository does not keep track of all the cache keys that are being used, and therefore can't just loop through all of the keys in cache and remove the items one by one. So we use a little trick. Each cache key is prefixed with a counter value that we can increment in order to essentially "clear out" the previous cached items, in the sense that they won't be found and will eventually be cleaned out by the caching provider as they get old. I mention this because this presents an issue of where to store that counter. If you are using caching on just a single server environment, then we can simply use a static class to manage this without a problem. If you are using a distributed cache like memcached because you are using SharpRepository in the cloud or on a server farm, then we can't do that since the static class will be different per server. Therefore, we must use the cache itself to store this counter.

Now why did I tell you all that? Because in order to have the ability to clear out all of the cached items, instead of going through your repositories one by one and calling ClearCache() (which you can do if you'd like), you will need to let SharpRepository know which type of environment you are in. You would typically set the CachePrefixManager in your Global.asax, or App_Start code. Here is how you do that:

// we are on a single server 
Cache.CachePrefixManager = new SingleServerCachingPrefixManager();

// if in a distributed environment, then we must also tell SharpRepository what caching provider to use
Cache.CachePrefixManager = new MultiServerCachePrefixManager(new MemcachedCachingProvider());

// now that SharpRepository knows how to handle the caching prefix, we can call the ClearAll() without getting an exception
Cache.ClearAll();

Note: if you try to call Cache.ClearAll() without first setting the CachePrefixManager you will receive an exception letting you know that you need to configure it first.

Partitioning

When using a generational caching approach, as the StandardCachingStrategy does for queries, the cache is "cleared" whenever there is an update to the database table for that repository. This means that the caching will have a benefit when there are more reads than writes. Partitioning allows you to be a little bit smarter about when the cache gets "cleared" out.

Let's say that you have a blog where each post has zero or more comments attached to it. The Comment table has a PostId foreign key that holds the relationship back to the Post table. With a normal StandardCachingStrategy, each time a new comment is added to the Comment table, the repository for Comments is cleared out and the next query will always hit the database. By partitioning the cache on the PostId, we can allow the queries that get all of the Comments for a particular Post to live in cache longer and only be cleared out when a new comment on that particular Post is added.

Here is some code to show how to do this:

// use the basic standard caching
var commentRepository = new Ef5Repository<Comment, int>(dbContext, new StandardCachingStrategy<Comment, int>());

// get all comments for post 1
var comments = commentRepository.FindAll(x => x.PostId == 1);

// adding a comment to post 2, will clear out the cached results of the previous query
commentRepository.Add(new Comment() { PostId = 2, Comment = "some comment" });

// this will not be in cache and will do a query against the backend
comments = commentRepository.FindAll(x => x.PostId == 1);

// commentRepository.CacheUsed will equal false

// since we are always getting comments based on the Post,
//	let's be smarter and setup a caching partition on the PostId
//	the third generic type is the type of the partition property
commentRepository = new Ef5Repository<Comment, int>(dbContext, new StandardCachingStrategyWithPartition<Comment, int, int>());

// get all comments for post 1
comments = commentRepository.FindAll(x => x.PostId == 1);

// adding a comment to post 2, will not clear out the cached results of the previous query since it is for a different partition
commentRepository.Add(new Comment() { PostId = 2, Comment = "some comment" });

// this will be in cache still
comments = commentRepository.FindAll(x => x.PostId == 1);

// commentRepository.CacheUsed will equal true

Warnings

When using caching you need to be aware of some things and understand what is going on. When caching an entity that has foreign key relationships to other entities, you need to be careful about accessing the cached foreign key entities as they may be stale. As an example, let's say you have a User entity which has multiple Address entities attached to it. When your User entity gets cached, it will also cached the Addresses for that entity since there is an Addresses property on the User object.

If you update an Address object through the your IRepository<Address, int> repository, it won't automatically update the cache for the User object that it is attached to. Therefore, if you get the User object from cache, the Address entities may be the old version that was cached originally.

We are hoping to solve this issue in the future, but for now, be careful, and when in doubt you can always go to the Address directly, or disable caching for your call to get the User like so:

using (repos.DisableCaching())
{
    repos.Get(userId);
}
Clone this wiki locally
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy