编写优雅代码,从挖掉恶心的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.编写高质量异步代码的基础.本文 ...
随机推荐
- 如何利用jenkins插件查看allure报告-----完整篇(解决404和无数据问题)
背景: python3+appium+pytest+allure写了安卓的自动化脚本,在windows本机pycharm上跑通过后生成了allure报告. 公司jenkins搭建在linux服务器上 ...
- Container及其内部进程监控剖析
目前市场上的虚拟化技术种类很多,例如moby(docker).LXC.RKT等等.在带来方便应用部署和资源充分利用的好处的同时,如何监控相应Container及其内部应用进程成为运维人员不可避免遇到的 ...
- 基于mybatisPlus的特殊字符校验
要实现以下代码前提是导入Mybatis-plus的jar: * @author WENGKAIBO505 */ @Target({ElementType.FIELD, ElementType.METH ...
- 解决mysql乱码
总结的几个乱码问题 希望我们全体学员也能够学会总结 java web 很是希望大家能够学好.并且也希望大家能够在学习过程中不段的积累相关的知识点 1.在response中写<meta http ...
- CSS布局:元素水平垂直居中
CSS布局:元素水平垂直居中 本文将依次介绍在不同条件下实现水平垂直居中的多种方法 水平垂直居中是在写网页时经常会用到的需求,在上两篇博客中,分别介绍了水平居中和垂直居中的方法.本文的水平垂直居中就是 ...
- Selenium3 + Python3自动化测试系列十——调用JavaScript代码
调用JavaScript代码 一.调用JavaScript代码方法 Selenium在对浏览器操作时会有自动化代码中不稳定的部分,经常出错的部分,可以将这部分对网页元素进行操作的代码换成对应的Java ...
- 四种为HttpClient添加默认请求报头的解决方案
HttpClient在Web调用中具有广泛的应用,而为它添加默认请求头是我们经常遇到的需求,本文介绍4种为HttpClient添加默认请求头的方式. 第一种方式 直接在创建的HttpClient对象的 ...
- git 如何实现进行多人协作开发(远程仓库)
第一.Git作为分布式的版本控制系统,你是你本地仓库的主人,但是想要实现多人的协作开发,你就要将你本地的开发推送到远程共享仓库中供大家下载,本篇主要以github作为远程服务器来介绍有关远程仓库这块内 ...
- NLP(二十) 利用词向量实现高维词在二维空间的可视化
准备 Alice in Wonderland数据集可用于单词抽取,结合稠密网络可实现其单词的可视化,这与编码器-解码器架构类似. 代码 from __future__ import print_fun ...
- [python]文档字符串
文档字符串可以在运行时访问,也可以用来自动生成文档. 输入: def foo(): print "This is a doc string" return True foo() 运 ...