This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.
- Version History
- NuGets
- Basic Usage
- Cache Types And Policies
- Cache Management
- AOP Caching
- Extending With Your Own Classes
(available at nuget.org)
The core - ICache
, CacheManager
, LayeredCache
:
PubComp.Caching.Core
InMemoryCache
based on System.Runtime.Caching.ObjectCache
:
PubComp.Caching.SystemRuntime
RedisCache
based on StackExchange.Redis
:
PubComp.Caching.RedisCaching
AOP (Aspect Oriented Programming) caching based on PostSharp
:
PubComp.Caching.AopCaching
You can access the cache directly via its interface:
ICache cache = new RedisCache("myRemoteCache", new RedisCachePolicy());
MyData data;
if (!cache.TryGet("myKey", out data))
{
data = FallbackMethodToRun();
cache.Set("myKey", data);
}
or
MyData data = cache.Get("myKey", () => FallbackMethodToRun());
this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).
or use a PostSharp based aspect to wrap a method with cache:
[Cache]
MyData AMethodThatNeedsCaching(string parameter1, int parameter2)
{
// Some long running operation
}
calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:
var key = "{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyData result = myCache.Get(key, () => MyMethod("one", 2));
You can clear a specific item from the cache:
myCache.Clear("myKey");
or clear the entire cache (all items):
myCache.ClearAll();
If you have multiple caches, clearing one will NOT affect the other e.g.
ICache cache1 = new InMemoryCache("myLocalCache", new InMemoryPolicy());
ICache cache2 = new InMemoryCache("myLocalCache", new InMemoryPolicy());
cache1.ClearAll(); // Only cache1 is cleared
You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).
When you create a cache, you pass a name and the policy.
You can use the CacheManager
to get a cache with a specific name e.g.
var cache = CacheManager.GetCache("myLocalCache");
A cache can be created programmatically:
var cache = new InMemoryCache("myOtherLocalCache", new InMemoryPolicy());
or received from the CacheManager
which can be configured via code:
CacheManager.SetCache("noCache*", new NoCache("noCache"));
var cache = CacheManager.GetCache("noCache1");
or via config file:
<configSections>
<sectionGroup name="PubComp">
<section
name="CacheConfig"
type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
allowLocation="true"
allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or " in the policy JSON -->
<!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy -->
<!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration -->
<!-- You can create your own cache type, just direct the config to your assembly and class -->
<add name="localCache" assembly="PubComp.Caching.SystemRuntime" type="PubComp.Caching.SystemRuntime.InMemoryCache"
policy="{'ExpirationFromAdd':'01:00:00'}" />
<add name="localCacheLRU" assembly="PubComp.Caching.SystemRuntime" type="PubComp.Caching.SystemRuntime.InMemoryCache"
policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{"ExpirationFromAdd":"'01:00:00"}" would work too -->
</CacheConfig>
</PubComp>
You can request a cache, by name, from the CacheManager
:
var cache = CacheManager.GetCache("localCacheLRU");
or via AOP (Aspect Oriented Programming)...
Wrap a method with cache, so that the underlaying method is only called on cache misses:
[Cache("localCache")]
private List<Item> MethodToCache()
{
// data retrieval goes here
}
(*) When using AOP, the aspect only requests the cache from CacheManager
once, for optimization, therefore changing the registeration of the cache (using SetCache
won't have an effect later on).
You can also cache an IList of items, each time running the underlaying method only for the missing keys:
Using AOP, you can easily wrap a method with a lazy-loading cache:
[Cache("localCache")]
private List<Item> MethodToCache()
{
// data retrieval goes here
}
The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.
so that the following methods will not have the same key:
[Cache("localCache")]
private List<Item> MethodToCache(string x)
{
// data retrieval goes here
}
[Cache("localCache")]
private List<Item> MethodToCache(int x)
{
// data retrieval goes here
}
[Cache("localCache")]
private List<Item> MethodToCache(byte x)
{
// data retrieval goes here
}
You can get the key used by the Cache
aspect (CacheAttribute
), in order to enable manual clearing of the data like so:
var key = CacheKey.GetKey(() => service.MethodToCache("parameterValue"));
and then clear as usual:
CacheManager.GetCache("localCache").Clear(key);
You can use the CacheList
aspect (CacheListAttribute
) to cache data item by item.
When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).
Example:
// Both IDs parameter and return type have to be of type IList<>
[CacheList(typeof(MockDataKeyConverter))]
public IList<MockData> GetItems(IList<string> keys)
{
using (var context = new MyDbContext())
{
return content.MyData.Where(d => keys.Contains(d.Id)).ToList();
}
}
// Example data type
public class MockData
{
public string Id { get; set; }
public string Value { get; set; }
}
// This class enables the aspect, given a result what its key (ID) is
public class MockDataKeyConverter : IDataKeyConverter<string, MockData>
{
public string GetKey(MockData data)
{
return data.Id;
}
}
If the keys are not the first parameter of the method, use keyParameterNumber
to instruct the aspect which parameter to use (0-based):
[CacheList(typeof(MockDataKeyConverter), keyParameterNumber = 1)]
public IList<MockData> GetItems(string parameter0, IList<string> keys)
{
using (var context = new MyDbContext())
{
return content.MyData.Where(d => keys.Contains(d.Id)).ToList();
}
}
You can instruct the aspects (Cache
and CacheList
) to ignore a specific parameter when generating the cache-key
using the DoNotIncludeInCacheKey
attribute (DoNotIncludeInCacheKeyAttribute
):
[Cache]
public string MethodToCache1(int id, [DoNotIncludeInCacheKey]object obj)
{
}
You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:
- YourCache - the actual cache's code. Must implement
ICache
- YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from
CacheConfig
. - YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.
You will then be able to configure usage of your cache like so:
<PubComp>
<CacheConfig>
<add name="cacheName" assembly="YourAssembly" type="YourType"
policy="JSON goes here" />
</CacheConfig>
</PubComp>