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

时间: 2024-10-17 05:10:48

AutoMapper小技巧:通过特性配置读取缓存的相关文章

如何在u不能图上搭配android开发环境——ubuntu小技巧4

如何在linux下用eclipse配置android开发环境 好长时间没有搭配android开发环境了,前几天在win下配了一个用了一下,开始经常使用linux系统的我无法满足于win,今天在ubuntu下试了下,配置了linux下的android环境,在这里分享给想学安卓 的朋友!在另外一篇博客里面介绍了如何搭配win下的android开发环境,有兴趣的朋友可以看一看! 搭配android环境有两种方法:第一种使用集成开发包,第二种自己下载配置插件. 至于是否方便,当然第一种比较容易,省时,合

思科命令配置小技巧三:alias 命令

大家都用过手机上的快捷拨号设置 思科设备是否支持命令的快捷键定义呢 答案是肯定的 suzhouxiaoniu(config)#alias exec xx show ip inter bri  xx是自定义的快捷键名称,可以是数字 suzhouxiaoniu#xx 直接敲定义好的名称Interface                  IP-Address      OK? Method Status                ProtocolFastEthernet1/0          

思科命令配置小技巧四:用ACL控制debug 输出

使用debug命令可以帮助我们TS,但是使用debug命令往往会输出一大堆信息,很多是我们不需要用的,也会造成CPU高负荷,这种情况下我们可以限制debug的输出 可以应用ACL到debug以限定仅输出要求的debug信息. 如仅查看从1.1.1.1到1.1.1.2的ICMP包: Router(config)# access-list 100 permit icmp host 1.1.1.1 host 1.1.1.2 Router# debug ip packet detail 100 思科命令

Nginx return 关键字配置小技巧

Nginx的return关键字属于HttpRewriteModule模块: 语法:return http状态码 默认值:无 上下文:server,location,if 该指令将结束执行直接返回http状态码到客户端. 支持的http状态码:200, 204, 400, 402-406, 408, 410, 411, 413, 416 , 500-504,还有非标准的444状态码. 使用方法: #不符合规则的返回403禁止访问 location /download/ {     rewrite 

思科命令配置小技巧一:rang命令

在交换机的配置中,经常会对一组端口进行相同的操作,为简化配置,提高设备性能 可以在配置中使用range命令: suzhouxiaoniu(config)#inter range fa1/1-10  对10个连续的端口同时进行操作suzhouxiaoniu(config-if-range)#swi mo accsuzhouxiaoniu(config-if-range)#swi acc vlan 2 suzhouxiaoniu(config)#inter range fa1/1 ,fa1/3 ,f

思科命令配置小技巧二:macro命令

在 思科命令配置小技巧一中,我们谈到,使用range命令可以简化我们的配置 但是如果我们经常对一组不连续的端口进行操作 比如 interface-range  fa1/1 ,fa1/3 ,fa1/5 ,fa1/7 ,fa1/11 即使使用range命令也会显得很繁琐 我们总想越简单越好(命令敲再多,工资还是那个数,要是按命令字数算工资多好) 此时交换机的宏命令就派上用场了 suzhouxiaoniu(config)#define interface-range abc fa1/1 ,fa1/3

Putty 工具 保存配置的 小技巧

用Putty 已经很长时间了,但一直被一个问题困扰,有时候是懒得去弄,反正也不怎么碍事,今天小研究了下,把这个问题解决了,心里也舒服了. Putty是一个免费小巧的Win32平台下的telnet,rlogin和ssh客户端. 它的主程序不到1M, 是完全免费的telnet和ssh客户端工具. 而且无需安装,下载后在桌面建个快捷方式就行 . Putty官网的下载地址: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html 主

思科命令配置小技巧五:记事本

对于自己常用的命令 可以事先有记事本配置保存好 比如 en conf t line con 0 logg syn exec-t  0 0 exit host 3548 把上面的命令直接在设备的   > 或者 # 模式下复制进去即可 要注意的是:黏贴命令可以可以包含 enter 键的. 如果选择复制的时候包含了 enter 键盘. 那么黏贴后回立即执行 思科命令配置小技巧五:记事本

思科命令配置小技巧六:default inter

在配置命令,特别是做实验的时候,经常在一个接口下配置了大量的命令 而在做另外一个实验的时候,又要清除接口的大部分或者全部配置 suzhouxiaoniu#show run inter s1/1Building configuration... Current configuration : 242 bytes!interface Serial1/1 description suzhouxiaoniu bandwidth 512 ip address 8.8.8.8 255.255.255.0 e