PostSharp is used in. NET project and MemoryCache is used to process (transfer) caches.

Keywords: C# Database Programming Windows

A previous essay< Using PostSharp in. NET Project to Realize AOP Aspect-Oriented Programming Processing > This paper introduces the use of PostSharp framework. Using PostSharp can bring me a lot of convenience and advantages, reduce code redundancy, improve readability, and can more elegantly realize the processing of routine business scenarios such as log, exception, cache, transaction and so on. This article mainly introduces how to use MemoryCache to implement cache processing.

1. Review of MemoryCache

There is no mention of cache processing in the previous article. In general, we can use Microsoft's distributed cache component MemoryCache to process the cache. MemoryCache is a new caching object introduced in. NET 4.0, mainly replacing the caching module of the original enterprise library, so that. NET caching can be used everywhere instead of using it based on a specific version of Windows.

Caching is needed in many cases. Reasonable use of caching can not only improve the response speed of programs, but also reduce the pressure of access to specific resources. This paper mainly introduces the use of caching in Winform, hoping that you can learn some caching scenarios and usage methods. Caching is a problem that must be considered in large and medium-sized systems. In order to avoid accessing background resources (such as databases) for each request, we usually consider saving some data that is not updated frequently and can be reused temporarily in a certain way, and subsequent requests can directly access these saved data according to the situation. This mechanism is the so-called caching mechanism.

The cache function of. NET 4.0 mainly consists of three parts: System.Runtime.Caching, System.Web.Caching.Cache and Output Cache.

System.Runtime.Caching is a new caching framework in. NET 4.0. It mainly uses the MemoryCache object, which exists in the assembly System.Runtime.Caching.dll.

System.Web.Caching.Cache is a cached object that has existed since. NET 2.0. It is mainly used in the Web, but it can also be used in Winform, but it needs to refer to System.Web.dll.

Output Cache is used in Asp. NET. Before ASP. NET 4.0, the System.Web.Caching.Cache was used directly to cache HTML fragments. It was redesigned in ASP.NET 4.0 to provide an Output Cache Provider for developers to extend, but by default it still uses System.Web.Caching.Cache for caching.

My previous essay< Cache usage in Winform > The processing of MemoryCache auxiliary classes was introduced to facilitate cached data operations. The main code of its auxiliary class is shown below.

    /// <summary>
    /// Cache Auxiliary Class Based on MemoryCache
    /// </summary>
    public static class MemoryCacheHelper
    {
        private static readonly Object locker = new object();
     
        /// <summary>
        /// Create a cached key value, specify the time range of the response, and automatically retrieve the corresponding value if it fails
        /// </summary>
        /// <typeparam name="T">object type </typeparam>
        /// <param name="key">key of the object </param>
        /// <param name="cachePopulate">operation to get cached value </param>
        /// <param name="sliding Expiration">time range of failure </param>
        /// <param name="absolute Expiration">absolute time of failure </param>
        /// <returns></returns>
        public static T GetCacheItem<T>(String key, Func<T> cachePopulate, TimeSpan? slidingExpiration = null, DateTime? absoluteExpiration = null)
        {
            if(String.IsNullOrWhiteSpace(key)) throw new ArgumentException("Invalid cache key");
            if(cachePopulate == null) throw new ArgumentNullException("cachePopulate");
            if(slidingExpiration == null && absoluteExpiration == null) throw new ArgumentException("Either a sliding expiration or absolute must be provided");
     
            if(MemoryCache.Default[key] == null)
            {
                lock(locker)
                {
                    if(MemoryCache.Default[key] == null)
                    {
                        var item = new CacheItem(key, cachePopulate());
                        var policy = CreatePolicy(slidingExpiration, absoluteExpiration);
     
                        MemoryCache.Default.Add(item, policy);
                    }
                }
            }
     
            return (T)MemoryCache.Default[key];
        }
     
        private static CacheItemPolicy CreatePolicy(TimeSpan? slidingExpiration, DateTime? absoluteExpiration)
        {
            var policy = new CacheItemPolicy();
     
            if(absoluteExpiration.HasValue)
            {
                policy.AbsoluteExpiration = absoluteExpiration.Value;
            }
            else if(slidingExpiration.HasValue)
            {
                policy.SlidingExpiration = slidingExpiration.Value;
            }
     
            policy.Priority = CacheItemPriority.Default;
     
            return policy;
        }

        /// <summary>
        /// Clear the cache
        /// </summary>
        public static void ClearCache()
        {
            List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
            foreach (string cacheKey in cacheKeys)
            {
                MemoryCache.Default.Remove(cacheKey);
            }
        }

      ...//Omit part of the code

    }

In our program, if we need to use caching, then it is more convenient to call this auxiliary class to solve it. The code to implement caching is as follows.

    public static class UserCacheService
    {
        /// <summary>
        /// Get all the user's simple object information and put it in the cache
        /// </summary>
        /// <returns></returns>
        public static List<SimpleUserInfo> GetSimpleUsers()
        {
            System.Reflection.MethodBase method = System.Reflection.MethodBase.GetCurrentMethod();
            string key = string.Format("{0}-{1}", method.DeclaringType.FullName, method.Name);

            return MemoryCacheHelper.GetCacheItem<List<SimpleUserInfo>>(key,
                delegate() {
                    //return CallerFactory<IUserService>.Instance.GetSimpleUsers(); 

                    //Simulate the acquisition of data from a database
                    List<SimpleUserInfo> list = new List<SimpleUserInfo>();
                    for(int i = 0; i< 10; i++)
                    {
                        var info = new SimpleUserInfo();
                        info.ID = i;
                        info.Name = string.Concat("Name:", i);
                        info.FullName = string.Concat("Full name:", i);
                        list.Add(info);
                    }
                    return list;
                },
                new TimeSpan(0, 10, 0));//10 minutes overdue
        }

        /// <summary>
        /// According to the user's ID, get the user's login name and put it in the cache
        /// </summary>
        /// <param name="userId">user ID</param>
        /// <returns></returns>
        public static string GetNameByID(string userId)
        {
            string result = "";
            if (!string.IsNullOrEmpty(userId))
            {
                System.Reflection.MethodBase method = System.Reflection.MethodBase.GetCurrentMethod();
                string key = string.Format("{0}-{1}-{2}", method.DeclaringType.FullName, method.Name, userId);

                result = MemoryCacheHelper.GetCacheItem<string>(key,
                    delegate() {
                        //return CallerFactory<IUserService>.Instance.GetNameByID(userId.ToInt32()); 

                        return string.Concat("Name:", userId);
                    },
                    new TimeSpan(0, 30, 0));//30 minutes overdue
            }
            return result;
        }

In the above case, I simulated the construction of database data return. Otherwise, we usually use BLLFactory < T > or CallerFactory < T > in the mixed framework client to make the call interface, which is equivalent to the need for further function encapsulation processing to achieve the goal.

In the case, the failure cache time can be set, and after the failure, the cache content can be automatically retrieved through the function of Func < T > cachePopulate. In practice, it is also a very intelligent way to deal with it.

2. Implementing Caching with PostSharp and Memory Cache

The above case uses MemoryCache auxiliary classes to implement cache processing, which can solve practical problems. But at the same time, the problem also comes. Every cache processing, we need to write an additional code for processing. The code is very redundant, and once caching is used in many places, it is very difficult to maintain these codes.

We hope to introduce PostSharp technology to reduce the duplication of the system code, reduce the coupling between modules, and be conducive to future operability and maintainability. This AOP code weaving technology can separate cross-section and business processing very well, so as to simplify the code.

With respect to the above code issues, let's see how our code implements caching after the introduction of PostSharp.

    /// <summary>
    /// Using PostSharp and MemoryCache to Implement Cache Processing Classes
    /// </summary>
    public class CacheService
    {               
        /// <summary>
        /// Get all the user's simple object information and put it in the cache
        /// </summary>
        /// <returns></returns>
        [Cache(ExpirationPeriod = 30)]
        public static List<SimpleUserInfo> GetSimpleUsers(int userid)
        {//return CallerFactory<IUserService>.Instance.GetSimpleUsers(); 

            //Simulate the acquisition of data from a database
            List<SimpleUserInfo> list = new List<SimpleUserInfo>();
            for (int i = 0; i < 10; i++)
            {
                var info = new SimpleUserInfo();
                info.ID = i;
                info.Name = string.Concat("Name:", i);
                info.FullName = string.Concat("Full name:", i);
                list.Add(info);
            }
            return list;
        }
                       
        /// <summary>
        /// According to the user's ID, get the user's login name and put it in the cache
        /// </summary>
        /// <param name="userId">user ID</param>
        /// <returns></returns>
        [Cache]
        public static string GetNameByID(string userId)
        {//return CallerFactory<IUserService>.Instance.GetNameByID(userId.ToInt32()); 
            return string.Concat("Name:", userId);
        }
    }

We noticed that the above function code, in addition to calling business logic (here to construct a data demonstration), is actually no redundant other code. But we started the function with a feature tag:

[Cache(ExpirationPeriod = 30)]

perhaps

[Cache]

This is the function that we declare to use caching, that's all, is it very simple?

Let's take a look at the results of decompiling the generated code, as shown below.

This is different from our actual code. We integrate PostSharp's weaving code to achieve cache processing, but we are transparent in the development process, just need to maintain the code we write.

In this case, CacheAttribute is used for identification. The code of this class is processed using the base class of PostSharp.

    /// <summary>
    /// Method to implement cache identification
    /// </summary>
    [Serializable]
    public class CacheAttribute : MethodInterceptionAspect
    {
        /// <summary>
        /// Cache failure time setting, default 30 minutes
        /// </summary>
        public int ExpirationPeriod = 30;

        /// <summary>
        /// Call Processing of PostSharp to Implement Cache Processing of Data
        /// </summary>
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            //Default 30 minutes invalidation, if set expiration time, then use set value
            TimeSpan timeSpan = new TimeSpan(0, 0, ExpirationPeriod, 0);

            var cache = MethodResultCache.GetCache(args.Method, timeSpan);
            var arguments = args.Arguments.ToList();
            var result = cache.GetCachedResult(arguments);
            if (result != null)
            {
                args.ReturnValue = result;
                return;
            }
            else
            {
                base.OnInvoke(args);

                //Update the cache after invocation
                cache.CacheCallResult(args.ReturnValue, arguments);
            }
        }
    }

This CacheAttribute feature class contains a set time interval (minute) to specify the time when the function returns the result. By inheriting the MethodInterception Aspect base class, we rewrite the void OnInvoke (MethodInterception Args args) function to intervene in the cross section of the calling process:

If the cached result is obtained during the invocation process, it will be returned directly without calling the function business logic; otherwise, the calling function will get the return value and reset the cached result value.

In the function code, the method cache object is constructed by passing in parameters (including method object, timeout, etc.).

MethodResultCache.GetCache(args.Method, timeSpan);

In MethodResultCache, we are dealing with the cache of the method. First, we need to declare an object of MemoryCache to manage the cache (distributed cache).

        /// <summary>
        /// Initialize the cache manager
        /// </summary>
        private void InitCacheManager()
        {
            _cache = new MemoryCache(_methodName);
        }

The key to obtain method and parameter by function is the only key.

        /// <summary>
        /// Get the cache key based on the calling method name and parameters
        /// </summary>
        /// <param name="arguments">method parameter list </param>
        /// <returns></returns>
        private string GetCacheKey(IEnumerable<object> arguments)
        {
            var key = string.Format("{0}({1})", _methodName,
              string.Join(", ", arguments.Select(x => x != null ? x.ToString() : "<Null>")));
            return key;
        }

To set the cache, we call the MemoryCache cache management class to set the key values, as shown in the following code.

        /// <summary>
        /// Cache result content
        /// </summary>
        /// <param name="result">result to be cached </param>
        /// parameter set of < param name= "arguments"> method </param>
        public void CacheCallResult(object result, IEnumerable<object> arguments)
        {
            _cache.Set(GetCacheKey(arguments), result, DateTimeOffset.Now.Add(_expirationPeriod));
        }

In this way, we set up a key value cache and specify the expiration time of the cache. During this period, the data we get every time does not need to call the external interface again, and we get it directly from the cache, which greatly improves the speed, and also reduces the IO pressure on the servers in the distributed architecture.

We can write a small piece of code to test the efficiency, as shown in the following code.

            //First test
            DateTime start = DateTime.Now;
            var list = CacheService.GetSimpleUsers(1);
            int end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;

            Console.WriteLine(" first: " + end);

            //Second test
            start = DateTime.Now; 
            list = CacheService.GetSimpleUsers(2);
            end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;
            Console.WriteLine(" Second: " + end);

The results obtained are shown below (the time for obtaining the results is described separately).

 first: 519
 Second: 501

 first: 0
 Second: 0

 first: 0
 Second: 0

As can be seen from the above code, the first request for data has a certain time lag, and the number of milliseconds after the request is directly zero.

Through the integration of PostSharp and MemoryCache above, we can greatly simplify the caching processing code, and can use the better MemoryCache cache management class to achieve caching processing, which is very convenient and efficient.

The author has several related articles, and by the way, they are really good:

Using PostSharp in. NET Project to Realize AOP Aspect-Oriented Programming Processing

Use PostSharp in. NET project and MemoryCache to implement caching

Using PostSharp in. NET project and Cache Manager to implement the processing of various caching frameworks

Posted by Divine Winds on Wed, 17 Apr 2019 22:42:34 -0700