• AutoMapper小技巧:通过特性配置读取缓存


    在项目开发中经常会遇到这样的场景:查询一个复杂实体,其中一部分字段数据从数据库中直接查出,另一部字段数据从缓存中取出。这里通过AutoMapper和特性,提供一种优雅的编码方法。

    这种方法的大概思路是:在成员的特性中配置好[缓存字典的key]、[与缓存字典关联的外键名称]和[缓存字典里目标字段的名称]。然后根据上述参数从缓存里取出需要的数据,最后通过配置AutoMapper的Profile来将数据映射到要查询的list里。

    可能这样说会让人有点摸不着头脑,接下来就开始一步一步讲解如何编码。

    1.建立一个Attribute并在Property中标记以获取我们需要的参数

        /// <summary>
        /// 使用映射
        /// </summary>
        [AttributeUsage(AttributeTargets.Property)]
        public  class MapperPropertyAttribute: Attribute
        {
            /// <summary>
            /// 缓存的Key
            /// </summary>
            public string CacheTableKey { get; set; }
    
            /// <summary>
            /// 与缓存字典关联的外键
            /// </summary>
            public string SearchKey { get; set; }
    
            /// <summary>
            ///  缓存字典里的目标字段
            /// </summary>
            public string SearchName { get; set; }
     
        }

    有了上面的Attribute,就可以用它来标记需要Mapper的Property了。如:

            [MapperProperty(CacheTableKey = CacheKey.SYS_USER,SearchKey ="sysID",SearchName ="RealName")]
            public string CreatedUserName { set; get; }

    2.根据参数从缓存里取出数据


    首先建立一个Wrapper来存放从缓存中取出的数据

        /// <summary>
        /// 缓存包装层,用于封装从缓存读取的数据
        /// </summary>
        public class CacheDictInfoWrapper
        {
    
            public CacheDictInfoWrapper()
            {
                TempDict = new Dictionary<MapperKey, MapperResult>();
            }
    
            public Dictionary<MapperKey, MapperResult> TempDict { get; set; }
    
        }
    
        /// <summary>
        /// 映射的主键
        /// </summary>
        public class MapperKey
        {
            public MapperKey(){}
    
            public MapperKey(string keyValue, string fieldName)
            {
                KeyValue = keyValue;
                FieldName = fieldName;
            }
    
            /// <summary>
            /// 要对比的主键值
            /// </summary>
            public string KeyValue { get; set; }
            /// <summary>
            /// 要匹配的字段名
            /// </summary>
            public string FieldName { get; set; }
    
            public override bool Equals(object obj)
            {
                var entity = obj as MapperKey;
                return entity.KeyValue.Equals(KeyValue) && entity.FieldName.Equals(FieldName);
            }
    
            public override int GetHashCode()
            {
                return $"{KeyValue},{FieldName}".GetHashCode();
            }
        }
    
       public  class MapperResult
        {
    
            public string Key { get; set; }
            public string Result { get; set; }
        }

    Wrapper最终会被用于Mapper进我们要查询的对象里。具体是如何操作的稍后再进行讲解。

    有了Wrapper类,接下来的工作就是把缓存数据查出来并放入new好的Wrapper。

    思路在注释里写得比较详细了,这里就不再赘述。

    public class MapperConvert
        {
            public static IEnumerable<T> Pure<T>(IEnumerable<T> srclist)
            {
                if (srclist == null || srclist.Count() < 1)
                    return srclist;
                //通过反射获取T的Properties
                var props = srclist.FirstOrDefault().GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CustomAttributes.Any()).ToList();
                if (props.Any())
                {
                    Dictionary<string, CacheDictInfoWrapper> totalDict = GetCacheDictInfoWrapperDict(props);
                    if (totalDict.Keys.Count > 0)
                    {
                        foreach (var entity in srclist)
                        {
                            //使用AutoMapper来将CacheDictInfoWrapper里的数据Mapper进entity
                            AutoMapperBootstrap.Mapper.Map<Dictionary<string, CacheDictInfoWrapper>, T>(totalDict, entity);
                        }
                    }
                }
                return srclist;
            }
    
            /// <summary>
            /// 获取CacheDictInfoWrapper
            /// </summary>
            /// <param name="props"></param>
            /// <returns></returns>
            private static Dictionary<string, CacheDictInfoWrapper> GetCacheDictInfoWrapperDict(List<PropertyInfo> props)
            {
                //一个类存在多个字段使用同一缓存字典时直接从这里拿缓存,不用重复从缓存中间件取
                Dictionary<string, List<JObject>> historyDict = new Dictionary<string, List<JObject>>();
                //Dictionary的key就是缓存字典的Key,用于区分缓存的来源
                Dictionary<string, CacheDictInfoWrapper> totalDict = new Dictionary<string, CacheDictInfoWrapper>();
                //这一句是我这里获取CacheProvider实例的方法,各位需要根据自己的代码修改
                ICacheProvider cacheProvider = CacheContainer.Resolve<ICacheProvider>();
                //遍历Properties,只有标记了MapperPropertyAttribute的property才进行缓存操作
                foreach (var property in props)
                {
                    var attr = property.GetCustomAttribute(typeof(MapperPropertyAttribute), true);
                    if (attr != null)
                    {
                        //将Attribute里配置好的参数取出
                        var mapper = (attr as MapperPropertyAttribute);
                        var key = mapper.CacheTableKey;
                        var keyName = property.Name;
                        var searchKey = mapper.SearchKey;
                        var searchName = mapper.SearchName;
                        try
                        {
                            if (!historyDict.TryGetValue(key, out List<JObject> objList))
                            {
                                objList = new List<JObject>();
                                //从CacheProvider取出缓存
                                dynamic list = cacheProvider.Get(key);
                                if (list != null)
                                {
                                    //这里把它转成JObject是因为只有JObject才能通过[xxx]匹配,如果有更好的方法欢迎提出
                                    foreach (var item in list)
                                    {
                                        objList.Add(JObject.FromObject(item));
                                    }
                                    historyDict.Add(key, objList);
                                    totalDict.Add(key, new CacheDictInfoWrapper());
                                }
                            }
                            //从缓存取出的数据不为空时,把数据放入Wrapper
                            if (objList != null && objList.Any())
                            {
                                var dictWrapper = GenerateDictInfoWrapper(objList, searchKey, searchName, keyName);
                                foreach (var kevValue in dictWrapper.TempDict)
                                {
                                    totalDict[key].TempDict.Add(kevValue.Key, kevValue.Value);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            logger.Erroe(ex);
                        }
                    }
                }
                return totalDict;
            }
    
            /// <summary>
            /// 根据Jobject组装Wrapper
            /// </summary>
            /// <param name="listObj"></param>
            /// <param name="mapperInfo"></param>
            /// <returns></returns>
            private static CacheDictInfoWrapper GenerateDictInfoWrapper(List<JObject> listObj, string searchKey, string searchName, string keyName)
            {
                CacheDictInfoWrapper cacheDictInfoWrapper = new CacheDictInfoWrapper();
                foreach (var jObject in listObj)
                {
                    MapperKey mapperKey;
                    MapperResult mapperResult;
                    try
                    {
                        //上面提到的,只有JObject能这样匹配
                        var key = jObject[searchKey].ToString();
                        mapperKey = new MapperKey { KeyValue = key, FieldName = keyName };
                        //这里把结果全部转成String了,AutoMapper会自动根据property的类型映射
                        mapperResult = new MapperResult { Key = key, Result = jObject[searchName].ToString() };
                        cacheDictInfoWrapper.TempDict.TryAdd(mapperKey, mapperResult);
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex);
                    }
                }
                return cacheDictInfoWrapper;
            }
        }

    这样一来,执行Pure<T>()时,就能得到装满缓存数据的Wrapper了。

    3.配置AutoMapper的Profile

    有了Wrapper,我们需要配置AutoMapper的Profile来让AutoMapper正常工作。对这方面不熟悉的朋友可以百度一下AutoMapper和Profile。

    每个需要Mapper的实体类都需要配置一个Profile。

        public class CacheProfile : Profile
        {
    
            public CacheProfile()
            {
                //创建automapper映射关系
                CreateMap<Dictionary<string,CacheDictInfoWrapper>, BaseDto>(MemberList.None)
                    .ForMember(dest => dest.UpdatedUserName, opts => opts.MapFrom((src1, dest1, res1) =>
                     {
                         var result = string.Empty;//非String的类型这里需要 = null或defult()
                         MapperResult tr;
                         MapperKey mapperKey = new MapperKey(dest1.UpdatedBy, nameof(dest1.UpdatedUserName));
                         if (src1.TryGetValue(CacheKey.SYS_USER, out CacheDictInfoWrapper dic))
                         {
                             if ((!string.IsNullOrEmpty(dest1.UpdatedBy)) && dic.TempDict.TryGetValue(mapperKey, out tr))
                             {
                                 result = tr.Result;
                             }
                         }
                         return result;
                     }))
                  .ForMember(dest => dest.CreatedUserName, opts => opts.MapFrom((src1, dest1, res1) =>
                  {
                      var result = string.Empty;
                      MapperResult tr;
                      MapperKey mapperKey = new MapperKey(dest1.UpdatedBy, nameof(dest1.CreatedUserName));
                      if (src1.TryGetValue(CacheKey.SYS_USER, out CacheDictInfoWrapper dic))
                      {
                          if ((!string.IsNullOrEmpty(dest1.UpdatedBy)) && dic.TempDict.TryGetValue(mapperKey, out tr))
                          {
                              result = tr.Result;
                          }
                      }
                      return result;
                  })).IncludeAllDerived(); //添加此方法,用于子类有重复映射时,不会覆盖该映射,导致该映射失效。
            }
        }

    配置了Profile,AutoMaperr就可以把查出的缓存mapper进我们想要查询的列表里了。最后我们可以写一个扩展方法来让操作更优雅:

            public static void Pure<T>(this IEnumerable<T> srcList) where T: class//BaseDto
            {
                 MapperConvert.Pure<T>(srcList);
            }

    这样一来,只需要一句 list.Pure(); 就可以优雅的把缓存的数据mapper到查询的列表里了。

  • 相关阅读:
    C#--SqlDependency监控数据库表的变化
    C#--尝试读取或写入受保护的内存,这通常指示其他内存已损坏。
    C#--lock线程锁
    C#--抽象类(转载)
    ArrayList源码分析
    多线程编程bug起源分析
    Linux-CentOS-Nginx安装
    记一次springMVC的跨域解决方案
    Linux下压缩工具gzip和归档工具tar及其实战shell应用
    Docker在Centos 7上的部署
  • 原文地址:https://www.cnblogs.com/lws66/p/12596296.html
Copyright © 2020-2023  润新知