logo

Caching

Caching allows you to increase the application’s performance.

In ELMA, there are several types of cache:

  • Global cache (ICacheService), used to write the data between web queries. Use it for caching data that doesn’t have to be up-to-date at this particular moment. If a server farm is used, the data stored in this cache is available and is the same for any ELMA server.
  • Server cache (IMemoryCacheService), used to store data in the memory of one ELMA server instance. Use it to write data that does not change during the server lifespan (for example, reflection data).
    • Metadata cache (Server cache modification, based on IMemoryCacheService) is a special metadata object storage, which is cleared when publishing. Metadata cache is intended for storing metadata, metadata-based results, and reflection on types, which may change when publishing.
  • Complex cache (IComplexCacheService) - is intended for storing data in the memory of a single ELMA server instance with a guaranteed reset in case of using a server farm and changing data on one of the servers.
  • Complex cache for storing rarely changing data (IUnionComplexCacheService) is intended for storing data in the memory of a single ELMA server instance, occasionally addressing the distributed cache for information updates. It should be used to cache data that change rarely. For all these data, a single check for changes request is executed, which reduces the distributed cache load when using a server farm.
  • Context cache (IContextBoundVariableService), used to write data within one web-query. Use it to prevent any repeated calculations of data that does not affect the logic if updated within one web query.

Global Cache

ELMA has its own API for data caching, please do not use anything else, like ASP.NET Cache memcached etc.

The operation of this cache depends on the environment where the application is run. If it is run on one server, the data is stored in the server’s memory. In the case of a server farm (cluster), it is stored in the distributed cache (AppFabric cache service).

That is why you always have to consider the distributed cache when using this type of cache.

ICacheService

ICacheService is registered in the IoC container and is available for all server components of the application. The exact ICacheService execution depends on the environment where the application is run: an independent application on one IIS server, IIS based cluster farm or Windows Azure.

/// <summary>
/// Interface for operating cache
/// </summary>
public interface ICacheService
{
/// <summary>
/// Add or change cache item according to key, specifying dependency and caching time 
/// ATTENTION! This method might be deleted in the future since distributed cache does not support dependencies (windows appfabric cache, windows azure web cache etc.)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="dependencyKey"></param>
/// <param name="cacheDuration"></param>
void Insert<T>(string key, T value, string dependencyKey, TimeSpan cacheDuration);
 
/// <summary>
///  Add or change cache item according to key, specifying caching time 
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="cacheDuration"></param>
void Insert<T>(string key, T value, TimeSpan cacheDuration);
 
/// <summary>
///  Add or change cache item according to key with dependency
///  ATTENTION! This method might be deleted in the future since distributed cache does not support dependencies (windows appfabric cache, windows azure web cache and etc.)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="dependencyKey"></param>
void Insert<T>(string key, T value, string dependencyKey);
 
/// <summary>
///  Add or change cache item according to key
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
void Insert<T>(string key, T value);
 
/// <summary>
/// Check the item in cache according to key
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool Contains(string key);
 
/// <summary>
/// Get item from cache according to key
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
T Get<T>(string key);
 
/// <summary>
/// Delete item from cache according to key 
/// </summary>
/// <param name="key"></param>
void Remove(string key);
}

Caching method return values

The CacheAttribute attribute allows you to cache the return values of methods called from components. When the method is called, it composes a key based on the values of the sent arguments and uses ICacheService to check if the item is stored in the cache. If it is, the value is returned from cache. If not, the method’s body is called, and the result is put into the cache.

A method marked with the CacheAttribute attribute must comply with the following conditions: 

  • It is declared with the virtual keyword
  • The return value can be serialized (marked with the Serializable attribute)
  • The method’s arguments must either be primitive or implement theIIdentified interface 

E.g., when loading data through URL, it is cached for 5 seconds:

using System.Net;
using System.Xml;
using EleWise.ELMA.Cache.Attributes;
using EleWise.ELMA.ComponentModel;
 
[Service] 
public class FeedLoader
{
 
/// <summary>
/// Load data in xml
/// </summary>
/// <param name="url">address</param>
/// <returns></returns> 
[Cache] 
public virtual XmlDocument Load(string url)
{
    var req = WebRequest.Create(url);
    var response = req.GetResponse();
    var stream = response.GetResponseStream();
    var xmlDocument = new XmlDocument();
    xmlDocument.Load(stream);
    return xmlDocument;
}
}

Server cache

Compared to the global cache, an implementation of this cache type always stores its data in the server memory. If using a server farm, the data between nodes are not synchronized, therefore you can store only the information that is not changed over time in this cache type.

IMemoryCacheService

IMemoryCacheService is registered in the IoC container and is available on all the server components of the application.

/// <summary>
    /// Memory cache service
    /// </summary>
    public interface IMemoryCacheService
    {

        /// <summary>
        /// Try and get a value from the cache
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key</param>
        /// <param name="result">Value</param>
        /// <returns></returns>
        bool TryGetValue<T>(string key, out T result);

        /// <summary>
        /// Check up an item in the cache by a key
        /// </summary>
        /// <param name="key">Key</param>
        /// <returns>True, if there is an item in the cache</returns>
        bool Contains(string key);

        /// <summary>
        /// Add or change a cache item by a key
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key</param>
        /// <param name="value">Value</param>
        void Insert<T>(string key, T value);

        /// <summary>
        /// Add or change a cache item by a key
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key</param>
        /// <param name="value">Value</param>
        /// <param name="timeout">Value storage timeout</param>
        void Insert<T>(string key, T value, TimeSpan timeout);

        /// <summary>
        /// Remove an item from cache by a key
        /// </summary>
        /// <param name="key">Key</param>
        void Remove(string key);

        /// <summary>
        /// Remove when a substring enters a key
        /// </summary>
        /// <param name="subkey"></param>
        void RemoveBySubkey(string subkey);
    }

Metadata cache

Attention!
The information below applies to ELMA 3.13.7 and higher.

Cache is based on caching functions. You create a special function, having input a lambda expression with parameters. The expression must perform certain operations with input data and return a result. As an output, you get a function that saves its calculation results in the metadata cache, and you can use it in your code.

Caching function principles: if the function has already been called with these parameters, a result from the cache will be returned, otherwise, it calculates, saves to cache and returns a result.

The methods of the MetadataLoader class are used for the metadata cache:

public class MetadataLoader
{

    ...

    /// <summary>
    /// Create an unconditionally caching function
    /// </summary>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <returns>Caching function</returns>
    public static Func<TResult> UseCachingForFunc<TResult>(Expression<Func<TResult>> extractValueExpr) { ... }

    /// <summary>
    /// Create an unconditionally caching function and return a method for clearing the cache of its values
    /// </summary>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <param name="clearAction">Cache clearing method</param>
    /// <returns>Caching function</returns>
    public static Func<TResult> UseCachingForFunc<TResult>(Expression<Func<TResult>> extractValueExpr, out Action clearAction) { ... }

    /// <summary>
    /// Create an unconditionally caching function
    /// </summary>
    /// <typeparam name="T1">Type of the first function parameter</typeparam>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <returns>Caching function</returns>
    public static Func<T1, TResult> UseCachingForFunc<T1, TResult>(Expression<Func<T1, TResult>> extractValueExpr) { ... }

    /// <summary>
    /// Create a conditionally caching function. The condition (whether to cache or not) is controlled by the function expression.
    /// </summary>
    /// <typeparam name="T1">Type of the first function parameter</typeparam>
    /// <typeparam name="TResult">TYpe of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <returns>Caching function</returns>
    public static Func<T1, TResult> UseConditionalCachingForFunc<T1, TResult>(Expression<Func<T1, Tuple<TResult, bool>>> extractValueExpr) { ... }

    /// <summary>
    /// Create a conditionally caching function and return a method for clearing the cache of its values
    /// </summary>
    /// <typeparam name="T1">Type of the first function parameter</typeparam>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <param name="clearAction">Cache clearing method</param>
    /// <returns>Caching function</returns>
    public static Func<T1, TResult> UseCachingForFunc<T1, TResult>(Expression<Func<T1, TResult>> extractValueExpr, out Action clearAction) { ... }

    /// <summary>
    /// Create an unconditionally caching function
    /// </summary>
    /// <typeparam name="T1">Type of the first function parameter</typeparam>
    /// <typeparam name="T2">Type of the second function parameter</typeparam>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <returns>Caching function</returns>
    public static Func<T1, T2, TResult> UseCachingForFunc<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> extractValueExpr) { ... }

    /// <summary>
    /// Create an unconditionally caching function and return a method for clearing the cache of its values
    /// </summary>
    /// <typeparam name="T1">Type of the first function parameter</typeparam>
    /// <typeparam name="T2">Type of the second function parameter</typeparam>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <param name="clearAction">Cache clearing method</param>
    /// <returns>Caching function</returns>
    public static Func<T1, T2, TResult> UseCachingForFunc<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> extractValueExpr, out Action clearAction) { ... }

    /// <summary>
    /// Create an unconditionally caching function
    /// </summary>
    /// <typeparam name="T1">Type of the first function parameter</typeparam>
    /// <typeparam name="T2">Type of the second function parameter</typeparam>
    /// <typeparam name="T3">Type of the third function parameter</typeparam>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <returns>Caching function</returns>
    public static Func<T1, T2, T3, TResult> UseCachingForFunc<T1, T2, T3, TResult>(Expression<Func<T1, T2, T3, TResult>> extractValueExpr) { ... }

    /// <summary>
    /// Create an unconditionally caching function and return a method for clearing the cache of its values
    /// </summary>
    /// <typeparam name="T1">Type of the first function parameter</typeparam>
    /// <typeparam name="T2">Type of the second function parameter</typeparam>
    /// <typeparam name="T3">Type of the third function parameter</typeparam>
    /// <typeparam name="TResult">Type of the function returned value</typeparam>
    /// <param name="extractValueExpr">Function expression</param>
    /// <param name="clearAction">Cache clearing method</param>
    /// <returns>Caching function</returns>
    public static Func<T1, T2, T3, TResult> UseCachingForFunc<T1, T2, T3, TResult>(Expression<Func<T1, T2, T3, TResult>> extractValueExpr, out Action clearAction) { ... }

    ...
}
Note
This method has limitations:
  • Lambda expression parameters at the input must be simple: Type, string, Guid, int, uint, long, ulong, bool, sbyte, byte, short, ushort, char, float, double, decimal and their enumerations (derived from IEnumerable); 
  • A lambda expression must be a single line, i.e. you cannot use an operation block with curly brackets.

The list of metadata types, for which you can use the metadata cache:

ClassMetadata
EntityFilterMetadata
EntityMetadata
FormContextMetadata
CustomActivityParameters
DocumentMetadata
RegistrationCardMetadata
ProcessContext
ProcessInstanceMetricsContainer
ProcessMetricsContainer
ProjectMetadata
ReportParametersContainer
TablePartMetadata
Class1CMetadata
EnumMetadata
EntityActionsMetadata
EnumValueMetadata
PropertyMetadata
EntityPropertyMetadata
DocumentAttributeMetadata
ProcessInstanceMetric
ProcessMetric

Metadata cache use case:

// declare the caching function
private static Func<Type, string, bool, PropertyMetadata> getPropertyMetadata =
    MetadataLoader.UseCachingForFunc<Type, string, bool, PropertyMetadata>((type, name, inherit) => GetPropertyMetadata(type, name, inherit));

// the main function, where the result is calculated
private static PropertyMetadata GetPropertyMetadata(Type type, string propName, bool inherit)
{
    var metadata = MetadataLoader.LoadMetadata(type, inherit) as ClassMetadata;
    return (metadata != null)
        ? metadata.Properties.FirstOrDefault(p => p.Name == propName)
        : null;
}

public static PropertyMetadata LoadPropertyMetadata(Type type, string propName, bool inherit)
{
    // call the caching function
    return getPropertyMetadata(type, propName, inherit);
}

Conditional cache use case:

// declare the caching function
private Func<Guid, long?> getProjectTemplateIdCached;

{
    ...
    // create a conditionally caching function
    getProjectTemplateIdCached = MetadataLoader.UseConditionalCachingForFunc<Guid, long?>(uid => GetProjectTemplateId(uid));
    ...
}

// a function that calculates the result and defines whether to cache it or not
private Tuple<long?, bool> GetProjectTemplateId(Guid projectTypeUid)
{
    var template = ProjectMetadataHeadManager.GetMetadataHeadWithElevatedPrivilegies(projectTypeUid).ProjectTemplate;
    return template != null
        // this project template ID value will be cached
        ? new Tuple<long?, bool>(template.Id, true)
        // this value will not be cached
        : new Tuple<long?, bool>(null, false);
}

private bool IsProjectTemplateInternal(T project)
{
    if (project == null)
    {
        return false;
    }

    // use a conditionally caching function
    var templateId = getProjectTemplateIdCached(project.TypeUid);
    // If null is returned, it is not saved to cache.
    // Upon the next call of the caching function with the same project.TypeUid value
    // the result is calculated again

    return !templateId.HasValue || templateId == (long)project.GetId();
}

Complex cache

When using complex cache, the data are stored in the server memory, but the timestamp for synchronizing farm server is stored in the global cache. To use this cache type, you must define two keys: for storing data in the server cache and for storing the timestamp in the global cache.

IComplexCacheService

IComplexCacheService is registered in the IoC container and is available in all the server components of the application.

/// <summary>
    /// Complex cache
    /// </summary>
    public interface IComplexCacheService
    {

        /// <summary>
        /// Get the value from cache or calculate a value and save to cache if not found
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key to store in the cache</param>
        /// <param name="timestampKey">Key for the timestamp</param>
        /// <param name="valueAccessor">Function, which returns a value. It is called if data is not found in the cache.</param>
        /// <returns>Value</returns>
        T GetOrAdd<T>(string key, string timestampKey, Func<T> valueAccessor);

        /// <summary>
        /// Get a value from cache or calculate a value and save to cache if not found
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key to store in the cache</param>
        /// <param name="timestampKey">Key for the timestamp</param>
        /// <param name="valueAccessor">Function, which returns a value. It is called if data is not found in the cache.</param>
        /// <param name="timeout">Value storage timeout</param>
        /// <returns>Value</returns>
        T GetOrAdd<T>(string key, string timestampKey, Func<T> valueAccessor, TimeSpan timeout);

        /// <summary>
        /// Get a value from cache or calculate a value and save to cache, if not found
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key to store in the cache</param>
        /// <param name="timestampKey">Key for the timestamp</param>
        /// <param name="timestampRegionKey">Key for the region timestamp</param>
        /// <param name="valueAccessor">Function, which returns a value. It is called if data is not found in the cache.</param>
        /// <returns>Value</returns>
        T GetOrAdd<T>(string key, string timestampKey, string timestampRegionKey, Func<T> valueAccessor);

        /// <summary>
        /// Get a value from cache or calculate a value and save to cache, if not found
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key to store in the cache</param>
        /// <param name="timestampKey">Key for the timestamp</param>
        /// <param name="timestampRegionKey">Key for the region timestamp</param>
        /// <param name="valueAccessor">Function, which returns a value. It is called if data is not found in the cache.</param>
        /// <param name="timeout">Value storage timeout</param>
        /// <returns>Value</returns>
        T GetOrAdd<T>(string key, string timestampKey, string timestampRegionKey, Func<T> valueAccessor, TimeSpan timeout);

        /// <summary>
        /// Update the timestamp
        /// </summary>
        /// <param name="timestampKey">Key for the timestamp</param>
        void RefreshTimestamp(string timestampKey);

        /// <summary>
        /// Update the timestamp
        /// </summary>
        /// <param name="timestampKey">Key for the timestamp</param>
        /// <param name="timestampRegionKey">Key for the region timestamp</param>
        void RefreshTimestamp(string timestampKey, string timestampRegionKey);

        /// <summary>
        /// Update the region timestamp
        /// </summary>
        /// <param name="timestampRegionKey">Key for the region timestamp</param>
        void RefreshTimestampRegion(string timestampRegionKey);

    }

Caching method returned values

The ComplexCacheAttribute attribute allows you to cache the return values of methods called from components. A key for storing the timestamp in the global cache is passed in the attribute parameters. When the method is called, it composes a key based on the values of the sent arguments and uses IComplexCacheService to check if the item is stored in the cache. If it is, the value is returned from cache. If not, the method’s body is called, and the result is put into the cache.

A method marked with the ComplexCacheAttribute attribute must comply with the following conditions:

  • It is declared with the virtual keyword;
  • The method’s arguments must either be primitive or implement the IIdentified interface.

Complex cache for storing rarely changing data

When using this cache type, the data are also stored in the server memory, and the global cache stores timestamps for synchronizing farm servers for each cache item individually and one timestamp for all the cache items. Для использования данного вида кэша, как и для составного кэша, нужно задавать 2 ключа: для хранения данных в серверном кэше и для хранения отпечатка времени в глобальном кэше.

IUnionComplexCacheService

IUnionComplexCacheService is registered in the IoC container and is available in all the server components of the application.

/// <summary>
    /// Complex cache for storing rarely changing data (for calling the distributed cache occasionally)
    /// </summary>
    public interface IUnionComplexCacheService
    {

        /// <summary>
        /// Get a value from cache or calculate a value and save to the cache if not found
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key for storing in the cache</param>
        /// <param name="timestampKey">Key for the timestamp</param>
        /// <param name="valueAccessor">A function, which returns a value. It is called when data is not found in the cache.</param>
        /// <returns>Value</returns>
        T GetOrAdd<T>(string key, string timestampKey, Func<T> valueAccessor);

        /// <summary>
        /// Get a value from cache or calculate a value and save to the cache if not found
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="key">Key for storing in the cache</param>
        /// <param name="timestampKey">Key for the timestamp</param>
        /// <param name="valueAccessor">A function, which returns a value. It is called when data is not found in the cache.</param>
        /// <param name="timeout">Value storage timeout</param>
        /// <returns>Value</returns>
        T GetOrAdd<T>(string key, string timestampKey, Func<T> valueAccessor, TimeSpan timeout);

        /// <summary>
        /// Update timestamp
        /// </summary>
        /// <param name="timestampKey">Key for the timestamp</param>
        void RefreshTimestamp(string timestampKey);

    }

Context cache

A context cache stores the data in memory only for the current request (current execution thread).

To employ context cache, you can use the IContextBoundVariableService service or the ContextVars static class.

IContextBoundVariableService

IContextBoundVariableService is registered in the IoC container and is available in all the server components of the application.

/// <summary>
    /// Abstract service for working with variables
    /// </summary>
    public interface IAbstractBoundVariableService
    {

        /// <summary>
        /// Whether there is a variable value
        /// </summary>
        /// <param name="name">Variable name</param>
        /// <returns>True, if there is</returns>
        bool Contains(string name);

        /// <summary>
        /// Get the variable value. If not defined, VariableNotFoundException is returned
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="name">Variable name</param>
        /// <returns>Variable value</returns>
        T Get<T>(string name);

        /// <summary>
        /// Try and get the variable value
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="name">Variable name</param>
        /// <param name="value">Obtained value (or the default value for the type <typeparamref name="T"/>, if the item is not found)</param>
        /// <returns>True, if the variable is found</returns>
        bool TryGetValue<T>(string name, out T value);

        /// <summary>
        /// Set a variable value
        /// </summary>
        /// <param name="name">Variable name</param>
        /// <param name="value">Variable value</param>
        void Set(string name, object value);

        /// <summary>
        /// Remove a variable value
        /// </summary>
        /// <param name="key"></param>
        void Remove(string key);
    }

    /// <summary>
    /// Interface of the service for working with variable values within a context (e.g. a web request)
    /// </summary>
    public interface IContextBoundVariableService : IAbstractBoundVariableService
    {
        /// <summary>
        /// Clear all variables
        /// </summary>
        void Clear();
    }

ContextVars

ContextVars is a static class for more convenient work with IContextBoundVariableService.

/// <summary>
    /// A static class for working with variable values within a context (e.g. a web request)
    /// </summary>
    /// <remarks>
    /// To work with it, set an implementing service via the SetImpl method
    /// </remarks>
    public static class ContextVars
    {

        /// <summary>
        /// Whether there is a variable value
        /// </summary>
        /// <param name="name">Variable name</param>
        /// <returns>True, if there is</returns>
        public static bool Contains(string name);

        /// <summary>
        /// Get the variable value. 
        /// If not defined, VariableNotFoundException is returned
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="name">Variable name</param>
        /// <returns>Variable value</returns>
        public static T Get<T>(string name);

        /// <summary>
        /// Try and get a new variable value
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="name">Variable name</param>
        /// <param name="value">Obtained value (or the default value for the type <typeparamref name="T"/>, if the item is not found)</param>
        /// <returns>True, if the variable is found</returns>
        public static bool TryGetValue<T>(string name, out T value);

        /// <summary>
        /// Get the variable value or set from the function.
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="name">Variable name</param>
        /// <param name="val">Function for getting a value</param>
        /// <returns>Variable value</returns>
        public static T GetOrAdd<T>(string name, Func<T> val);

        /// <summary>
        /// Set the variable value
        /// </summary>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="name">Variable name</param>
        /// <param name="value">Variable value</param>
        public static void Set<T>(string name, T value);

        /// <summary>
        /// Remove the variable value
        /// </summary>
        /// <param name="name"></param>
        public static void Remove(string name);

    }

Caching method returned values

ContextCacheAttribute allows you to cache the return values of the methods called from components during the lifespan of one HTTP request or another execution context.