编写优雅代码,从挖掉恶心的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();

原文地址:https://www.cnblogs.com/JulianHuang/p/11567322.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


引言 本文从Linux小白的视角, 在CentOS 7.x服务器上搭建一个Nginx-Powered AspNet Core Web准生产应用。 在开始之前,我们还是重温一下部署原理,正如你所常见的.Net Core 部署图: 在Linux上部署.Net Core App最好的方式是在Linux机器
引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式。 相信很多开发者都看到如下异步编程实践原则: 遵守以上冷冰冰的②③条的原则,可保证异步程序按照预期状态正常运作;我们在各大编程论坛常看到违背
一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件。 每个组件是pipeline 中的一环。 自行决定是否将请求传递给下一个组件 在处理管道的下个组件执行之前和之后执行业务逻辑 二. 特性和行为 ASP.NET Core处
背景 在.Net和C#中运行异步代码相当简单,因为我们有时候需要取消正在进行的异步操作,通过本文,可以掌握 通过CancellationToken取消任务(包括non-cancellable任务)。 Task&#160;表示无返回值的异步操作, 泛型版本Task&lt;TResult&gt;表示有返
HTTP基本认证 在HTTP中,HTTP基本认证(Basic Authentication)是一种允许网页浏览器或其他客户端程序以(用户名:口令) 请求资源的身份验证方式,不要求cookie,session identifier、login page等标记或载体。 - 所有浏览器据支持HTTP基本认
1.Linq 执行多列排序 OrderBy的意义是按照指定顺序排序,连续两次OrderBy,后面一个有可能会打乱前面一个的排序顺序,可能与预期不符。 要实现sql中的order by word,name类似效果; LINQ 有ThenBy可以紧接使用, ThenBy记住原本排序的值,然后再排其他值,
ASP.NET Core 核心特性:开源、跨平台、高性能是其决战JAVA的必胜法宝,最引人关注的跨平台特性 到底是怎么实现? &#xA; 本文分Unix、Windows剖析跨平台内幕,读完让你大呼过瘾。
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现。 IAsyncResult BeginGetResponse(AsyncCallback callback, object state
引言 最近在公司开发了一个项目,项目部署架构图如下: 思路 如图中文本所述,公司大数据集群不允许直接访问外网,需要一个网关服务器代理请求,本处服务器A就是边缘代理服务器的作用。 通常技术人员最快捷的思路是在服务器A上部署IISʺpplication Request Routing Module组件
作为一枚后端程序狗,项目实践常遇到定时任务的工作,最容易想到的的思路就是利用Windows计划任务/wndows service程序/Crontab程序等主机方法在主机上部署定时任务程序/脚本。 但是很多时候,若使用的是共享主机或者受控主机,这些主机不允许你私自安装exe程序、Windows服务程序
引言 熟悉TPL Dataflow博文的朋友可能记得这是个单体程序,使用TPL Dataflow 处理工作流任务, 在使用Docker部署的过程中, 有一个问题一直无法回避: 在单体程序部署的瞬间(服务不可用)会有少量流量无法处理;更糟糕的情况下,迭代部署的这个版本有问题,上线后无法运作, 更多的流
合格的web后端程序员,除搬砖技能,还必须会给各种web服务器配置Https,本文结合ASP.NET Core部署模型聊一聊启用Https的方式。 温故知新 目前常见的Http请求明文传输,请求可能被篡改,访问的站点可能被伪造。 HTTPS是HTTP加上TLS/SSL协议构建的可进行加密传输、身份认
长话短说 前文《解剖HttpClientFactory,自由扩展HttpMessageHandler》主要讲如何为HttpClientFactory自定义HttpMessageHandler组件, 现在来完成课后的小作业: 将重点日志字段显示到Nlog的LayoutRenderer上。 本文实现一个
引言问题 作为资深老鸟,有事没事,出去面试;找准差距、定位价值。 面试必谈哈希, Q1:什么是哈希? Q2:哈希为什么快? Q3:你是怎么理解哈希算法利用空间换取时间的? Q4:你是怎么解决哈希冲突的? Q5:你有实际用写过哈希算法吗? 知识储备 哈希(也叫散列)是一种查找算法(可用于插入),哈希算
前言 如题,有感于博客园最近多次翻车,感觉像胡子眉毛一把抓, 定位不了生产环境的问题。 抛开流程问题,思考在生产环境中如何做故障排除,&#160;发现博客园里面这方面的文章比较少。 .Net 本身是提供了sos.dll工具帮助我们在生产中故障排除,通过提供有关内部公共语言运行时(CLR)环境的信息,
.NET程序是基于.NET Framework、.NET Core、Mono、【.NET实现】开发和运行的 ,定义以上【.NET实现】的标准规范称为.NET Standard .NET Standard .NET标准是一组API集合,由上层三种【.NET实现】的Basic Class Library
长话短说 上个月公司上线了一个物联网数据科学项目,我主要负责前端接受物联网事件,并提供 参数下载。 webapp 部署在Azure云上,参数使用Azure SQL Server存储。 最近从灰度测试转向全量部署之后,日志时常收到: SQL Session超限报错。 排查 我在Azure上使用的是 S
临近年关,搜狗,360浏览器出现页面无法成功跳转,同域Cookie丢失? 也许是服务端 SameSite惹的祸。&#xA;本文揭示由于Chrome低版本内核不识别 SameSite= None, 引发的单点登录故障。
本文聊一聊TraceID的作用和一般组成,衍生出ASP. NETCore 单体和分布式程序中 TraceId 的使用方式
通过给 HttpClint请求的日志增加 TraceId,解锁自定义扩展 HttpClientFacroty 的姿势