• 编写优雅代码,从挖掉恶心的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();
  • 相关阅读:
    poj 2251 Dungeon Master-搜索进阶-暑假集训
    棋盘问题-POJ 1321
    Popular Cows
    The Factor
    整数解 (hdu 2092
    Strange fuction hdu 2899
    Factors and Multiples
    Trailing Zeroes (III) -;lightoj 1138
    Swap——hdu 2819
    Arithmetic Sequence
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/11567322.html
Copyright © 2020-2023  润新知