zoukankan      html  css  js  c++  java
  • 编写优雅代码,从挖掉恶心的if/else 开始

    背景

      长话短说, 作为开发人员经常需要根据条件灵活(过滤+排序)数据库,不管你是用rawsql 还是EFCore, 以下类似伪代码大家都可能遇到:

            /// <summary>
            /// 灵活过滤 能耗数据表  (rawsql)
            /// </summary>
            [Route("all")]
            [HttpGet]
            public async Task<List<CarEnergyModelEntity>> GetModeParametersAsync(
               [FromQuery] string carVersion,
               [FromQuery] string carId,
               [FromQuery] string userId,
               [FromQuery] string soVersion,
               [FromQuery] string configVersion,
               [FromQuery] string ConfigContent
                )
            {
                StringBuilder strWhere = new StringBuilder(" 1=1 ");
    
                if (!string.IsNullOrEmpty(carVersion))
                    strWhere.Append($" and car_version='{carVersion}'");
                if (!string.IsNullOrEmpty(carId))
                    strWhere.Append($" and car_id_='{carId}'");
                if (!string.IsNullOrEmpty(userId))
                    strWhere.Append($" and user_id='{userId}'");
                if (!string.IsNullOrEmpty(soVersion))
                    strWhere.Append($" and so_version='{soVersion}'");
                if (!string.IsNullOrEmpty(configVersion))
                    strWhere.Append($" and config_version='{configVersion}'");
                if (!string.IsNullOrEmpty(ConfigContent))
                    strWhere.Append($" and config_content='{ConfigContent}'");
                
                var dt = new DataTable();
                using (SqlConnection con = new SqlConnection("//connectStr//"))
                {
                    var sql = $"select * from dbo.[car_energy_model] where {strWhere.ToString()}";
                    using (SqlCommand cmd = new SqlCommand(sql, con))
                    {
                        // TODO
                    }
                }
            }
           /// <summary>
            /// 灵活过滤 能耗数据表  (EFCore)
            /// </summary>
            [Route("all")]
            [HttpGet]
            public async Task<List<CarEnergyModelEntity>> GetModeParametersAsync1(
               [FromQuery] string carVersion,
               [FromQuery] string carId,
               [FromQuery] string userId,
               [FromQuery] string soVersion,
               [FromQuery] string configVersion,
               [FromQuery] string ConfigContent
                )
            {
                var sqlQuery = _context.CarEnergyModels;
    
                if (!string.IsNullOrEmpty(carVersion))
                    sqlQuery = sqlQuery.Where(x=>x.CarVersion == carVersion);
                if (!string.IsNullOrEmpty(carId))
                    sqlQuery = sqlQuery.Where(x => x.CarId == carId);
                if (!string.IsNullOrEmpty(userId))
                    sqlQuery = sqlQuery.Where(x => x.UserId == userId);
                if (!string.IsNullOrEmpty(soVersion))
                    sqlQUery = sqlQuery.Where(x => x.SoVersion == soVersion);
                if (!string.IsNullOrEmpty(configVersion))
                    sqlQuery = sqlQuery.Where(x => x.ConfigVersion == configVersion);
                if (!string.IsNullOrEmpty(ConfigContent))
                    sqlQuery = sqlQuery.Where(x => x.ConfigContent == ConfigContent);
    
                return sqlQuery.ToList();
            }

       特别是在大数据产品或者物联网产品中,字段甚多;需要 过滤/ 排序 的字段千变万化, if/else 写到死,一边写一边吐。

       写出优雅漂亮的代码,从移除if/else 开始。

    头脑风暴

      从灵活查询的要求看,每一个字段都有为null 或 不为null 的可能, 以上伪代码6个字段, 理论上仅过滤字段最终执行查询时形成的sql 共有2^6= 64种可能, 还不算 灵活的排序字段。

    现在我们要写这么多if 语法,是因为:

      -  在编码阶段,强制判断字段存在, 并据此组装 rawsql

      -  在编码阶段,强制判断字段存在,并据此使用lambda强类型 构造IQueryable

    为了解决这个痛点, 引入动态Linq,动态Linq的不同之处在于 查询方法的参数不限于强类型的lamdba表达式,而是可以使用字符串;

    使用字符串,意味着我们可在运行时动态决定过滤、排序内容

    // 常规EF Linq: where条件过滤 + 倒排
    _context.CarEnergyModels.Where(x=>x.CarVersion == carVersion).OrderByDescending(x=>x.UploadTime);
    
    // 动态EF Linq: where 条件过滤 + 倒排
    _context.CarEnergyModels.Where("carVersion=="ft_version_3.2"").OrderBy("UploadTime desc");

      同时由于我们在服务端可完全抓取QueryString(可一次性组装动态Linq字符串), 故动态灵活构建查询的方案呼之欲出。

    编码实践

    以上面伪代码业务举例, 根据条件灵活查询。

    1.  nuget引入DynamicLinq:

    Install-Package Microsoft.EntityFrameworkCore.DynamicLinq -Version 1.0.19

    2. 定义EFCore 查询实体类:

        public class CarModelContext : DbContext
        {
            public DbSet<CarEnergyModelEntity> CarEnergyModels { get; set; }
    
            public CarModelContext(DbContextOptions<CarModelContext> options) : base(options)
            {
            }
        }
    
        [Table("car_energy_model")]
        public class CarEnergyModelEntity
        {
            public CarEnergyModelEntity() { }
    
            [JsonIgnore]
            [Key]
            public Guid Id { get; set; }
    
            [Column("car_version")]
            public string CarVersion { get; set; }
            [Column("car_id")]
            public string CarId { get; set; }
    
            [Column("user_id")]
            public string UserId { get; set; }
    
            [Column("so_version")]
            public string SoVersion { get; set; }
    
            [Column("config_version")]
            public string ConfigVersion { get; set; }
    
            [Column("config_content")]
            public string ConfigContent { get; set; }
    
            [Column("uploadtime")]
            public DateTime UploadTime => DateTime.UtcNow;
        }

    3. Query集合抓取所有QueryString,列举字段的方式 判断字段为null, 并构造查询

            [Route("all")]
            [HttpGet]
            public async Task<List<CarEnergyModelEntity>> GetModeParametersAsync(
               [FromQuery] string carVersion,
               [FromQuery] string carId,
               [FromQuery] string userId,
               [FromQuery] string soVersion,
               [FromQuery] string configVersion,
               [FromQuery] string configContent
                )
            {
           //   这里使用列举字段的方式构造 strWhere
                var query = HttpContext.Request.Query;
                var validQueryArray1 = query.Where(x => (new string[] { "CarVersion", "carId", "userId", "soVersion", "configVersion", "configContent" }).Contains(x.Key, StringComparer.OrdinalIgnoreCase))
                    .Where(x => !string.IsNullOrEmpty(x.Value))
                    .Select(x => x.Key + "=="" + x.Value + """).ToArray();
    
                string strWhere = string.Join(" and ", validQueryArray1);
                strWhere = string.IsNullOrEmpty(strWhere) ? " 1=1" : strWhere;
                var sqlQuery = _context.CarEnergyModels.Where(strWhere);
                 
                return sqlQuery.ToList();
            }    

     EFCore生成的SQL如下:

    SELECT [c].[Id], [c].[car_id], [c].[car_version], [c].[config_content], [c].[config_version], [c].[so_version], [c].[user_id]
    FROM [car_energy_model] AS [c]
    WHERE (((([c].[car_version] = N'FT_Version_3.2') AND ([c].[car_id] = N'CD292FE0900X')) AND ([c].[user_id] = N'u_1960988792x')) AND ([c].[so_version] = N'so_ver1.2')) AND ([c].[config_version] = N'cv_1.2') 

    ok, That‘s all 

    以上查询还可扩展:前端组装排序字符串(orderStr:Uploadtime descending)通过QueryString传给API,API通过DyanmicLinq构造灵活的排序字段

    经过验证,以上过滤和排序都是在SqlServer层面完成的。

    移除恶心的 if、else之后代码是不是看起来更优雅一些。

    总结

    以上场景相信很多开发者都会遇到,特别是进阶到一定水平,移除if/else  的欲望愈加强烈。

    再次强化本文 知识点:   

      DynamicLinq 具备动态形成查询条件的能力,不再依靠lambda 强类型表达式,而是根据构造的过滤和排序字符串,内部解析成查询条件。

    --------------------2019/9/23 下班前更新--------------------------------------

    DynamicLinq  若动态组装String,确实存在 SQL注入问题, 使用placeholder 可避免

    更新代码:

                // 构建动态查询
                var query = HttpContext.Request.Query;
                var validQueryArray1 = query.Where(x => (new string[] { "CarVersion", "carId", "userId", "soVersion", "configVersion", "configContent" }).Contains(x.Key, StringComparer.OrdinalIgnoreCase))
                    .Where(x => !string.IsNullOrEmpty(x.Value));
    
                var predicate = validQueryArray1.Select((x,i) => $"{x.Key}==@{i}").ToArray();
                var paramses = validQueryArray1.Select(x=>x.Value.ToString()).ToArray();
                string strPredicate = string.Join(" and ", predicate);
                strPredicate = string.IsNullOrEmpty(strPredicate) ? " 1=1" : strPredicate;
                var sqlQuery = _context.CarEnergyModels.Where(strPredicate, paramses);
                 
                return sqlQuery.ToList();
  • 相关阅读:
    mysql索引
    springboot mybatis 后台框架平台 shiro 权限 集成代码生成器
    java 企业网站源码模版 有前后台 springmvc SSM 生成静态化
    java springMVC SSM 操作日志 4级别联动 文件管理 头像编辑 shiro redis
    activiti工作流的web流程设计器整合视频教程 SSM和独立部署
    .Net Core中的ObjectPool
    文件操作、流相关类梳理
    .Net Core中的配置文件源码解析
    .Net Core中依赖注入服务使用总结
    消息中间件RabbitMQ(一)
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/11567322.html
Copyright © 2011-2022 走看看