编写优雅代码,从挖掉恶心的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.
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();
编写优雅代码,从挖掉恶心的if/else 开始的更多相关文章
- 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!【转载+整理】
原文地址 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和for-of循环 生成器 Generators 模板字符串 不定参数和默认参数 解构 Destructu ...
- 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!
原文地址 迁移到:http://www.bdata-cap.com/newsinfo/1741515.html 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和f ...
- 如果让莎士比亚、海明威编写JavaScript代码
本文作者Angus Croll是Twitter工程师.JavaScript迷.文学迷,并且非常喜欢作家海明威.他在梦中"梦见"一些名人编写JavaScript代码,不同的作家呈现出 ...
- .Net高级进阶,在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码?
本文将通过场景例子演示,来通俗易懂的讲解在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码. 通过一系列优化最终达到两个效果,1.通过代码块来控制事务(分布式事务),2.通过委托优化Tran ...
- Guava 教程1-使用 Google Collections,Guava,static imports 编写漂亮代码
原文出处: oschina (API:http://ifeve.com/category/framework/guava-2/ JAR DOC Source 链接:http://pan.baidu.c ...
- 如何运用多阶构建编写优雅的Dockerfile
导读 Kubernetes要从容器化开始,而容器又需要从Dockerfile开始,本文将介绍如何写出一个优雅的Dockerfile文件. 文章主要内容包括: Docker容器 Dockerfile 使 ...
- 如何更规范化编写Java 代码
如何更规范化编写Java 代码 Many of the happiest people are those who own the least. But are we really so happy ...
- myeclipse 编写java代码提示 dead code 原因
经常使用MyEclipse或Eclipse编辑器编写java代码的程序员,可能经常遇到一个黄线警告提示:dead code:一般程序员遇到这些问题都会置之不理,反正也不影响程序的编译执行.对,这不是b ...
- 使用 Vert.X Future/Promise 编写异步代码
Future 和 Promise 是 Vert.X 4.0中的重要角色,贯穿了整个 Vert.X 框架.掌握 Future/Promise 的用法,是用好 Vert.X.编写高质量异步代码的基础.本文 ...
随机推荐
- ubuntu16.04安装Ros(kinetic版本)【亲测好用】
准备 1.ubuntu16.04 64位桌面版 ps:关于系统的下载和安装这里不做介绍,请自行百度,不是介绍重点 2.更改源 图上的几个勾默认是选上的,如果没有选上,选成上图这样(如果修改过勾,点击关 ...
- [HNOI2008]玩具装箱toy(斜率优化dp)
前言 这是我写的第一道$dp$斜率优化的题目,$dp$一直都很菜,而且咖啡鸡都说了这是基础的东西,然而看别人对$dp$斜率优化一大堆公式又看不懂就老老实实做几道题目,这个比较实在 描述 给出$n$和$ ...
- Django上线部署之IIS
环境: 1.Windows Server 2016 Datacenter 64位 2.SQL Server 2016 Enterprise 64位 3.Python 3.6.0 64位 4.admin ...
- 消息中间件——RabbitMQ(六)理解Exchange交换机核心概念!
前言 来了解RabbitMQ一个重要的概念:Exchange交换机 1. Exchange概念 Exchange:接收消息,并根据路由键转发消息所绑定的队列. 蓝色框:客户端发送消息至交换机,通过路由 ...
- LoRaWAN stack移植笔记(六)_调试2
前言 调试的过程中碰到的问题基本都是以前没有遇到过的,而且需要对整个协议栈及射频方面的工作流程较熟悉才能找到问题的原因,需要多读SX1276的数据手册以及与射频芯片的物理层通信例程和MAC层通信例程进 ...
- linux 目录大小 文件个数 基于文件大小排列显示
显示硬盘占用空间du -hlsblk 查看指定目录大小du -sh /opt查看各个目录大小du -h --max-depth=1 当前目录的全部文件个数(包含子文件夹的文件)ls -lR | gre ...
- C#简单爬取数据(.NET使用HTML解析器ESoup和正则两种方式匹配数据)
一.获取数据 想弄一个数据库,由于需要一些人名,所以就去百度一下,然后发现了360图书馆中有很多人名 然后就像去复制一下,发现复制不了,需要登陆 此时f12查看源码是可以复制的,不过就算可以复制想要插 ...
- python 30 基于TCP协议的socket通信
目录 1. 单对单循环通信 2. 循环连接通信:可连接多个客户端 3. 执行远程命令 4. 粘包现象 4.1 socket缓冲区 4.2 出现粘包的情况: 4.3 解决粘包现象 bytes 1. 单对 ...
- Leetcode之深度优先搜索(DFS)专题-690. 员工的重要性(Employee Importance)
Leetcode之深度优先搜索(DFS)专题-690. 员工的重要性(Employee Importance) 深度优先搜索的解题详细介绍,点击 给定一个保存员工信息的数据结构,它包含了员工唯一的id ...
- MySQL的count(*)的优化,获取千万级数据表的总行数[转]
一.前言 这个问题是今天朋友提出来的,关于查询一个1200w的数据表的总行数,用count(*)的速度一直提不上去.找了很多优化方案,最后另辟蹊径,选择了用explain来获取总行数. 二.关于cou ...