背景

  长话短说, 作为开发人员经常需要根据条件灵活(过滤+排序)数据库,不管你是用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 开始的更多相关文章

  1. 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!【转载+整理】

    原文地址 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和for-of循环 生成器 Generators 模板字符串 不定参数和默认参数 解构 Destructu ...

  2. 最新的JavaScript核心语言标准——ES6,彻底改变你编写JS代码的方式!

    原文地址 迁移到:http://www.bdata-cap.com/newsinfo/1741515.html 本文内容 ECMAScript 发生了什么变化? 新标准 版本号6 兑现承诺 迭代器和f ...

  3. 如果让莎士比亚、海明威编写JavaScript代码

    本文作者Angus Croll是Twitter工程师.JavaScript迷.文学迷,并且非常喜欢作家海明威.他在梦中"梦见"一些名人编写JavaScript代码,不同的作家呈现出 ...

  4. .Net高级进阶,在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码?

    本文将通过场景例子演示,来通俗易懂的讲解在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码. 通过一系列优化最终达到两个效果,1.通过代码块来控制事务(分布式事务),2.通过委托优化Tran ...

  5. Guava 教程1-使用 Google Collections,Guava,static imports 编写漂亮代码

    原文出处: oschina (API:http://ifeve.com/category/framework/guava-2/ JAR DOC Source 链接:http://pan.baidu.c ...

  6. 如何运用多阶构建编写优雅的Dockerfile

    导读 Kubernetes要从容器化开始,而容器又需要从Dockerfile开始,本文将介绍如何写出一个优雅的Dockerfile文件. 文章主要内容包括: Docker容器 Dockerfile 使 ...

  7. 如何更规范化编写Java 代码

    如何更规范化编写Java 代码 Many of the happiest people are those who own the least. But are we really so happy ...

  8. myeclipse 编写java代码提示 dead code 原因

    经常使用MyEclipse或Eclipse编辑器编写java代码的程序员,可能经常遇到一个黄线警告提示:dead code:一般程序员遇到这些问题都会置之不理,反正也不影响程序的编译执行.对,这不是b ...

  9. 使用 Vert.X Future/Promise 编写异步代码

    Future 和 Promise 是 Vert.X 4.0中的重要角色,贯穿了整个 Vert.X 框架.掌握 Future/Promise 的用法,是用好 Vert.X.编写高质量异步代码的基础.本文 ...

随机推荐

  1. net core Webapi基础工程搭建(七)——小试AOP及常规测试_Part 2

    目录 前言 引入 自定义属性 测试 小结 前言 前一篇讲到了中间层的使用,可能不是那么AOP,今天主要来说下一个轻量级的AOP第三方类库AspectoCore. 简单介绍下这个类库,AspectCor ...

  2. 给定n个十六进制正整数,输出它们对应的八进制数。

    问题描述 给定n个十六进制正整数,输出它们对应的八进制数. 输入格式 输入的第一行为一个正整数n (1<=n<=10). 接下来n行,每行一个由0~9.大写字母A~F组成的字符串,表示要转 ...

  3. Nginx安装之源码安装

    nginx部署 1. 安装依赖 yum install gcc gccc++ pcre pcre-devel zlib zlib-devel openssl openssl-devel-y 2. 下载 ...

  4. 实验Oracle数据文件被误删除的场景恢复

    环境:RHEL 5.4 + Oracle 11.2.0.3 背景:数据库没有备份,数据库文件被误操作rm,此时数据库尚未关闭,也就是对应句柄存在,如何快速恢复? 1.某个普通数据文件被删除 2.所有数 ...

  5. rabbitmq集群操作与启停

    一.rabbitmq集群必要条件 1.1. 绑定实体ip,即ifconfig所能查询到的绑定到网卡上的ip,以下是绑定方法 1.2. 配置域名映射到实体ip 二.启动停止 2.1 停止 2.2 启动 ...

  6. SpringCloud阶段总结

    学习时间:8.15 -- 8.29 学习目标:了解SpringCloud常见组件的使用 学习方式: 输入:视频+博客+开源项目代码参考 输出:调试代码+写博客输出 组件列表 服务注册:Eureka 客 ...

  7. HTML 画布(摘自菜鸟教程)

    颜色.样式和阴影 属性 描述 fillStyle 设置或返回用于填充绘画的颜色.渐变或模式. strokeStyle 设置或返回用于笔触的颜色.渐变或模式. shadowColor 设置或返回用于阴影 ...

  8. MySQL如何选择合适的索引

    先来看一个栗子 EXPLAIN select * from employees where name > 'a'; 如果用name索引查找数据需要遍历name字段联合索引树,然后根据遍历出来的主 ...

  9. Spring Cloud开发人员如何解决服务冲突和实例乱窜?(IP实现方案)

    一.背景 在我上一篇文章<Spring Cloud开发人员如何解决服务冲突和实例乱窜?>中提到使用服务的元数据来实现隔离和路由,有朋友问到能不能直接通过IP来实现?本文就和大家一起来讨论一 ...

  10. 你真的了解MyBatis中${}和#{}的区别吗?

    动态sql是mybatis的主要特性之一.在mapper中定义的参数传到xml中之后,在查询之前mybatis会对其进行动态解析. mybatis提供了两种支持动态sql的语法:#{} 和 ${}. ...