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到查询的列表里了。

原文地址:https://www.cnblogs.com/lws66/p/12596296.html