前言

最近几乎每天40度,越热越不想面对电脑,还好开源项目都比较稳定没那么多待解决问题,趁着暑假带着女儿学习游泳已略有小成。游泳好处太多了,建议有孩子的都去学学,我是在岸边指导大约一周左右就学会了,目前可游200米。

FreeSql 有一个用户很迷的功能 WhereDynamicFilter 动态表格查询,本文讲解它的设计初衷,如何高效理解,从此不再迷惑。

小时候学习编程,老师经常教导我们,程序 = 数据结构 + 算法,今天就以我自身的认知讲解该功能的完整设计过程,其中包含数据结构和算法。


ORM概念

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

FreeSql 是 .Net ORM,能支持 .NetFramework4.0+、.NetCore、Xamarin、MAUI、Blazor、以及还有说不出来的运行平台,因为代码绿色无依赖,支持新平台非常简单。目前单元测试数量:8500+,Nuget下载数量:900K+。QQ群:4336577(已满)、8578575(在线)、52508226(在线)

FreeSql 使用最宽松的开源协议 MIT https://github.com/dotnetcore/FreeSql ,完全可以商用,文档齐全,甚至拿去卖钱也可以。

FreeSql 主要优势在于易用性上,基本是开箱即用,在不同数据库之间切换兼容性比较好,整体的功能特性如下:

  • 支持 CodeFirst 对比结构变化迁移;
  • 支持 DbFirst 从数据库导入实体类;
  • 支持 丰富的表达式函数,自定义解析;
  • 支持 批量添加、批量更新、BulkCopy;
  • 支持 导航属性,贪婪加载、延时加载、级联保存;
  • 支持 读写分离、分表分库,租户设计;
  • 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/人大金仓/翰高/MsAccess Ado.net 实现包,以及 Odbc 的专门实现包;

8500+个单元测试作为基调,支持10多数数据库,我们提供了通用Odbc理论上支持所有数据库,目前已知有群友使用 FreeSql 操作华为高斯、mycat、tidb 等数据库。安装时只需要选择对应的数据库实现包:

dotnet add packages FreeSql.Provider.MySql


需求矛盾

虽然 ORM 有理论定义支撑,但实际开发过程中,难免遇到动态查询的需求,常见的有后台管理系统用户自定义过滤查询,如:

鉴于实际与理论的矛盾,导致很多非常实用的功能类库让一些人诟病,指这是 SqlHelper,并非 ORM,在此不便理论,功过自在人心。


数据结构

数据结构的定义,决定了功能的使用深度,这块也参考了一些竟品类似的功能,实际在 .NET ORM 领域很少有完美并简单的现实,要么使用太复杂,要么不支持深层级。

类似的功能其实市面产品应用挺广泛,几乎已经形成了一套成熟的产品规则。如果不是亲身经历过类似产品,是很难定义出完美的数据结构的,作为一个公众开源项目,API 一旦确定再改是非常痛苦的决定,用户升级不兼容的情况不仅会影响 FreeSql 口碑,还会让使用者进退两难,到底要不要升级?好在 FreeSql 从 2018 年最初理念保持至今,关于前后破坏性升级几乎没有。

最终根据对 SQL 逻辑表达式的理解,加上参考 JAVA 一个知名的后台开源框架,取长补短确定了最终数据结构。

说这么多无外乎三个重点:

1、自己不熟悉的,多方面学习,接纳更成熟的方案;

2、自己要是没想好怎么做,多观察再做;

3、多思考用户场景;

我们需要考虑的场景有以下几种:

1、WHERE id = 1

{
"Field": "id",
"Operator": "Equals",
"Value": 1
}

2、WHERE id = 1 AND id = 2

{
"Logic": "And",
"Filters":
[
{
"Field": "id",
"Operator": "Equals",
"Value": 1
},
{
"Field": "id",
"Operator": "Equals",
"Value": 2
}
]
}

3、WHERE id IN (1,2)

{
"Field": "id",
"Operator": "Contains",
"Value": [1,2] //或者 "1,2"
}

4、WHERE id = 1 OR id = 2

{
"Logic": "Or",
"Filters":
[
{
"Field": "id",
"Operator": "Equals",
"Value": 1
},
{
"Field": "id",
"Operator": "Equals",
"Value": 2
}
]
}

5、WHERE id = 1 AND (id = 2 OR id = 3)

注意优先级,它不是 id = 1 AND id = 2 OR id = 3

{
"Logic": "And",
"Filters":
[
{
"Field": "id",
"Operator": "Equals",
"Value": 1
},
{
"Logic": "Or",
"Filters":
[
{
"Field": "id",
"Operator": "Equals",
"Value": 2
},
{
"Field": "id",
"Operator": "Equals",
"Value": 3
}
]
}
]
}

第5个例子最特别,这也是为什么 WhereDynamicFilter 数据结构定义成树型的主要原因。

关于 Operator 我们需要以下使用场景:

  • Contains/StartsWith/EndsWith/NotContains/NotStartsWith/NotEndsWith:包含/不包含,like '%xx%',或者 like 'xx%',或者 like '%xx'
  • Equal/NotEqual:等于/不等于
  • GreaterThan/GreaterThanOrEqual:大于/大于等于
  • LessThan/LessThanOrEqual:小于/小于等于
  • Range:范围查询
  • DateRange:日期范围,有特殊处理 value[1] + 1
  • Any/NotAny:是否符合 value 中任何一项(直白的说是 SQL IN)
  • Custom:自定义解析

最终完整的 c# 数据结构类定义如下:

/// <summary>
/// 动态过滤条件
/// </summary>
[Serializable]
public class DynamicFilterInfo
{
/// <summary>
/// 属性名:Name<para></para>
/// 导航属性:Parent.Name<para></para>
/// 多表:b.Name<para></para>
/// </summary>
public string Field { get; set; }
/// <summary>
/// 操作符
/// </summary>
public DynamicFilterOperator Operator { get; set; }
/// <summary>
/// 值
/// </summary>
public object Value { get; set; } /// <summary>
/// Filters 下的逻辑运算符
/// </summary>
public DynamicFilterLogic Logic { get; set; }
/// <summary>
/// 子过滤条件,它与当前的逻辑关系是 And<para></para>
/// 注意:当前 Field 可以留空
/// </summary>
public List<DynamicFilterInfo> Filters { get; set; }
} public enum DynamicFilterLogic { And, Or }
public enum DynamicFilterOperator
{
/// <summary>
/// like
/// </summary>
Contains,
StartsWith,
EndsWith,
NotContains,
NotStartsWith,
NotEndsWith, /// <summary>
/// =<para></para>
/// Equal/Equals/Eq 效果相同
/// </summary>
Equal,
/// <summary>
/// =<para></para>
/// Equal/Equals/Eq 效果相同
/// </summary>
Equals,
/// <summary>
/// =<para></para>
/// Equal/Equals/Eq 效果相同
/// </summary>
Eq,
/// <summary>
/// &lt;&gt;
/// </summary>
NotEqual, /// <summary>
/// &gt;
/// </summary>
GreaterThan,
/// <summary>
/// &gt;=
/// </summary>
GreaterThanOrEqual,
/// <summary>
/// &lt;
/// </summary>
LessThan,
/// <summary>
/// &lt;=
/// </summary>
LessThanOrEqual, /// <summary>
/// &gt;= and &lt;<para></para>
/// 此时 Value 的值格式为逗号分割:value1,value2 或者数组
/// </summary>
Range, /// <summary>
/// &gt;= and &lt;<para></para>
/// 此时 Value 的值格式为逗号分割:date1,date2 或者数组<para></para>
/// 这是专门为日期范围查询定制的操作符,它会处理 date2 + 1,比如:<para></para>
/// 当 date2 选择的是 2020-05-30,那查询的时候是 &lt; 2020-05-31<para></para>
/// 当 date2 选择的是 2020-05,那查询的时候是 &lt; 2020-06<para></para>
/// 当 date2 选择的是 2020,那查询的时候是 &lt; 2021<para></para>
/// 当 date2 选择的是 2020-05-30 12,那查询的时候是 &lt; 2020-05-30 13<para></para>
/// 当 date2 选择的是 2020-05-30 12:30,那查询的时候是 &lt; 2020-05-30 12:31<para></para>
/// 并且 date2 只支持以上 5 种格式 (date1 没有限制)
/// </summary>
DateRange, /// <summary>
/// in (1,2,3)<para></para>
/// 此时 Value 的值格式为逗号分割:value1,value2,value3... 或者数组
/// </summary>
Any,
/// <summary>
/// not in (1,2,3)<para></para>
/// 此时 Value 的值格式为逗号分割:value1,value2,value3... 或者数组
/// </summary>
NotAny, /// <summary>
/// 自定义解析,此时 Field 为反射信息,Value 为静态方法的参数(string)<para></para>
/// 示范:{ Operator: "Custom", Field: "RawSql webapp1.DynamicFilterCustom,webapp1", Value: "(id,name) in ((1,'k'),(2,'m'))" }<para></para>
/// 注意:使用者自己承担【注入风险】<para></para>
/// 静态方法定义示范:<para></para>
/// namespace webapp1<para></para>
/// {<para></para>
/// public class DynamicFilterCustom<para></para>
/// {<para></para>
/// [DynamicFilterCustom]<para></para>
/// public static string RawSql(object sender, string value) => value;<para></para>
/// }<para></para>
/// }<para></para>
/// </summary>
Custom
} /// <summary>
/// 授权 DynamicFilter 支持 Custom 自定义解析
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class DynamicFilterCustomAttribute : Attribute { }

安全考虑

由于 ISelect.WhereDynamicFilter 方法实现动态过滤条件(与前端交互),在 SQL 注入安全防御这块一定要进行到底,主要思考如下:

1、Field 只允许传递 c# 实体属性名(不支持使用数据库字段名,甚至直接使用 SQL 内容段);

2、Operator 只允许规定的枚举操作类型;

3、Value 必须根据 Operator 进行强制类型检查,比如 "1,2" + Any 检索出来的数据是 int[] { 1,2 };

4、Operator Custom 类型支持用户自行扩展,可现实更自由的查询;


算法

如果把数据结构定义成灵魂,那算法就是驱壳,实现 WhereDynamicFilter 的核心算法是递归树结构。

感兴趣的朋友可以直接去源码查看实现:https://github.com/dotnetcore/FreeSql


难理解

WhereDynamicFilter 功能2020年上线到现在,我个人都觉得其实蛮难理解的,更不要提很多使用者反馈。主要原因是数据结构为树结构,通常80%的人只是简单的一层 AND/OR 需求,他们很少会遇到深层级的自定义查询。

但是作为功能性 ORM 类库,应该满足更多适用范围,而不是妥协为求简单来做。

其实便于理解也不难,只要掌握以下方法:

1、Logic 是设置 Filters 数组下的逻辑关系(这很重要,一定要理解正确)

为了解决 WHERE id = 1 AND (id = 2 OR id = 3) 优先级问题,Filters 更像一对括号

{
"Logic": "And",
"Filters":
[
{ "Field": "id", "Operator": "Equals", "Value": 1 },
{
"Logic": "Or",
"Filters":
[
{ "Field": "id", "Operator": "Equals", "Value": 2 },
{ "Field": "id", "Operator": "Equals", "Value": 3 }
]
}
]
}

2、Field/Operator/Value 与 Logic/Filters 不要同时设置(避免理解困难)

3、删除 JSON 中不必要的内容

这个病不好治,因为强类型对象产生的默认 json 内容,即使无用的属性也序列化了。

{
"Field": null,
"Operator": "And",
"Value": null,
"Logic": "Or",
"Filters":
[
{
"Field": "Name-1",
"Operator": "Equals",
"Value": "ye-01",
"Logic": "And",
"Fitlers": null
},
{
"Field": "Name-1",
"Operator": "Equals",
"Value": "ye-02",
"Logic": "And",
"Fitlers": null
}
]
}

以上类型改成如下,是不是更好理解?

{
"Logic": "Or",
"Filters":
[
{
"Field": "Name-1",
"Operator": "Equals",
"Value": "ye-01"
},
{
"Field": "Name-1",
"Operator": "Equals",
"Value": "ye-02"
}
]
}

最终功能

一个任意定制的高级查询功能预览如下:

前端只需要按要求组装好 DynamicFilterInfo 对应的 JSON 数据内容,后台就可轻易完成高级过滤查询,有多轻易呢?

var dyfilter = JsonConvert.DeserializeObject<DynamicFilterInfo>(jsonText);
var list = fsql.Select<T>().WhereDynamicFilter(dyfilter).ToList();

结束语

希望这篇文章能帮助大家从 WhereDynamicFilter 的设计初衷,轻松理解并熟练掌握它,为企业的项目研发贡献力量。

开源地址:https://github.com/dotnetcore/FreeSql


作者是什么人?

作者是一个入行 18年的老批,他目前写的.net 开源项目有:

开源项目描述开源地址开源协议
ImCore架构最简单,扩展性最强的聊天系统架构https://github.com/2881099/im最宽松的 MIT 协议,可商用
FreeRedis最简单的 RediscClienthttps://github.com/2881099/FreeRedis最宽松的 MIT 协议,可商用
csredishttps://github.com/2881099/csredis最宽松的 MIT 协议,可商用
FightLandlord斗地主单机或网络版https://github.com/2881099/FightLandlord最宽松的 MIT 协议,学习用途
IdleScheduler定时任务https://github.com/2881099/IdleBus/tree/master/IdleScheduler最宽松的 MIT 协议,可商用
IdleBus空闲容器https://github.com/2881099/IdleBus最宽松的 MIT 协议,可商用
FreeSql国产最好用的 ORMhttps://github.com/dotnetcore/FreeSql最宽松的 MIT 协议,可商用
FreeSql.Cloud分布式事务tcc/sagahttps://github.com/2881099/FreeSql.Cloud最宽松的 MIT 协议,可商用
FreeSql.AdminLTE低代码后台管理项目生成https://github.com/2881099/FreeSql.AdminLTE最宽松的 MIT 协议,可商用
FreeSql.DynamicProxy动态代理https://github.com/2881099/FreeSql.DynamicProxy最宽松的 MIT 协议,学习用途

需要的请拿走,这些都是最近几年的开源作品,以前更早写的就不发了。

QQ群:4336577(已满)、8578575(在线)、52508226(在线)

【设计过程】.NET ORM FreeSql WhereDynamicFilter 动态表格查询功能的更多相关文章

  1. [开源] .Net ORM FreeSql 1.8.0-preview 最新动态播报(番号:我还活着)

    写在开头 FreeSql 是 .NET 开源生态下的 ORM 轮子,在一些人眼里属于重复造轮子:不看也罢.就像昨天有位朋友截图某培训直播发给我看,内容为:"FreeSQL(个人产品),自己玩 ...

  2. [开源] .Net orm FreeSql 1.5.0 最新版本(番号:好久不见)

    废话开头 这篇文章是我有史以来编辑最长时间的,历时 4小时!!!原本我可以利用这 4小时编写一堆胶水代码,真心希望善良的您点个赞,谢谢了!! 很久很久没有写文章了,上一次还是在元旦发布 1.0 版本的 ...

  3. [开源] .Net ORM FreeSql 1.10.0 稳步向行

    写在开头 FreeSql 是 .NET 开源生态下的 ORM 轮子,转眼快两年了,说真的开源不容易(只有经历过才明白).今天带点干货和湿货给大家,先说下湿货. 认识我的人,知道 CSRedisCore ...

  4. 小房子配置开发实例-IT资产管理(资产类管理)--开发设计过程

    小房子(Houselet)作为一个集开发和应用为一体的管理软件平台,通过数据库配置开发的方式来开发管理系统:目的在于辅助企业低成本快速建设管理系统.且系统为开放的,随时可以维护升级的,随企业管理的需要 ...

  5. 从涂鸦到发布——理解API的设计过程(转)

    英文原文:From Doodles to Delivery: An API Design Process 要想设计出可以正常运行的Web API,对基于web的应用的基本理解是一个良好的基础.但如果你 ...

  6. 项目实践 hrm项目的设计过程

    人事管理系统的设计过程 一.数据库表和持久化类 1.1   进行需求分析,根据功能模块设计数据库表 1.2   设计持久化实体 面向对象分析,即根据系统需求提取出应用中的对象,将这些对象抽象成类,再抽 ...

  7. cpu设计过程

    一款CPU是如何设计出来的? 前面一段,我们了解了芯片的制造过程,也就是如何从沙子中提取硅.把硅切成片,在片上通过离子注入实现PN结.实现各种二极管.三极管.CMOS管.从而实现千万门级大规模集成电路 ...

  8. 在&lt;s:iterator&gt;标签里给动态表格添加序号

    在<s:iterator>标签里给动态表格添加序号,需要用到<s:iterator>标签里的Status属性里的count eg:<s:iterator value=&q ...

  9. HTML5&amp;CSS3经典动态表格

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. 5.11-5.15javascript制作动态表格

    制作动态表格的主要是运用js中表格类的insertRow.insertCell简易添加行和列的代码,不过要注意每行添加的表格是有位置行编号的,每行的编号为rows.length-1,增加的表格内的标签 ...

随机推荐

  1. 1.4.2 solr字段类型--(1.4.2.6)使用外部文件和程序

    1.4.2 solr字段类型 (1.4.2.1) 字段类型定义和字段类型属性. (1.4.2.2) solr附带的字段类型 (1.4.2.3) 使用货币和汇率 (1.4.2.4) 使用Dates(日期 ...

  2. OnePlus One(一加1)刷机Kali Nethunter完整教程

    设备信息: 设备名称:OnePlus One(一加1) OS:ColorOS 1.2 设备型号:A0001 目标: 在OnePlus One(一加1)上将 ColorOS 1.2 刷机为 Kali N ...

  3. css模板

    最近好多人问我博客的css模板.... 现在是高三,没多少时间,趁放假赶紧更一下 主体就是把博客园的一个模板改动了一点 上面的图片特效,也是从别人那里得到的代码,大致就是下面那些,下面的三个图片换成自 ...

  4. NLP+VS︱深度学习数据集标注工具、方法摘录,欢迎补充~~

    ~~因为不太会使用opencv.matlab工具,所以在找一些比较简单的工具. . . 一.NLP标注工具BRAT BRAT是一个基于web的文本标注工具,主要用于对文本的结构化标注,用BRAT生成的 ...

  5. java的制作&quot;时间账本&quot;

    一直以来我都感觉自己的时间过得好荒废啊,貌似只是打开了一个网页链接的时间,一个下午便过去了:仿佛就是看了看空间,刷了刷微信,一天就过去了.哈,当然这是夸张的说法.但是我仔细地算了一下,大概我们每个人每 ...

  6. hbase搭建

    0. 软件版本下载 http://mirror.bit.edu.cn/apache/hbase/   1. 集群环境 Master 172.16.11.97 Slave1 172.16.11.98 S ...

  7. P3312 [SDOI2014]数表

    啊啊啊我昨天怎么没写题解wwww 补昨日题解... 题目链接 : https://www.luogu.org/problemnew/show/P3312 也是莫反 我要把fft留到今天写 [和zyn小 ...

  8. 【BZOJ2839】集合计数&amp;&amp;【BZOJ3622】已经没有什么好害怕的了

    再谈容斥原理来两道套路几乎一致的题目[BZOJ2839]集合计数Description一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交 ...

  9. 68.iOS设备尺寸及型号代码(iPhoneXR/XS)

    所有设备型号官网地址: https://www.theiphonewiki.com/wiki/Models iPhone: 机型 像素 比例 像素密度 屏幕尺寸 机型代码 发布日期 iPhone 2g ...

  10. 将hta包装为exe发布

    hta在打开的时候,有时候会被杀毒软件拦截而不给执行,更重要的一点是通常都可以右击查看源代码,里面如果涉及到域名或者其它的一些细节就很容易被其它人了解. 网络上有一些hta转exe的,类似的软件基本上 ...