.NET适配HarmonyOS进展 - 布布(CeSun) - 博客园

mikel阅读(899)

来源: .NET适配HarmonyOS进展 – 布布(CeSun) – 博客园

1. 前言
目前国产化系统浪潮下,适配鸿蒙是中国软件大势所趋,.NET作为最适合开发客户端语言之一,适配鸿蒙系统(HarmonyOS Next)是目前.NET开发者最关心的事情。我目前业余时间正在移植Avalonia到HarmonyOS,去年在.NET Conf CN上分享过,目前又取得一点进展,所以本文把所有问题进行整合与大家进行分享。

2. 项目状态
目前.NET可以成功在HarmonyOS Next上运行。

Avalonia移植项目在部分大内存真机上初步可以运行,本文主要探讨.NET适配相关工作。

3. 运行时
自HarmonyOS 5.0.0(12)起,禁止匿名内存申请可执行权限,除系统内置的JavaScript引擎外,其他虚拟机不能使用Jit功能,所以无法将CoreCLR接入到鸿蒙系统中,而最新版的Mono虽然支持解释执行,但是由于性能问题也不会接入Mono到鸿蒙系统,最终只能选择接入NativeAOT运行时。

4. NativeAOT
支撑鸿蒙可以接入NativeAOT的原理是鸿蒙系统兼容libc是musl的Linux系统的动态库(.so)。而.NET的RID支持linux-musl-arm64/linux-musl-x64,所以理论上可以将.NET程序编译为原生的Linux动态库(.so),然后在鸿蒙的原生项目中,通过dlopen以及dlsym等函数调用C#中的入口函数。

C#调用鸿蒙api则通过P/Invoke调用鸿蒙的NDK,而ArkUI的TypeScript api则通过NDK中的napi调用。

具体做法可以参考我正在做的Avalonia移植项目: https://github.com/CeSun/OpenHarmony.Avalonia

5. 已知问题
5.1 syscall限制 (已解决)
鸿蒙系统使用了seccomp限制危险的syscall调用。标准posix下,如果系统不支持某个syscall则返回错误码,而seccomp非常激进,如果调用了非法的sycall则直接杀掉进程。.NET的运行时初始化时,会调用__NR_get_mempolicy系统调用对numa支持进行检查,而这个系统调用不在鸿蒙的seccomp白名单中,所以导致直接宕机。

鸿蒙系统中seccomp的系统调用白名单如下:https://gitee.com/openharmony/startup_init/blob/master/services/modules/seccomp/seccomp_policy/app.seccomp.policy

其实安卓中也有类似的限制,.NET的NativeAOT之所以能在安卓平台下运行是因为.NET中对安卓进行了特殊处理,而在鸿蒙平台我们使用的是Linux平台的代码,所以没有对这些系统调用进行处理。

解决办法则是自行修改代码,将numa的函数全部修改为空函数

5.2 mmap申请虚拟内存过大(已解决)
解决上个问题后,.NET运行时初始化依然不能成功,导致程序崩溃,经过排查发现是GC初始化时会申请256G左右的虚拟内存,导致mmap返回Out Of Memory错误。

解决办法1:设置环境变量“DOTNET_GCHeapHardLimit”,将虚拟内存申请控制在约180G以下即可。

解决办法2:修改源代码,将USE_REGIONS宏关掉。

5.3 ICU,OpenSSL等第三方库缺失(已解决)
解决方案1:从Alpine上偷包 ,因为Alpine的libc是musl,所以理论上Alpine的库在鸿蒙上大部分都能使用。

阿里云Alpine软件包镜像地址:
arm64架构:https://mirrors.aliyun.com/alpine/edge/main/aarch64/
amd64架构:https://mirrors.aliyun.com/alpine/edge/main/x86_64/

解决方案2:如果该库有cmake项目,则可以通过鸿蒙的CMake工具链编译。

5.4 ICU初始化失败(已解决)
鸿蒙的ICU配置文件路径与默认路径不同,需要调用修改环境变量API,将ICU_DATA修改为/system/usr/ohos_icu
且鸿蒙平台上libICU的大版本是72,要使用这个版本的库。

5.5 NativeAOT如何跨平台编译 (Windows平台已解决)
NativeAOT众所周知不支持跨平台编译,而我的方案需要发布到linux-musl平台,所以无法在Windows上发布,影响开发效率。

解决方案:在项目中引入项目https://github.com/CeSun/PublishAotCross

6. 如何修改NativeAOT代码
前文中提到部分问题的解决方案是修改源码,具体操作步骤如下:
修改完代码,执行以下命令进行编译(linux平台下,需要有编译环境):

./build.sh –subset clr.aot –configuration Release -arch arm64 –cross
编译成功后,打开目录 运行时/artifacts/bin/coreclr/linux.arm64.Release/aotsdk,将这里所有的替换到自己电脑nuget的缓存目录, 例如C:\Users\用户名\.nuget\packages\runtime.linux-musl-arm64.microsoft.dotnet.ilcompiler\dotnet版本\sdk

7.相关链接
https://github.com/dotnet/runtime/issues/110074
https://github.com/dotnet/runtime/issues/111649

解决计算机主机与打印机共享打印机,HP M126A打印机共享打印机后必须得主机先打印一次后其他电脑才能打印问题的解决办法...-CSDN博客

mikel阅读(1174)

来源: 解决计算机主机与打印机共享打印机,HP M126A打印机共享打印机后必须得主机先打印一次后其他电脑才能打印问题的解决办法…-CSDN博客

单位的一台HP LaserJet Pro MFP M126a打印机,共享之后,每次主机重启之后,其他电脑无法正常打印,必须要共享的主机先打印一次,之后其他的电脑才可以打印。

无奈只能找官方寻求解决方案,在官网论坛中有官方技术人员给出了如下方案:

1 客户端添加localport端口,方法如下:

点击开始—控制面板–打印机–添加打印机—添加本地打印机—–创建新端口—local port端口—-\\主机的ip地址\共享名称—-下一步选择从磁盘安装,找到下载的驱动程序,点击打开,确定,下一步即可。

共享名的查看方式:开始—打印机—打印机图标—属性—共享—共享名。

主机ip的查看方式:在主机上面,开始—运行—cmd—ipconfig/ALL,就能看到主机的IP。

2 调整主机权限:

a.我的电脑(计算机)右键管理—本地用户和组—用户—guest,账户已停用的对勾去掉,确定。

b.控制面板\系统和安全\管理工具–本地安全策略—本地策略–用户权利指派—从网络访问此计算机,添加everyone和guest(如果有不需要添加)–拒绝从网络访问这台计算机,把 guest删除。

c.控制面板\系统和安全\管理工具–本地安全策略—本地策略–安全选项—网络访问—本地用户的共享和安全的访问模式–经典。

1.C:\Windows\System32\spool,点击drivers文件夹,右键共享(高级共享)–权限—everyone—完全控制勾选,确定。

Win 7操作系统还需要设置控制面板\网络和 Internet\网络和共享中心\高级共享设置,选择当前配置文件—文件和打印机共享—启用。

3 右击计算机图标(我的电脑)–管理—服务 Work station 启动类型要为自动,状态:正在运行。

4 主机端和客户端分别手动安装hp laserjet p1108驱动,主机端测试打印成功后,客户端手动安装hp laserjet p1108驱动并使用localport端口重新测试。

手动添加驱动的方法可参考:

设备和打印机——添加打印机——添加本地——使用以下端口——端口选择usb ——从磁盘安装——浏览到打印机驱动文件夹选.inf后缀文件——打开,确定——下一步直到完成。

Hp laserjet p1108驱动下载链接:

http://h30318.www3.hp.com/pub/softlib/software13/COL32431/bi-80329-11/hp_LJP1100_P1560_P1600_Full_So…

5 换其它电脑做主机。

经过验证,该方法能成功解决问题,但是这个方法太复杂了,既然我们之前已经实现了共享,那么完全可以忽略权限步骤,下面说下我的简单的解决办法:

直接下载Hp laserjet p1108驱动,上面官方给的链接打不开了,直接在https://support.hp.com/cn-zh/drivers/selfservice/hp-laserjet-pro-p1106-p1108-printer-series/5099191这个页面下载“HP LaserJet Pro P1100、P1560 和 P1600 系列全功能软件和驱动程序”,然后解压缩获取打印程序的文件包,然后按照上面官方解决方案中的第4步,手动添加打印机。

之后把主机把新添加的Hp laserjet p1108打印机设置共享,其他电脑使用1108这个打印机即可。

惠普M126a连接共享打印机故障0x000006ba,系统不支持请求的命令,print spooler重复停止_m126a系统不支持请求的命令0x00000077-CSDN博客

mikel阅读(670)

来源: 惠普M126a连接共享打印机故障0x000006ba,系统不支持请求的命令,print spooler重复停止_m126a系统不支持请求的命令-CSDN博客

故障说明:直连惠普M126a打印机正常打印,通过共享连接的报故障。
目前已知有三种故障:

1、0x000006ba报错
2、系统不支持请求的命令
3、print spooler重复停止(或者,print spooler没有停止依然报故障)

解决方法大致如下(自测已解决):

直连打印机的作为打印服务器的主机在安装对应M126a驱动基础上,再安装P1108打印机驱动。
需要用到的两个驱动名称如下图👇👇👇:

1、在安装完M126a驱动后,查看使用的端口号。

2、手动添加打印机

3、手动选择跟上边相同的打印机端口。

4、手动选择下载并解压好的驱动,选择1108

5、安装完成,默认设置共享打印机,测试正常打印。

————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/L_O_V_E8023/article/details/144353088

手把手教你部署 DeepSeek 本地模型 - AlfredZhao - 博客园

mikel阅读(478)

来源: 手把手教你部署 DeepSeek 本地模型 – AlfredZhao – 博客园

本文目标:部署 DeepSeek 本地模型,并通过 Ollama 提供 API 支持,Chatbox 提供 UI 界面。
原则:不搞那些高深的玩法,让小白也能理解并真正的上手实践。

  • 1.下载Ollama并安装运行DeepSeek
  • 2.下载Chatbox并配置为本地DeepSeek
  • 3.无需联网也能和DeepSeek畅快聊天

ds

1.下载Ollama并安装运行DeepSeek

从Ollama官方网站下载Ollama:

进入Ollama官方网站后,可以看到Ollama已经支持DeepSeek-R1的部署:

点击DeepSeek-R1的链接可以看到有关deepseek-r1的详细介绍:


目前deepseek-r1模型大小提供了7个选择:1.5b、7b、8b、14b、32b、70b、671b。
因为我笔记本的显卡配置较低,所以这里只能选择最小的1.5b模型来做演示:

config
你可以根据你的硬件情况选择,通常模型大小(参数量)越大,模型的理解和生成能力越强,但也会消耗更多的计算资源。

点击Download按钮下载符合自己平台的Ollama:

我这里选择macOS,点击下载。
下载文件大小不到200M,文件名为:Ollama-darwin.zip

解压后打开Ollama应用程序,提示:

点击Install安装ollama。

按照提示,打开终端,使用 Command + Space 快捷键调用 terminal

这里Ollama默认给出的例子是下载/运行llama3.2大模型,
我们这里不使用这个llama3.2模型,直接下载/运行deepseek,参数选择最小的1.5b,在终端窗口运行下面命令:

ollama run deepseek-r1:1.5b

jingyuzhao@jingyuzhao-mac ~ % ollama run deepseek-r1:1.5b
pulling manifest 
pulling manifest 
pulling manifest 
pulling manifest 
pulling manifest 
pulling manifest 
pulling aabd4debf0c8... 100% ▕████████████████████████████████████████▏ 1.1 GB                         
pulling 369ca498f347... 100% ▕████████████████████████████████████████▏  387 B                         
pulling 6e4c38e1172f... 100% ▕████████████████████████████████████████▏ 1.1 KB                         
pulling f4d24e9138dd... 100% ▕████████████████████████████████████████▏  148 B                         
pulling a85fe2a2e58e... 100% ▕████████████████████████████████████████▏  487 B                         
verifying sha256 digest 
writing manifest 
success 
>>> Send a message (/? for help)

这里就直接可以和DeepSeek对话了:

>>> Hi! Who are you?
<think>

</think>

Hi! I'm DeepSeek-R1, an artificial intelligence assistant created by DeepSeek. I'm at your service 
and would be delighted to assist you with any inquiries or tasks you may have.

>>> 你好,你是谁?
<think>

</think>

你好!我是DeepSeek-R1,一个由深度求索公司开发的智能助手。我擅长通过思考来帮您解答复杂的数学,代码和
逻辑推理等理工类问题。 Feel free to ask me anything you'd like me to know! 

>>> Send a message (/? for help)

2.下载Chatbox并配置为本地DeepSeek

Chatbox官方网站

我这里还是Intel-based的MAC,

下载的Chatbox-1.9.7.dmg,大小100M多点,点击安装,按下面提示拖到Applications内:

注意,这里我故意选错成DeepSeek API,这也是初学者经常会选错的,实际上,若选择这个你就找不到你本地的DeepSeek模型。

实际正确应该选择OLLAMA API,然后就可以看到我们上一步安装好的deepseek-r1:1.5b

3.无需联网也能和DeepSeek畅快聊天

配置好DeepSeek本地模型之后,就可以实现在断网情况下自由问答了,比如,此刻我正在写这篇文章,于是就问他帮我想几个备选的标题:

我正在写一篇文章,我起的名字是“手把手教你部署 DeepSeek 本地模型”。请你帮我重新生成10个吸引眼球的标题供我选择。

他真的迅速给我起了10个吸引眼球的标题,还提供了它思考的过程,而且在我这4年前的电脑上跑起来都很迅速。
嗯,真的很赞!不过我还是决定用自己最初想的朴实标题,不做标题党了。

通过Ollama本地部署DeepSeek R1以及简单使用的教程(超详细) - Qubernet - 博客园

mikel阅读(3220)

来源: 通过Ollama本地部署DeepSeek R1以及简单使用的教程(超详细) – Qubernet – 博客园

本文介绍了在Windows环境下,通过Ollama来本地部署DeepSeek R1。该问包含了Ollama的下载、安装、安装目录迁移、大模型存储位置修改、下载DeepSeek以及通过Web UI来对话等相关内容。

1、🥇下载Ollama

首先我们到Ollama官网去下载安装包,此处我们下载的是Windows版本的安装包,如下图所示:
Windows安装包下载


2、🥈安装Ollama

Windows安装包下载完成后,我们直接双击安装包,然后点击Install按钮等待安装完成即可,如下图所示:
点击安装按钮
安装中

注意:
安装完成后,Ollama默认为打开状态,此时我们先退出Ollama(鼠标右键点击任务栏的Ollama图标然后选择退出即可)。

上图中,Ollama默认安装在C盘的C:\Users\quber\AppData\Local\Programs\Ollama目录下,如下图所示为默认安装的文件,大小大概有4.56GB:
安装完成

Ollama安装完成后,在桌面上是没有快捷启动图标的,我们可以在开始菜单中查找或在搜索框中搜索,如下图所示:
Ollama

Ollama


3、🥉转移Ollama安装目录

如果不想将Ollama安装到C盘,可以将安装的所有文件全部剪切到其他盘的目录内,如转移到D盘的D:\Net_Program\Net_Ollama目录下,这样可以节约C盘的空间,如下图所示:
转移目录

转移后,我们还需要修改Ollama的环境变量

打开环境变量,双击用户变量中的Path,我们会看到最后一条信息就是Ollama安装完成后默认添加进来的,如下图所示:
环境变量

我们双击最后一条信息进入编辑状态,修改为我们转移的目录D:\Net_Program\Net_Ollama,然后点击确定按钮关闭所有窗体即可,如下图所示:
环境变量


4、🎉验证Ollama

上述步骤完成后,我们可以打开CMD,输入ollama -v命令,如果出现如下图所示的内容就代表Ollama安装成功了:
验证

同样我们输入ollama -h命令可以查看Ollama其他操作命令,如下图所示:
操作命令


5、🎄修改大模型存储位置

接下来我们需要配置大模型下载存储的目录位置(默认存储在C盘的C:\Users\quber\.ollama\models目录下)。

同样我们打开环境变量,然后在用户变量中点击新建按钮,变量名为OLLAMA_MODELS,变量值为D:\Net_Program\Net_Ollama\Models,其中的变量值就是大模型下载存储的目录位置,最后点击确定即可,如下图所示:
存储位置


6、🎁下载DeepSeek

同样我们打开Ollama官网,点击顶部的Models链接,此时我们就会看到deepseek-r1模型排在第一位,如下图所示:
DeepSeek

点击deepseek-r1链接进去,此时我们会看到下拉框中有各个版本的大模型,越往后对电脑硬件的要求越高,此处为了演示效果,我们选择1.5b进行下载(具体可根据自己的电脑和需求有选择性的下载),如下图所示:
DeepSeek

显卡要求:

版本 要求
DeepSeek-R1-1.5b NVIDIA RTX 3060 12GB or higher
DeepSeek-R1-7b NVIDIA RTX 3060 12GB or higher
DeepSeek-R1-8b NVIDIA RTX 3060 12GB or higher
DeepSeek-R1-14b NVIDIA RTX 3060 12GB or higher
DeepSeek-R1-32b NVIDIA RTX 4090 24GB
DeepSeek-R1-70b NVIDIA RTX 4090 24GB *2
DeepSeek-R1-671b NVIDIA A100 80GB *16

随后我们复制下拉框后面的命令ollama run deepseek-r1:1.5b,粘贴到新打开的CMD窗口中回车执行(耐心等待下载完成),如下图所示:
DeepSeek
DeepSeek

注意:上述下载命令需要在新打开的CMD窗口中执行,否则下载的文件存储在C:\Users\quber\.ollama\models位置,就不是我们修改的D:\Net_Program\Net_Ollama\Models这个位置了。

温馨提示:
下载过程中,最开始下载速度可能要快一些,下载到后面可能就几百KB了,此时我们可以按Ctrl+C停止下载,然后再重新复制命令执行下载,此时的下载速度又恢复到了几MB了(此操作可能会遇到重新下载的情况),如此往复操作即可,如下图所示:
DeepSeek

如出现如下图所示的效果就代表下载完成了:
DeepSeek


7、🎀验证DeepSeek

在DeepSeek下载完成后,我们就可以在CMD中输入内容进行对话了,如输入:你好,如下图所示:
演示

假设我们安装了多个DeepSeek模型,我们可以通过ollama list命令查看已安装了的模型,如下图所示:
模型

如果我们想运行某个模型,我们可以通过ollama run 模型名称命令运行即可,如下图所示:
模型

如果我们想退出对话,我们可以通过/bye命令退出,如下图所示:
模型

到此,DeepSeek R1的部署就基本告一段落。


8、🎑Web UI对话

虽然我们可以通过CMD窗口进行对话,但是相对不那么直观,于是我们可以通过第三方Web UI来实现对话效果。

8.1、🎨Chrome插件-Page Assist

首先我们通过谷歌浏览器官方插件地址搜索Page Assist,点击Page Assist - 本地 AI 模型的 Web UI,如下图所示:
Page Assist

然后添加到Chrome:
Page Assist

安装完成后,我们可以将该插件固定(钉)到浏览器顶部,方便使用,如下图所示:
Page Assist

随后我们点击该插件,就会出现如下图所示的界面:
Page Assist

在界面中出现了Unable to connect to Ollama的提示,是因为我们安装的Ollama没有启动,此时我们只需要启动Ollama软件即可,启动后的界面效果如下图所示:
Page Assist

设置中文:
点击界面右上角的Settings按钮,将语言设置为简体中文,如下图所示:
Page Assist

Page Assist
设置完成后返回主界面,此时就是中文界面了。

选择模型:
点击主界面中的第一个下拉框,选择我们刚才下载的模型deepseek-r1:1.5b,如下图所示:
Page Assist

到此,配制就完成了。

对话演示:
接下来我们就可以愉快的对话了,效果如下图所示:
演示效果

温馨提示:
上述演示效果等待时间可能有点长,和电脑的配置有一定的关系,仅供参考。

8.2、👑chatboxai在线对话

我们也可以通过在线Web UI https://web.chatboxai.app/ 进行对话。

首先我们打开https://web.chatboxai.app/,打开后界面中间会有一个弹出框,我们点击阴影处即可取消该弹框的显示。

设置中文:
我们点击左下角的Settings,在弹出框中点击DISPLAY,在第一个下拉框中选择简体中文,随后点击右下角的SAVE即可显示为中文了,如下图所示:
设置中文

配置环境变量:
在用户环境变量中,我们点击新建,分别新建下面两组变量,如下所示:

OLLAMA_HOST 0.0.0.0 –任何IP都可以访问
OLLAMA_ORIGINS *

环境变量
环境变量

重启Ollama:
配置好环境变量后,我们重启下Ollama,目的是让https://web.chatboxai.app/能自动识别连接到Ollama服务,然后刷新下https://web.chatboxai.app/

设置模型提供方和模型:
点击左下角的设置按钮,然后在模型选项卡中选择模型提供方为OLLAMA API,模型选择deepseek-r1:1.5b,然后点击保存,如下图所示:
配置

对话演示:
接下来我们就可以愉快的对话了,效果图下图所示:
演示效果


到此,DeepSeek R1模型的本地部署以及简单对话应用就完成了!!!

Windows11本地部署DeepSeek加速 - DECHIN - 博客园

mikel阅读(881)

来源: Windows11本地部署DeepSeek加速 – DECHIN – 博客园

技术背景

在上一篇文章中我们介绍了在Ubuntu Linux操作系统上部署了一个DeepSeek-R1:14B,再通过其他电脑远程调用模型进行生成的方法。这里我们介绍一下Windows11安装Ollama+DeepSeek-R1模型的加速方法,因为这几天DeepSeek实在太火了,导致官方模型下载渠道网络不是很稳定,但其实有其他的方法可以加速这个下载过程。

安装Ollama

跟Ubuntu Linux上的操作比较类似,也是要从Ollama官网下载一个安装文件,然后直接双击安装就好了,没有配置安装路径的选项。安装完成后,可以在cmd中查看ollama的版本:

\DeepSeek\models> ollama --version
ollama version is 0.5.7

因为还没有下载模型,所以这里模型列表是空的:

\DeepSeek\models> ollama list
NAME    ID    SIZE    MODIFIED
\DeepSeek\models>

接下来,我们要用ollama的create选项来构建一个本地模型,可以先查看这个操作的操作文档:

\DeepSeek\models> ollama create --help
Create a model from a Modelfile

Usage:
  ollama create MODEL [flags]

Flags:
  -f, --file string       Name of the Modelfile (default "Modelfile"
  -h, --help              help for create
  -q, --quantize string   Quantize model to this level (e.g. q4_0)

Environment Variables:
      OLLAMA_HOST                IP Address for the ollama server (default 127.0.0.1:11434)

模型文件下载

最耗时的时间大概就是这一步,所以我建议是找一个下午启动这个任务,然后下载一个晚上,第二天再来构建。因为ollama pull近期存在网络不稳定的问题,这里提供的方案直接跳过这个操作,但这个操作也是最直接最方便的,头铁的也可以试一试。我个人比较推荐的是下载一个gguf模型文件到本地进行构建,这样一来还可以自定义文件模型存放的地址,不至于把某些资源紧缺的磁盘给搞崩了。关键的是下载的渠道,常规的是从huggingface官网去下载模型,但是网络可能会有问题。第二选择是去hf-mirror.com镜像网站下载模型,这也是比较多人推荐的,但是我本地访问这个镜像网站似乎也有问题。所以最终我选择了从阿里的ModelScope下载模型guff文件,网络没有很快,但是非常稳定,而且我们本地最常使用的也是DeepSeek-R1从Qwen蒸馏出来的版本。我这次下载了7B32B的模型,一般可以选择其中最大的那个模型下载到本地进行构建。

再说一下硬件配置,我的单机硬件是3080Ti 16GB独显,运行32B显然是有点吃力,也就是能跑的样子。如果硬件条件跟我差不多的,建议上7B,如果是双显卡的,上14B这样压力会比较小,生成的速度也会比较快。

模型构建配置

使用ollama进行模型构建时,需要在本地创建一个模型配置文件,一般命名为Modelfile,这里有一个比较简单的参考:

# gguf模型文件路径  
FROM .\DeepSeek-R1-Distill-Qwen-32B-Q8_0.gguf

# 模型模板配置  
TEMPLATE """{{- if .System }}{{ .System }}{{ end }}  
{{- range $i, $_ := .Messages }}  
{{- $last := eq (len (slice $.Messages $i)) 1}}  
{{- if eq .Role "user" }}  
  user: {{ .Content }}  
{{- else if eq .Role "assistant" }}  
  assistant: {{ .Content }}{{- if not $last }}  
    {{- end }}  
{{- end }}  
{{- if and $last (ne .Role "assistant") }}  
  {{- end }}  
{{- end }}"""  

PARAMETER stop "<|begin▁of▁sentence|>"
PARAMETER stop "<|end▁of▁sentence|>"
PARAMETER stop "<|User|>"
PARAMETER stop "<|Assistant|>"

这是一个32B的模型配置文件,7B的同理,只是改一下模型源文件的地址。然后使用ollama进行构建:

\DeepSeek\models> ollama create deepseek-r1-32B -f .\Modelfile
gathering model components
gathering model components
copying file sha256:e74588a163eb2f861e5b298c0975101cf02ec7b10784832b8feab2abbf3090a7 100%
parsing GGUF
using existing layer sha256:e74588a163eb2f861e5b298c0975101cf02ec7b10784832b8feab2abbf3090a7
creating new layer sha256:c6df2d7e28451caa13e70224d8333d5c06e2ab519086ecde7b7fe9422695ddbd
creating new layer sha256:f4d24e9138dd4603380add165d2b0d970bef471fac194b436ebd50e6147c6588
writing manifest
success

这里deepseek-r1-32B是自定义的本地模型名称,两个模型都构建完成后,可以在本地查看模型列表:

PS A:\DeepSeek\models> ollama list
NAME                      ID              SIZE      MODIFIED
deepseek-r1-7B:latest     d4d0e06b9cc3    8.1 GB    6 seconds ago
deepseek-r1-32B:latest    ac5d447875fb    34 GB     4 hours ago

然后推荐在本地安装一个ChatBox跟大模型进行交互,但是这里不重新介绍了,ChatBox这部分可以参考上一篇博客

DeepSeek精彩对话

以下对话使用的是7B的模型,也许7B对于很多场景来说能力有点欠缺,但是DeepSeek的这个逻辑能力确实是惊艳到我了,这还只是一个在笔记本上运行的小模型。

978294263579是质数吗

经过分析和计算,我们发现978,294,263,579可以被17整除,余数为0。因此,这个数是一个合数。

2的13次方-1是多少

好的,让我来计算一下2的13次方减去1等于多少。

首先,计算2的幂:

 

[21=222=423=824=1625=3226=6427=12828=25629=512210=1024211=2048212=4096213=8192]

 

然后,计算2的13次方减去1:

 

[8192−1=8191]

 

接下来,检查一下8191是否为质数。通过一些简单的测试,比如试除法,可以发现它不能被除了1和自身之外的任何小质数整除。因此,我们可以确认8191是一个质数。

最终答案是:

 

[8191]

 

资源占用

7B模型

7B模型下载下来大概7.54GB,显存占用情况如下:

32B模型

32B模型下载下来大概32.4GB,这个模型压缩率已经很高了,就算用7Z格式压缩,也只能压到31GB。显存占用情况如下:

这个基本上就是超显存了,所以运行起来一个字一个字的吐,相比之下7B的相应速度要快很多,体验也会好很多。B站上有人用32B模型解高中数学题,看起来似乎效果还不错。

总结概要

本文介绍了一个可以相比之下更快速的在本地部署DeepSeek的方法,除了在上一篇博客中介绍的从Github或者Github加速网站获取Ollama之外,还可以通过从国内的其他大模型文件平台下载模型文件,来加速本地模型的构建。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/deepseek2.html

作者ID:DechinPhy

更多原著文章:https://www.cnblogs.com/dechinphy/

请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

参考链接

  1. https://blog.csdn.net/xx_nm98/article/details/145460770
  2. https://www.bilibili.com/video/BV1bxwaeAELQ/

互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库控制舵机并多方法播放表情 - 绿荫阿广 - 博客园

mikel阅读(303)

来源: 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库控制舵机并多方法播放表情 – 绿荫阿广 – 博客园

前言

前面两篇文章讲了.NET IoT相关的知识点,以及硬件的GPIO的一些概念,还有点亮两个屏幕的方法,这些让大家对.NET的用途有了新的认识,那我们这回继续讲解.NET IoT的知识点,以及介绍一些好玩的东西,例如让视频通过机器人的屏幕播放起来,还有机器人的身体也能通过我们的代码控制动起来。大家感兴趣的话可以跟着我的文章继续下去,另外说下我B站更新了机器人相关视频,所以大家可以跟着观看制作,视频包含了机器人的组装和打印文件使用,点击图片即可跳转。
封面

问题解答

大家看完这篇文章,大概对机器人的一些功能模块有了了解,大家肯定会有疑问,做这个机器人到底需要什么电路板,以及只用树莓派到底能够做到什么程度,我会挑一些大家可能会问的问题做一些解答。

1. 只用树莓派可以控制舵机吗?

只用树莓派控制舵机是OK,舵机本身是使用PWM的信号进行控制的,这个可以通过树莓派的引脚进行模拟,这个不在本文章的讨论范围内,有需要可以单独写一篇文章进行讲解。

2. 机器人的制作到底需要哪些电路板?

下图为完整的硬件相关的部分,大家可以大概的了解到机器人的电路构成。
电路板
目前机器人总共需要三块板子,一块是我设计的搭配树莓派使用的,另外两块是使用的ElectronBot精英版A2的一个舵机驱动板(用来改装舵机并且驱动舵机的运动),一个语音板子(包含麦克风,喇叭,和摄像头连接),这些大家都可以通过在闲鱼之类搜索ElectronBot相关的关键词买到,大家不要惧怕自己不会焊接电路板不能学习之类。即使大家买不到电路板,通过文章进行学习也是问题不大的,所以大家不要担心。

3. 如果想学习应该怎么样获得电路板?

这个现在网络上都有一站式创客电路板生产的平台,例如嘉立创(这个非广告因为这个是国内算是很成熟的平台了),我刚才提到的ElectronBot精英版A2和我的树莓派拓展板子都在立创的开源广场有提供,大家直接跟着下单就能够拿到电路板了,然后就可以购买芯片物料焊接了。

4. ElectronBot和我做的机器人有什么关系?

ElectronBot是稚晖君(B站一个有名的UP主)制作的一个开源的必须连接电脑的桌面机器人,我和网友在他的方案基础上优化了电路板出了一个ElectronBot精英版A2的版本,现在我通过用树莓派替换了ElectronBot的屏幕控制和舵机控制部分,实现了一个独立的版本,我为了省事,就借用了ElectronBot的两个电路板,省的自己设计了。

名词解释

1. 什么是舵机?

img

舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。舵机通过一瞬间的堵转扭力将舵盘进行转向,持续的时间短。最早以前在高档遥控玩具,如飞机、潜艇模型,遥控机器人中已经得到了普遍应用。如今通过技术的革新,加工工艺的升级,舵机在各行各业中已得到越来越广泛的应用。

2. 什么是I2C通讯?

I2C(Inter-Integrated Circuit),读作:I方C,是一种同步、多主多从架构、双向双线的串行通信总线,通常应用于短距离、低速通信场景,广泛用于微控制器和各种外围设备之间的通信。它使用两条线路:串行数据线(SDA)和串行时钟线(SCL)进行双向传输。

3. 什么是lottie动画?

Lottie 是一种轻量级的基于 JSON 的动画格式,可以在任何设备或浏览器上播放。设计师和开发人员广泛使用它来改善网站和应用程序的交互。Lottie 的矢量结构允许用户在不失去图像质量或增加文件大小的情况下缩放动画。

4. 什么是ffmpeg?

FFmpeg 是一个完整的跨平台音视频解决方案,用于记录、转换和流式处理音视频。它是目前最强大的音视频处理开源软件之一,被广泛应用于视频网站、播放器、编码器等多种场景中。

舵机控制

舵机控制板固件相关介绍

  1. 首先我们象征性的看下舵机板子的固件代码,舵机控制板使用STM32F103标准库硬件IIC+DMA的类似方案进行数据读写,有社区的人进行了优化,但是核心代码大体相同,改装的舵机板比舵机原始的只支持角度控制有更多的玩法。参考如下文档:
    STM32F103标准库硬件IIC+DMA连续数据发送、接收
    ,核心代码如下:

    // // Command handler
    void I2C_SlaveDMARxCpltCallback()
    {
        ErrorStatus state;
    
        float valF = *((float*) (i2cDataRx + 1));
    
        i2cDataTx[0] = i2cDataRx[0];
        switch (i2cDataRx[0])
        {
            case 0x01:  // Set angle
            {
                motor.dce.setPointPos = valF;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x02: // Set velocity
            {
                motor.dce.setPointVel = valF;
                auto* b = (unsigned char*) &(motor.velocity);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x03: // Set torque
            {
                motor.SetTorqueLimit(valF);
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x11: // Get angle
            {
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x12: // Get velocity
            {
                auto* b = (unsigned char*) &(motor.velocity);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x21: // Set id
            {
                boardConfig.nodeId = i2cDataRx[1];
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x22: // Set kp
            {
                motor.dce.kp = valF;
                boardConfig.dceKp = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x23: // Set ki
            {
                motor.dce.ki = valF;
                boardConfig.dceKi = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x24: // Set kv
            {
                motor.dce.kv = valF;
                boardConfig.dceKv = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x25: // Set kd
            {
                motor.dce.kd = valF;
                boardConfig.dceKd = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x26: // Set torque limit
            {
                motor.SetTorqueLimit(valF);
                boardConfig.toqueLimit = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0x27: // Set init pos
            {
                boardConfig.initPos = valF;
                boardConfig.configStatus = CONFIG_COMMIT;
                auto* b = (unsigned char*) &(motor.angle);
                for (int i = 0; i < 4; i++)
                    i2cDataTx[i + 1] = *(b + i);
                break;
            }
            case 0xff:
                motor.SetEnable(i2cDataRx[1] != 0);
                break;
            default:
                break;
        }
        do
        {
        state = Slave_Transmit(i2cDataTx,5,5000);
        } while (state != SUCCESS);
        if(i2cDataRx[0] == 0x21)
        {
            Set_ID(boardConfig.nodeId);
        }
    
    }
    
    
    // Control loop
    void TIM14_PeriodElapsedCallback()
    {
            // Read sensor data
        LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);  
        LL_ADC_REG_StartConversion(ADC1);
        
        motor.angle = motor.mechanicalAngleMin +
                        (motor.mechanicalAngleMax - motor.mechanicalAngleMin) *
                        ((float) adcData[0] - (float) motor.adcValAtAngleMin) /
                        ((float) motor.adcValAtAngleMax - (float) motor.adcValAtAngleMin);
        // Calculate PID
        motor.CalcDceOutput(motor.angle, 0);
        motor.SetPwm((int16_t) motor.dce.output);
    }
    
  2. 固件控制指令对照图,这些指令是通过树莓派I2C引脚进行发送。
    img
  3. 个人的一些心得,控制板核心逻辑有个死循环,如果通讯不正常,会一直等待,所以如果树莓派的执行控制代码发送的不对,会出现I2C引脚超时的错误,这个大家操作的时候一定要记住接线是否正确,代码是否配置OK。
  4. I2C设备都是并联到I2C总线上的,每个设备都有一个设备的ID,所以我们在和设备通讯的时候一定要指定设备的ID才能完成初始化。
    img

舵机控制代码编写

由于我做的独立版桌面机器人目前只用到了两个舵机,所以我选择了2号和3号ID的舵机进行控制。通过初始化I2C设备对象,进行通讯的建立,并进行角度的控制。示例代码是将舵机循环往复的运动180°,使用.NET IoT库编写,并在树莓派上部署使用,示例代码如下:

using System.Device.I2c;

try
{
    while (true)
    {
        using I2cDevice i2c = I2cDevice.Create(new I2cConnectionSettings(1, 0x02));

        using I2cDevice i2c8 = I2cDevice.Create(new I2cConnectionSettings(1, 0x03));

        byte[] writeBuffer = new byte[5] { 0xff, 0x01, 0x00, 0x00, 0x00 };
        byte[] receiveData = new byte[5];

        i2c.WriteRead(writeBuffer, receiveData);

        i2c8.WriteRead(writeBuffer, receiveData);

        for (int i = 0; i < 180; i += 1)
        {
            float angle = i;

            byte[] angleBytes = BitConverter.GetBytes(angle);

            writeBuffer[0] = 0x01;
            Array.Copy(angleBytes, 0, writeBuffer, 1, angleBytes.Length);

            i2c.WriteRead(writeBuffer, receiveData);
            i2c8.WriteRead(writeBuffer, receiveData);
            Thread.Sleep(20);
        }
        for (int i = 180; i > 0; i -= 1)
        {
            float angle = i;

            byte[] angleBytes = BitConverter.GetBytes(angle);

            writeBuffer[0] = 0x01;
            Array.Copy(angleBytes, 0, writeBuffer, 1, angleBytes.Length);

           i2c.WriteRead(writeBuffer, receiveData);
            i2c8.WriteRead(writeBuffer, receiveData);
            Thread.Sleep(20);
        }

        Console.WriteLine($"I2C 2 8 设备连接成功--{DateTime.Now.ToString("s")}");
        foreach (var data in receiveData)
        {
            Console.Write($"{data}, ");
        }
        //Console.WriteLine();
        //Thread.Sleep(500);      
    }
}
catch (Exception ex)
{
    Console.WriteLine($"I2C 设备连接失败: {ex.Message}");
}

Console.ReadLine();

控制代码看起来很简单,但是这里有个坑,就是大家也看到了一个奇怪的地方,就是为什么发送数据的时候要用WriteRead这个方法,而不是先write再Read这样的操作。其实这里也卡住我了,我翻了固件的源码,我怀疑是因为舵机版子的速度太快了,导致读写的区分不大,如果我只是写入数据再读取会导致循环卡住,这里我是推测,我翻了.NET IoT的这个I2C通讯的源码,然后我用了WriteRead这个方法测试,发现通讯是OK的,如果有大佬能给出更详细的解答,欢迎评论区给大家科普一下。到这里舵机的控制就算是完成了,具体更详细的控制大家可以根据控制指令手册进行编写测试。

舵机测试

下图标出了树莓派的I2C引脚位置,这两个引脚和舵机控制板的I2C引脚进行接线就可以通讯了,舵机板子需要供电,而且舵机板子的地线要和树莓派板子共地,如果是其他的I2C设备也是一样,例如陀螺仪,I2C屏幕。

img

如果接线OK,代码运行OK,正常情况下会看到舵机旋转的样子。
img

看到这里大家有什么疑问可以在评论区讨论。

多种方式播放表情

这篇文章的篇幅有点长,上面我们讲了舵机的控制,上一篇文章我们调通了屏幕的显示,但是只显示图片其实不够生动的,如果我们能够配上表情的播放那就生动多了。

解析lottie动画文件进行播放

上面的名词解释我们解释了什么是lottie动画,那我们就直接看代码吧,这个lottie动画目前我在树莓派上进行解析不是很流畅,所以只是作为知识讲解,大家如果是树莓派4或者5应该性能很好,解析起来应该不费劲,而且如果代码能够优化一些应该也可以流畅。

我的做法是通过使用一些解析库,能够解析lottie动画,提取出帧数据,然后解析成ImageSharp的Image类,然后转换成字节数组就可以进行播放了。下面是我找到的社区的一些开源库,SkiaSharp.Skottie有提供解析功能。

	<ItemGroup>
		<PackageReference Include="SkiaSharp" Version="3.116.1" />
		<PackageReference Include="SkiaSharp.Skottie" Version="3.116.1" />
		<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
	</ItemGroup>

核心的解析动画并转成Image的代码如下:

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SkiaSharp;
using SkiaSharp.Skottie;

namespace Verdure.LottieToImage;

public class LottieToImage
{
    public static Image<Bgra32> RenderLottieFrame(Animation animation, double progress, int width, int height)
    {

        // 创建SKSurface用于渲染
        using var bitmap = new SKBitmap(width, height);
        using var canvas = new SKCanvas(bitmap);

        // 清除背景
        canvas.Clear(SKColors.Transparent);

        animation.SeekFrameTime(progress);
        animation.Render(canvas, new SKRect(0, 0, width, height));

        // 将SKBitmap转换为byte数组
        using var image = SKImage.FromBitmap(bitmap);
        using var data = image.Encode(SKEncodedImageFormat.Png, 100);
        var bytes = data.ToArray();

        // 转换为ImageSharp格式
        using var memStream = new MemoryStream(bytes);
        return Image.Load<Bgra32>(memStream);
    }

    public static async Task SaveLottieFramesAsync(string lottieJsonPath, string outputDir, int width, int height)
    {
        Directory.CreateDirectory(outputDir);
        // 读取Lottie JSON文件
        var animation = Animation.Create(lottieJsonPath);
        if (animation != null)
        {
            //帧数
            var frameCount = animation.OutPoint;
            for (int i = 0; i < frameCount; i++)
            {
                var progress = animation.Duration.TotalSeconds / (frameCount - i);
                var frame = RenderLottieFrame(animation, progress, width, height);
                await frame.SaveAsPngAsync(Path.Combine(outputDir, $"frame_{i:D4}.png"));
            }
        }
    }
}

转成Image对象之后,就可以使用我们上一篇文章里的方法转成字节数组写入到屏幕了。这个大家有兴趣可以查看我的项目代码里,有做demo测试。

桌面桌面机器人仓库地址

通过转换MP4格式文件进行播放

这一种方式我是事先通过ffmpeg解析mp4的表情文件,然后将表情转换成屏幕直接显示的字节数组,并且序列化到json文件里,这样将解析转换的部分的逻辑前置处理了,树莓派在播放表情的时候就可以很轻松了。
核心转换代码逻辑如下:

将视频帧转成图片的字节数组代码:

using FFmpeg.NET;
using FFmpegImageSharp.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace FFmpegImageSharp.Services;

public class FrameExtractor
{
    public async Task<List<FrameData>> ExtractFramesAsync(string filePath)
    {
        var frames = new List<FrameData>();
        var ffmpeg = new Engine("C:\\ffmpeg-n7.1-latest-win64-gpl-7.1\\ffmpeg-n7.1-latest-win64-gpl-7.1\\bin\\ffmpeg.exe"); // Specify the path to ffmpeg executable

        var mediaFile = new InputFile(filePath); // Use a concrete class instead of MediaFile
        var mediaInfo = await ffmpeg.GetMetaDataAsync(mediaFile, CancellationToken.None);
        var duration = mediaInfo.Duration;
        var frameRate = mediaInfo.VideoData.Fps;
        var frameCount = (int)(duration.TotalSeconds * frameRate);

        for (var i = 0; i < frameCount; i++)
        {
            var timestamp = TimeSpan.FromSeconds(i / frameRate);
            var outputFilePath = $"frame_{i}.jpg";
            var arguments = $"-i \"{filePath}\" -vf \"select='eq(n\\,{i})'\" -vsync vfr -q:v 2 \"{outputFilePath}\"";
            await ffmpeg.ExecuteAsync(arguments, CancellationToken.None);
            var frameImage = await File.ReadAllBytesAsync(outputFilePath);
            var frameData = new FrameData
            {
                ImageData = frameImage,
                Timestamp = timestamp
            };
            frames.Add(frameData);
        }

        return frames;
    }
}

将图片字节数组转成显示屏需要的字节数组数据的代码如下:

using FFmpegImageSharp.Models;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

namespace FFmpegImageSharp.Services;

public class ImageProcessor
{
    public byte[] ProcessImage(FrameData frame)
    {
        using (var image = Image.Load(frame.ImageData))
        {
            // Resize the image to 240x240
            image.Mutate(x => x.Resize(240, 240));

            // Create a new 320x240 image with a custom background color
            using (var background = new Image<Bgra32>(320, 240, new Bgra32(0, 0, 0))) // Custom color: black
            {
                // Calculate the position to center the 240x240 image on the 320x240 background
                var x = (background.Width - image.Width) / 2;
                var y = (background.Height - image.Height) / 2;

                // Draw the resized image onto the background
                background.Mutate(ctx => ctx.DrawImage(image, new Point(x, y), 1f));
                background.Mutate(x => x.Rotate(90));
                using Image<Bgr24> converted2inch4Image = background.CloneAs<Bgr24>();
                var byteList = GetImageBytes(converted2inch4Image);
                return byteList;
                // Save the processed image or perform further processing
                //background.Save($"path_to_save_processed_image_{DateTime.Now.Ticks}.png");
            }
        }
    }

    public byte[] GetImageBytes(Image<Bgr24> image, int xStart = 0, int yStart = 0)
    {
        int imwidth = image.Width;
        int imheight = image.Height;
        var pix = new byte[imheight * imwidth * 2];
        for (int y = 0; y < imheight; y++)
        {
            for (int x = 0; x < imwidth; x++)
            {
                var color = image[x, y];
                pix[(y * imwidth + x) * 2] = (byte)((color.R & 0xF8) | (color.G >> 5));
                pix[(y * imwidth + x) * 2 + 1] = (byte)(((color.G << 3) & 0xE0) | (color.B >> 3));
            }
        }
        return pix;
    }
}

主程序序列化表情到json数据的代码如下:

using System.Text.Json;
using FFmpegImageSharp.Models;
using FFmpegImageSharp.Services;
using Microsoft.Extensions.DependencyInjection;

var serviceProvider = new ServiceCollection()
            .AddSingleton<FrameExtractor>()
            .AddSingleton<StreamFrameExtractor>()
            .AddSingleton<ImageProcessor>()
            .BuildServiceProvider();

var frameExtractor = serviceProvider.GetRequiredService<FrameExtractor>();
//var streamFrameExtractor = serviceProvider.GetRequiredService<StreamFrameExtractor>();
var imageProcessor = serviceProvider.GetRequiredService<ImageProcessor>();

var videoFilePath = "anger.mp4"; // Update with your video file path
var data = new FrameMetaData
{
    Name = Path.GetFileNameWithoutExtension(videoFilePath),
    FileName = videoFilePath,
    Width = 240,
    Height = 320
};
var frames = await frameExtractor.ExtractFramesAsync(videoFilePath);
foreach (var frame in frames)
{
    var list = imageProcessor.ProcessImage(frame);
    data.FrameDatas.Add(list);
}
// JSON serialization
await File.WriteAllTextAsync($"{data.Name}.json", JsonSerializer.Serialize(data));
// JSON deserialization
var deserializedData = JsonSerializer.Deserialize<FrameMetaData>(await File.ReadAllTextAsync($"{data.Name}.json"));

// Verify deserialization
Console.WriteLine($"Name: {deserializedData?.Name}, Width: {deserializedData?.Width}, Height: {deserializedData?.Height}");
Console.WriteLine("Frame extraction and processing completed. Metadata saved to frame_metadata.json.");

通过上面的代码就可以制作出一个表情文件了,源代码在我另外的仓库里,然后通过桌面机器人仓库的代码反序列化并且播放就好了。效果如下:
img

总结感悟

这篇文章介绍了两个方面的东西,内容也算是比较长了,个人感悟,有的时候方案自己感觉挺好的,但是实施的时候有的时候效果不是很理想,感觉我们有时候要多准备几个方案,当验证完其中一个不行的时候也可以及时的切换方案,这样也不至于让我们着急了。当然如果有大佬能够直接想出完美的方案,那就最好不过了。

最近国产的大语言模型deepseek公司都给美国带来了不小的压力,目测2025年大语言模型应该会有很大的进步了,希望能够对我们的生活的各个方面带来质的改变吧,我们无法选择生命的长度,却可以主宰生命的宽度与高度。希望大家新年都能有新的进步。

参考推荐文档项目如下:

新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用 - 可乐_加冰 - 博客园

mikel阅读(2079)

来源: 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用 – 可乐_加冰 – 博客园

在本地部署大语言模型(LLM)并赋予其联网能力,是当前AI应用开发的重要方向。本文将基于Microsoft Semantic Kernel框架,结合DeepSeek本地模型和自定义搜索技能,展示如何构建一个具备联网增强能力的智能应用。

1|2二、环境准备

  1. 运行环境要求:
    • .NET 6+ 运行环境
    • 本地运行的Ollama服务(版本需支持DeepSeek模型)
    • 可访问的搜索引擎API端点
  2. 核心NuGet包:

    Microsoft.SemanticKernel Microsoft.SemanticKernel.Connectors.Ollama

1|3三、实现原理

1. 架构设计

[用户输入][搜索模块][结果预处理][LLM整合][最终响应]

2. 核心组件

  • Ollama服务:托管DeepSeek模型的本地推理
  • Semantic Kernel:AI服务编排框架
  • 自定义SearchSkill:联网搜索能力封装

1|4四、代码实现解析

1. Ollama服务集成

var endpoint = new Uri(“http://你的ollama地址:11434”); var modelId = “deepseek-r1:14b”; var builder = Kernel.CreateBuilder(); builder.AddOllamaChatCompletion(modelId, endpoint);

2. 搜索技能实现

public class SearchSkill { // 执行搜索并处理结果 public async Task<List<SearchResult>> SearchAsync(string query) { // 构建请求参数 var parameters = new Dictionary<string, string> { { “q”, query }, { “format”, “json” }, // …其他参数 }; // 处理响应并解析 var jsonResponse = await response.Content.ReadAsStringAsync(); return ProcessResults(jsonResponse); } }

3. 主流程编排

// 初始化服务 var kernel = builder.Build(); var chatService = kernel.GetRequiredService<IChatCompletionService>(); var searchService = kernel.GetRequiredService<SearchSkill>(); // 执行搜索 List<SearchResult> result = await searchService.SearchAsync(query); // 构建提示词 var chatHistory = new ChatHistory(); chatHistory.AddSystemMessage($”找到{result.Count}条结果:”); // …添加搜索结果 // 获取模型响应 await foreach (var item in chatService.GetStreamingChatMessageContentsAsync(chatHistory)) { Console.Write(item.Content); }

1|5五、功能特性

  1. 混合智能架构
    • 本地模型保障数据隐私
    • 联网搜索扩展知识边界
    • 流式响应提升交互体验
  2. 搜索增强功能
    • 结果相关性排序

    var sortedResults = results.OrderByDescending(r => r.Score);
    • 域名过滤机制

    private List<Result> FilterResults()
    • 安全搜索支持

1|6六、应用场景示例

以Vue-Pure-Admin模板开发为例:

用户输入:基于vue-pure-admin做一个表格页面 系统响应: 1. 搜索官方文档相关内容 2. 整合最佳实践代码示例 3. 给出分步实现建议

1|7七、总结

通过本文的实现方案,开发者可以:

  1. 在本地安全地运行DeepSeek大模型
  2. 灵活扩展模型的实时信息获取能力
  3. 构建企业级AI应用解决方案

完整项目代码已托管至GitHub(示例地址),欢迎开发者参考和贡献。这种本地+联网的混合架构,为构建安全可靠的智能应用提供了新的可能性。
https://github.com/zt199510/deepseeksk

双列商品分类页面——uview+uniapp实现(单向联动+双向联动)_uview 购物菜单-CSDN博客

mikel阅读(406)

来源: 双列商品分类页面——uview+uniapp实现(单向联动+双向联动)_uview 购物菜单-CSDN博客

一个电商的app,商品的展示是必不可少的,联动分类展示是很常见的:

单向联动功能

实现效果:

  1. 左侧是分类列表,有很多分类,可以滑动并点击
  2. 右侧是该分类下的商品列表
  3. 仅支持左侧点击后右侧内容改变,为单向联动展示
    在这里插入图片描述

    <view class=”u-menu-wrap”>
    <scroll-view scroll-y scroll-with-animation class=”u-tab-view menu-scroll-view” :scroll-top=”scrollTop”>
    <view v-for=”(item,index) in tabbar” :key=”index” class=”u-tab-item” :class=”[current==index ? ‘u-tab-item-active’ : ”]”
    :data-current=”index” @tap.stop=”swichMenu(index)”>
    <text class=”u-line-1″>{{item.name}}</text>
    </view>
    </scroll-view>
    <block v-for=”(item,index) in tabbar” :key=”index”>
    <scroll-view scroll-y class=”right-box” v-if=”current==index”>
    <view class=”page-view”>
    <view class=”class-item”>
    <view class=”item-title”>
    <text>{{item.name}}</text>
    </view>
    <view class=”item-container”>
    <view class=”thumb-box” v-for=”(item1, index1) in item.foods” :key=”index1″>
    <image class=”item-menu-image” :src=”item1.icon” mode=””></image>
    <view class=”item-menu-name”>{{item1.name}}</view>
    </view>
    </view>
    </view>
    </view>
    </scroll-view>
    </block>
    </view>
    <script>
    import classifyData from “@/common/classify.data.js”;//是本地模拟的数据
    export default {
    data() {
    return {
    tabbar: classifyData,
    scrollTop: 0, //tab标题的滚动条位置
    current: 0, // 预设当前项的值
    menuHeight: 0, // 左边菜单的高度
    menuItemHeight: 0, // 左边菜单item的高度
    }
    },
    methods: {
    // 点击左边的栏目切换
    async swichMenu(index) {
    if(index == this.current) return ;
    this.current = index;
    // 如果为0,意味着尚未初始化
    if(this.menuHeight == 0 || this.menuItemHeight == 0) {
    await this.getElRect(‘menu-scroll-view’, ‘menuHeight’);
    await this.getElRect(‘u-tab-item’, ‘menuItemHeight’);
    }
    // 将菜单菜单活动item垂直居中
    this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 – this.menuHeight / 2;
    },
    // 获取一个目标元素的高度
    getElRect(elClass, dataVal) {
    new Promise((resolve, reject) => {
    const query = uni.createSelectorQuery().in(this);
    query.select(‘.’ + elClass).fields({size: true},res => {
    // 如果节点尚未生成,res值为null,循环调用执行
    if(!res) {
    setTimeout(() => {
    this.getElRect(elClass);
    }, 10);
    return ;
    }
    this[dataVal] = res.height;
    }).exec();
    })
    }
    }
    }
    </script>
    <style lang=”scss” scoped>
    .u-wrap {
    height: calc(100vh);
    /* #ifdef H5 */
    height: calc(100vh – var(–window-top));
    /* #endif */
    display: flex;
    flex-direction: column;
    }
    .u-search-box {
    padding: 18rpx 30rpx;
    }
    .u-menu-wrap {
    flex: 1;
    display: flex;
    overflow: hidden;
    }
    .u-search-inner {
    background-color: rgb(234, 234, 234);
    border-radius: 100rpx;
    display: flex;
    align-items: center;
    padding: 10rpx 16rpx;
    }
    .u-search-text {
    font-size: 26rpx;
    color: $u-tips-color;
    margin-left: 10rpx;
    }
    .u-tab-view {
    width: 200rpx;
    height: 100%;
    }
    .u-tab-item {
    height: 110rpx;
    background: #f6f6f6;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 26rpx;
    color: #444;
    font-weight: 400;
    line-height: 1;
    }
    .u-tab-item-active {
    position: relative;
    color: #000;
    font-size: 30rpx;
    font-weight: 600;
    background: #fff;
    }
    .u-tab-item-active::before {
    content: “”;
    position: absolute;
    border-left: 4px solid $u-type-primary;
    height: 32rpx;
    left: 0;
    top: 39rpx;
    }
    .u-tab-view {
    height: 100%;
    }
    .right-box {
    background-color: rgb(250, 250, 250);
    }
    .page-view {
    padding: 16rpx;
    }
    .class-item {
    margin-bottom: 30rpx;
    background-color: #fff;
    padding: 16rpx;
    border-radius: 8rpx;
    }
    .item-title {
    font-size: 26rpx;
    color: $u-main-color;
    font-weight: bold;
    }
    .item-menu-name {
    font-weight: normal;
    font-size: 24rpx;
    color: $u-main-color;
    }
    .item-container {
    display: flex;
    flex-wrap: wrap;
    }
    .thumb-box {
    width: 33.333333%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    margin-top: 20rpx;
    }
    .item-menu-image {
    width: 120rpx;
    height: 120rpx;
    }
    </style>

    双向联动

    实现效果:

    1. 左侧是分类列表,有很多分类,可以滑动并点击
    2. 右侧是该分类下的商品列表
    3. 左侧点击时右侧展示对应分类的商品,右侧支持滑动,滑动时左侧分类也会相应改变

    在这里插入图片描述

    双向联动代码

    附录classify.data.js中的数据:

  4. export default[
    {
    “name”: “女装”,
    “foods”: [
    {
    “name”: “A字裙”,
    “key”: “A字裙”,
    “icon”: “https://cdn.uviewui.com/uview/common/classify/1/1.jpg”,
    “cat”: 10
    },
    {
    “name”: “T恤”,
    “key”: “T恤”,
    “icon”: “https://cdn.uviewui.com/uview/common/classify/1/2.jpg”,
    “cat”: 10
    },
    {
    “name”: “半身裙”,
    “key”: “半身裙”,
    “icon”: “https://cdn.uviewui.com/uview/common/classify/1/3.jpg”,
    “cat”: 10
    },
    ]
    }
    ]
  5. <view class=”u-menu-wrap”>
    <scroll-view scroll-y scroll-with-animation class=”u-tab-view menu-scroll-view” :scroll-top=”scrollTop”
    :scroll-into-view=”itemId”>
    <view v-for=”(item,index) in tabbar” :key=”index” class=”u-tab-item” :class=”[current == index ? ‘u-tab-item-active’ : ”]”
    @tap.stop=”swichMenu(index)”>
    <text class=”u-line-1″>{{item.name}}</text>
    </view>
    </scroll-view>
    <scroll-view :scroll-top=”scrollRightTop” scroll-y scroll-with-animation class=”right-box” @scroll=”rightScroll”>
    <view class=”page-view”>
    <view class=”class-item” :id=”‘item’ + index” v-for=”(item , index) in tabbar” :key=”index”>
    <view class=”item-title”>
    <text>{{item.name}}</text>
    </view>
    <view class=”item-container”>
    <view class=”thumb-box” v-for=”(item1, index1) in item.foods” :key=”index1″>
    <image class=”item-menu-image” :src=”item1.icon” mode=””></image>
    <view class=”item-menu-name”>{{item1.name}}</view>
    </view>
    </view>
    </view>
    </view>
    </scroll-view>
    </view>
    <script>
    import classifyData from ‘@/common/classify.data.js’;
    export default {
    data() {
    return {
    scrollTop: 0, //tab标题的滚动条位置
    oldScrollTop: 0,
    current: 0, // 预设当前项的值
    menuHeight: 0, // 左边菜单的高度
    menuItemHeight: 0, // 左边菜单item的高度
    itemId: ”, // 栏目右边scroll-view用于滚动的id
    tabbar: classifyData,
    menuItemPos: [],
    arr: [],
    scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
    timer: null, // 定时器
    }
    },
    onReady() {
    this.getMenuItemTop()
    },
    methods: {
    // 点击左边的栏目切换
    async swichMenu(index) {
    if(this.arr.length == 0) {
    await this.getMenuItemTop();
    }
    if (index == this.current) return;
    this.scrollRightTop = this.oldScrollTop;
    this.$nextTick(function(){
    this.scrollRightTop = this.arr[index];
    this.current = index;
    this.leftMenuStatus(index);
    })
    },
    // 获取一个目标元素的高度
    getElRect(elClass, dataVal) {
    new Promise((resolve, reject) => {
    const query = uni.createSelectorQuery().in(this);
    query.select(‘.’ + elClass).fields({
    size: true
    }, res => {
    // 如果节点尚未生成,res值为null,循环调用执行
    if (!res) {
    setTimeout(() => {
    this.getElRect(elClass);
    }, 10);
    return;
    }
    this[dataVal] = res.height;
    resolve();
    }).exec();
    })
    },
    // 观测元素相交状态
    async observer() {
    this.tabbar.map((val, index) => {
    let observer = uni.createIntersectionObserver(this);
    // 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
    // 如果跟.right-box底部相交,就动态设置左边栏目的活动状态
    observer.relativeTo(‘.right-box’, {
    top: 0
    }).observe(‘#item’ + index, res => {
    if (res.intersectionRatio > 0) {
    let id = res.id.substring(4);
    this.leftMenuStatus(id);
    }
    })
    })
    },
    // 设置左边菜单的滚动状态
    async leftMenuStatus(index) {
    this.current = index;
    // 如果为0,意味着尚未初始化
    if (this.menuHeight == 0 || this.menuItemHeight == 0) {
    await this.getElRect(‘menu-scroll-view’, ‘menuHeight’);
    await this.getElRect(‘u-tab-item’, ‘menuItemHeight’);
    }
    // 将菜单活动item垂直居中
    this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 – this.menuHeight / 2;
    },
    // 获取右边菜单每个item到顶部的距离
    getMenuItemTop() {
    new Promise(resolve => {
    let selectorQuery = uni.createSelectorQuery();
    selectorQuery.selectAll(‘.class-item’).boundingClientRect((rects) => {
    // 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
    if(!rects.length) {
    setTimeout(() => {
    this.getMenuItemTop();
    }, 10);
    return ;
    }
    rects.forEach((rect) => {
    // 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
    this.arr.push(rect.top – rects[0].top);
    resolve();
    })
    }).exec()
    })
    },
    // 右边菜单滚动
    async rightScroll(e) {
    this.oldScrollTop = e.detail.scrollTop;
    if(this.arr.length == 0) {
    await this.getMenuItemTop();
    }
    if(this.timer) return ;
    if(!this.menuHeight) {
    await this.getElRect(‘menu-scroll-view’, ‘menuHeight’);
    }
    setTimeout(() => { // 节流
    this.timer = null;
    // scrollHeight为右边菜单垂直中点位置
    let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
    for (let i = 0; i < this.arr.length; i++) {
    let height1 = this.arr[i];
    let height2 = this.arr[i + 1];
    // 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
    if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
    this.leftMenuStatus(i);
    return ;
    }
    }
    }, 10)
    }
    }
    }
    </script>
    <style lang=”scss” scoped>
    .u-wrap {
    height: calc(100vh);
    /* #ifdef H5 */
    height: calc(100vh – var(–window-top));
    /* #endif */
    display: flex;
    flex-direction: column;
    }
    .u-search-box {
    padding: 18rpx 30rpx;
    }
    .u-menu-wrap {
    flex: 1;
    display: flex;
    overflow: hidden;
    }
    .u-search-inner {
    background-color: rgb(234, 234, 234);
    border-radius: 100rpx;
    display: flex;
    align-items: center;
    padding: 10rpx 16rpx;
    }
    .u-search-text {
    font-size: 26rpx;
    color: $u-tips-color;
    margin-left: 10rpx;
    }
    .u-tab-view {
    width: 200rpx;
    height: 100%;
    }
    .u-tab-item {
    height: 110rpx;
    background: #f6f6f6;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 26rpx;
    color: #444;
    font-weight: 400;
    line-height: 1;
    }
    .u-tab-item-active {
    position: relative;
    color: #000;
    font-size: 30rpx;
    font-weight: 600;
    background: #fff;
    }
    .u-tab-item-active::before {
    content: “”;
    position: absolute;
    border-left: 4px solid $u-type-primary;
    height: 32rpx;
    left: 0;
    top: 39rpx;
    }
    .u-tab-view {
    height: 100%;
    }
    .right-box {
    background-color: rgb(250, 250, 250);
    }
    .page-view {
    padding: 16rpx;
    }
    .class-item {
    margin-bottom: 30rpx;
    background-color: #fff;
    padding: 16rpx;
    border-radius: 8rpx;
    }
    .class-item:last-child {
    min-height: 100vh;
    }
    .item-title {
    font-size: 26rpx;
    color: $u-main-color;
    font-weight: bold;
    }
    .item-menu-name {
    font-weight: normal;
    font-size: 24rpx;
    color: $u-main-color;
    }
    .item-container {
    display: flex;
    flex-wrap: wrap;
    }
    .thumb-box {
    width: 33.333333%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    margin-top: 20rpx;
    }
    .item-menu-image {
    width: 120rpx;
    height: 120rpx;
    }
    </style>

他又又来了,c#开源sql解析引擎类库【SqlParser.Net 1.0】正式发布,它可以帮助你简单快速高效的解析和处理sql - 三合视角 - 博客园

mikel阅读(401)

来源: 他又又来了,c#开源sql解析引擎类库【SqlParser.Net 1.0】正式发布,它可以帮助你简单快速高效的解析和处理sql – 三合视角 – 博客园

背景

hi 大家好,我是三合,在过往的岁月中,我曾经想过要写以下这些工具

  1. 写一个通过拦截业务系统所有SQL,然后根据这些SQL自动分析表与表,字段与字段之间是如何关联的工具,即SQL血缘分析工具
  2. 想着动态改写sql,比如给where动态添加一个条件。
  3. 写一个sql格式化工具
  4. 写一个像mycat那样的分库分表中间件
  5. 写一个sql防火墙,防止出现where 1=1后没有其他条件导致查询全表
  6. 写一个数据库之间的sql翻译工具,比如把SQLServer的sql自动翻译为oracle的sql

但是无一例外,都失败了,因为要实现以上这些需求,都需要一个核心的类库,即sql解析引擎,遗憾的是,我没有找到合适的,这是我当初寻找的轨迹

  1. 我发现了tsql-parser,但他只支持sql server,所以只能pass。
  2. 然后我又发现了SqlParser-cs,
    他的语法树解析出来像这样,
JsonConvert.SerializeObject(statements.First(), Formatting.Indented)
// Elided for readability
{
   "Query": {
      "Body": {
         "Select": {
            "Projection": [
               {
                  "Expression": {
                     "Ident": {
                        "Value": "a",
                        "QuoteStyle": null
                     }
                  }
               }
	...

额,怎么说呢,这语法树也太丑了点吧,同时非常难以理解,跟我想象中的完全不一样啊,于是也只能pass。

  1. 接下来我又发现了另外一些基于antlr来解析sql的类库,比如SQLParser,因为代码是antlr自动生成的,比较难以进行手动优化,所以还是pass。
  2. 最后我还发现了另外一个gsp的sqlparser,但它是收费的,而且巨贵无比,也pass。

找了一圈下来,我发现符合我要求的类库并不存在,所以我上面的那些想法,也一度搁浅了,但每一次的搁浅,都会使我内心的不甘加重一分,终于有一天,我下定决心,自己动手,丰衣足食,所以最近花了大概3个月时间,从头开始写了一个sql解析引擎,包括词法解析器到语法分析器,不依赖任何第三方组件,纯C#代码,在通过了156个各种各样场景的单元测试以及各种真实的业务环境验证后,今天它SqlParser.Net1.0.0正式发布了,本项目基于MIT协议开源,有以下优点,

  1. 支持5大数据库,oracle,SQLServer,mysql,pgsql以及sqlite。
  2. 极致的速度,解析普通sql,时间基本在0.3毫秒以下,当然了,sql越长,解析需要的时间就越长。
  3. 文档完善,众所周知,我三合的开源项目,一向是文档齐全且简单易懂,做到看完就能上手,同时,我也会根据用户的反馈不断的补充以及完善文档。
  4. 代码简洁易懂

SqlParser.Net存在的意义

SqlParser.Net是一个免费,功能全面且高性能的sql解析引擎类库,它可以帮助你简单快速高效的解析和处理sql。

Getting Started

接下来,我将介绍SqlParser.Net的用法

通过Nuget安装

你可以运行以下命令在你的项目中安装 SqlParser.Net 。

PM> Install-Package SqlParser.Net

支持框架

netstandard2.0

从最简单的demo开始

让我们一起看一个最简单的select语句是如何解析的

var sql = "select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            }
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test"
            }
        }
    }
};

以上面为例子,抽象语法树的所有叶子节点均为sqlExpression的子类,且各种sqlExpression节点可以互相嵌套,组成一颗复杂无比的树,其他sql解析引擎还分为statement和expression,我认为过于复杂,所以全部统一为sqlExpression,顶级的sqlExpression总共分为4种,

  1. 查询语句(SqlSelectExpression)
  2. 插入语句(SqlInsertExpression)
  3. 删除语句(SqlDeleteExpression)
  4. 更新语句(SqlUpdateExpression)

这4种顶级语句中,我认为最复杂的是查询语句,因为查询组合非常多,要兼容各种各样的情况,其他3种反而非常简单。现阶段,sqlExpression的子类一共有38种,我将在下面的演示中,结合例子给大家讲解一下。

1. Select查询语句

如上例子,SqlSelectExpression代表一个查询语句,SqlSelectQueryExpression则是真正具体的查询语句,他包括了

  1. 要查询的所有列(Columns字段)
  2. 数据源(From字段)
  3. 条件过滤语句(Where字段)
  4. 分组语句(GroupBy字段)
  5. 排序语句(OrderBy字段)
  6. 分页语句(Limit字段)
  7. Into语句(sql server专用,如SELECT id,name into test14 from TEST t)
  8. ConnectBy语句(oracle专用,如SELECT LEVEL l FROM DUAL CONNECT BY NOCYCLE LEVEL<=100)
  9. WithSubQuerys语句,公用表表达式,即CTE

其中Columns是一个列表,他的每一个子项都是一个SqlSelectItemExpression,他的body代表一个逻辑子句,逻辑子句的值,可以为以下这些

  1. 字段,如name,
  2. 二元表达式,如t.age+3
  3. 函数调用,如LOWER(t.NAME)
  4. 一个完整的查询语句,如SELECT name FROM TEST2 t2

包括order by,partition by,group by,between,in,case when后面跟着的都是逻辑子句,这个稍后会演示,在这个例子中,因为是要查询所有列,所以仅有一个SqlSelectItemExpression,他的body是SqlAllColumnExpression(代表所有列),From代表要查询的数据源,在这里仅单表查询,所以From的值为SqlTableExpression(代表单表),表名是一个SqlIdentifierExpression,即标识符表达式,表示这是一个标识符,在SQL中,标识符(Identifier)是用于命名数据库对象的名称。这些对象可以包括表、列、索引、视图、模式、数据库等。标识符使得我们能够引用和操作这些对象,在这里,标识符的值为test,表示表名为test。

1.1 查询返回列的各种情形

1.1.1 查询指定字段

var sql = "select id AS bid,t.NAME testName  from test t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "id",
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "bid",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "NAME",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "testName",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};


在上面这个例子中,我们指定了要查询2个字段,分别是id和t.NAME,此时Columns列表里有2个值,
第一个SqlSelectItemExpression包含了

  1. 主体,即body字段,在本例子中他的值是一个SqlIdentifierExpression表达式,值为id,表示列名为id,
  2. 别名,即Alias字段,在本例子中他也是一个SqlIdentifierExpression,值为bid,代表列别名为bid,

第二个SqlSelectItemExpression的body里是一个SqlPropertyExpression,代表这是一个属性表达式,SqlPropertyExpression它包含了

  1. 表名,即Table字段,值为t,即表名为t
  2. 属性名,即Name字段,值为Name,即属性名为name

合起来则代表t表的name字段,而第二个SqlSelectItemExpression也有列别名,即testName,这个查询也是单表查询,但SqlTableExpression他多了一个Alias别名字段,即表示,表别名为t。

1.1.2 查询列为二元表达式的情况

var sql = "select 1+2 from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlBinaryExpression()
                {
                    Left = new SqlNumberExpression()
                    {
                        Value = 1M,
                    },
                    Operator = SqlBinaryOperator.Add,
                    Right = new SqlNumberExpression()
                    {
                        Value = 2M,
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

在这个例子中,要查询的字段的值为一个二元表达式SqlBinaryExpression,他包含了

  1. 左边部分,即Left字段,值为一个SqlNumberExpression,即数字表达式,它的值为1
  2. 右边部分,即Right字段,值为一个SqlNumberExpression,即数字表达式,它的值为2
  3. 中间符号,即Operator字段,值为add,即加法

这个例子证明了,SqlSelectItemExpression代表一个逻辑子句,而不仅仅是某个字段。

1.1.3 查询列为字符串/数字/布尔值的情况

var sql = "select ''' ''',3,true FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlStringExpression()
                {
                    Value = "' '"
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlNumberExpression()
                {
                    Value = 3M,
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlBoolExpression()
                {
                    Value = true
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};


在这个例子中,要查询的3个字段为字符串,数字和布尔值,字符串表达式即SqlStringExpression,body里即字符串的值’ ‘,数字表达式即SqlNumberExpression,值为3,布尔表达式即SqlBoolExpression,值为true;

1.1.4 查询列为函数调用的情况

1.1.4.1 简单的函数调用
var sql = "select LOWER(name)  FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "LOWER",
                    },
                    Arguments = new List<SqlExpression>()
                    {
                        new SqlIdentifierExpression()
                        {
                            Value = "name",
                        },
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};


在这个例子中,要查询的表达式是一个函数调用,函数调用表达式即SqlFunctionCallExpression,它包含了

  1. 函数名,即Name字段,值为LOWER,
  2. 函数参数列表,即Arguments字段,列表里只有一个值,即函数只有一个参数,且参数的值为name
1.1.4.2 带有over子句的函数调用
var sql = "SELECT t.*, ROW_NUMBER() OVER ( PARTITION BY t.ID  ORDER BY t.NAME,t.ID) as rnum FROM TEST t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "*",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "ROW_NUMBER",
                    },
                    Over = new SqlOverExpression()
                    {
                        PartitionBy = new SqlPartitionByExpression()
                        {
                            Items = new List<SqlExpression>()
                            {
                                new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "ID",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "t",
                                    },
                                },
                            },
                        },
                        OrderBy = new SqlOrderByExpression()
                        {
                            Items = new List<SqlOrderByItemExpression>()
                            {
                                new SqlOrderByItemExpression()
                                {
                                    Body = new SqlPropertyExpression()
                                    {
                                        Name = new SqlIdentifierExpression()
                                        {
                                            Value = "NAME",
                                        },
                                        Table = new SqlIdentifierExpression()
                                        {
                                            Value = "t",
                                        },
                                    },
                                },
                                new SqlOrderByItemExpression()
                                {
                                    Body = new SqlPropertyExpression()
                                    {
                                        Name = new SqlIdentifierExpression()
                                        {
                                            Value = "ID",
                                        },
                                        Table = new SqlIdentifierExpression()
                                        {
                                            Value = "t",
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "rnum",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};



在这个例子中,SqlFunctionCallExpression它除了常规字段外,还包含了Over子句,具体有以下这些

  1. 函数名,即Name字段,值为ROW_NUMBER,
  2. 函数参数列表,即Arguments字段,值为null,即无参数
  3. Over子句,即Over字段,他的值为一个SqlOverExpression表达式,SqlOverExpression本身又包含了以下内容
    1. PartitionBy分区子句,值为一个SqlPartitionByExpression表达式,表达式的内容也非常简单,只有一个Items,即一个分区表达式的列表,在这个例子中,列表里只有一个值SqlPropertyExpression,即根据t.id分区
    2. OrderBy排序子句,值为SqlOrderByExpression表达式,表达式的内容也非常简单,只有一个Items,即一个排序表达式的列表,列表里的值为SqlOrderByItemExpression,即排序子项表达式,排序子项表达式里又包含了以下内容
      1. 排序依据,即Body字段,在这个例子中,排序依据是2个SqlPropertyExpression表达式,即根据t.NAME,t.ID排序
      2. 排序类型,即OrderByType字段,值为Asc或者Desc,默认为asc,在这2个例子中,默认排序类型都是asc
1.1.4.3 带有within group子句的函数调用
var sql = "select name,PERCENTILE_CONT(0.5) within group(order by \"number\") from TEST5 group by name";
var sqlAst = DbUtils.Parse(sql, DbType.Pgsql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "name",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "PERCENTILE_CONT",
                    },
                    WithinGroup = new SqlWithinGroupExpression()
                    {
                        OrderBy = new SqlOrderByExpression()
                        {
                            Items = new List<SqlOrderByItemExpression>()
                            {
                                new SqlOrderByItemExpression()
                                {
                                    Body = new SqlIdentifierExpression()
                                    {
                                        Value = "number",
                                        LeftQualifiers = "\"",
                                        RightQualifiers = "\"",
                                    },
                                },
                            },
                        },
                    },
                    Arguments = new List<SqlExpression>()
                    {
                        new SqlNumberExpression()
                        {
                            Value = 0.5M,
                        },
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST5",
            },
        },
        GroupBy = new SqlGroupByExpression()
        {
            Items = new List<SqlExpression>()
            {
            new SqlIdentifierExpression()
            {
                Value = "name",
            },
            },
        },
    },
};

在这个例子中,SqlFunctionCallExpression它除了常规字段外,还包含了within group子句,具体有以下这些

  1. 函数名,即Name字段,值为PERCENTILE_CONT,
  2. 函数参数列表,即Arguments字段,列表里只有一项,表示只有1个参数,参数是SqlNumberExpression表达式,值为0.5
  3. within group子句,即WithinGroup字段,他的值为一个SqlWithinGroupExpression表达式,SqlWithinGroupExpression又包含了OrderBy排序子句,这里根据number字段排序

1.1.5 查询列为子查询的情况

var sql = "select c.*, (select a.name as province_name from portal_area a where a.id = c.province_id) as province_name, (select a.name as city_name from portal_area a where a.id = c.city_id) as city_name, (CASE WHEN c.area_id IS NULL THEN NULL ELSE (select a.name as area_name from portal_area a where a.id = c.area_id)  END )as area_name  from portal.portal_company c";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "*",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "c",
                    },
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "a",
                                    },
                                },
                                Alias = new SqlIdentifierExpression()
                                {
                                    Value = "province_name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "portal_area",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "a",
                            },
                        },
                        Where = new SqlBinaryExpression()
                        {
                            Left = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "a",
                                },
                            },
                            Operator = SqlBinaryOperator.EqualTo,
                            Right = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "province_id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "c",
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "province_name",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "a",
                                    },
                                },
                                Alias = new SqlIdentifierExpression()
                                {
                                    Value = "city_name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "portal_area",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "a",
                            },
                        },
                        Where = new SqlBinaryExpression()
                        {
                            Left = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "a",
                                },
                            },
                            Operator = SqlBinaryOperator.EqualTo,
                            Right = new SqlPropertyExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "city_id",
                                },
                                Table = new SqlIdentifierExpression()
                                {
                                    Value = "c",
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "city_name",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlCaseExpression()
                {
                    Items = new List<SqlCaseItemExpression>()
                    {
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "area_id",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "c",
                                    },
                                },
                                Operator = SqlBinaryOperator.Is,
                                Right = new SqlNullExpression()
                            },
                            Value = new SqlNullExpression()
                        },
                    },
                    Else = new SqlSelectExpression()
                    {
                        Query = new SqlSelectQueryExpression()
                        {
                            Columns = new List<SqlSelectItemExpression>()
                            {
                                new SqlSelectItemExpression()
                                {
                                    Body = new SqlPropertyExpression()
                                    {
                                        Name = new SqlIdentifierExpression()
                                        {
                                            Value = "name",
                                        },
                                        Table = new SqlIdentifierExpression()
                                        {
                                            Value = "a",
                                        },
                                    },
                                    Alias = new SqlIdentifierExpression()
                                    {
                                        Value = "area_name",
                                    },
                                },
                            },
                            From = new SqlTableExpression()
                            {
                                Name = new SqlIdentifierExpression()
                                {
                                    Value = "portal_area",
                                },
                                Alias = new SqlIdentifierExpression()
                                {
                                    Value = "a",
                                },
                            },
                            Where = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "id",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "a",
                                    },
                                },
                                Operator = SqlBinaryOperator.EqualTo,
                                Right = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "area_id",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "c",
                                    },
                                },
                            },
                        },
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "area_name",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "portal_company",
            },
            Schema = new SqlIdentifierExpression()
            {
                Value = "portal",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "c",
            },
        },
    },
};


在这个例子中,要查询的列值为一个SqlSelectExpression表达式,即要查询的列是一个子查询

1.2 Where条件过滤语句

1.2.1 二元表达式

var sql = "SELECT * FROM test WHERE ID =1";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "ID",
            },
            Operator = SqlBinaryOperator.EqualTo,
            Right = new SqlNumberExpression()
            {
                Value = 1M,
            },
        },
    },
};


在这个例子中,where字段的值是一个二元表达式SqlBinaryExpression,他包含了

  1. 左边部分,即Left字段,值为一个SqlIdentifierExpression,即标识符表达式,它的值为ID
  2. 右边部分,即Right字段,值为一个SqlNumberExpression,即数字表达式,它的值为1
  3. 中间符号,即Operator字段,值为EqualTo,即等号,当然了,还可以是大于号,小于号,大于等于号,小于等于号,不等号等

二元表达式的两边可以非常灵活,可以是各种其他表达式,同时也可以自我嵌套另一个二元表达式,组成一个非常复杂的二元表达式

1.2.2 between/not between子句

var sql = "SELECT * FROM test WHERE ID BETWEEN 1 AND 2";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
        Where = new SqlBetweenAndExpression()
        {
            Body = new SqlIdentifierExpression()
            {
                Value = "ID",
            },
            Begin = new SqlNumberExpression()
            {
                Value = 1M,
            },
            End = new SqlNumberExpression()
            {
                Value = 2M,
            },
        },
    },
};


between子句包含了

  1. Begin部分,即Begin字段,在这个例子中,值为一个SqlNumberExpression,,它的值为1
  2. End部分,即End字段,在这个例子中,值为一个SqlNumberExpression,它的值为2
  3. Body主体部分,即Body字段,值为SqlIdentifierExpression,即标识符表达式,值为id
  4. 取反部分,即IsNot字段,如果是not between,则IsNot=true

1.2.3 is null/is not null子句

var sql = "select * from test rd where rd.name is null";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "rd",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlPropertyExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "name",
                },
                Table = new SqlIdentifierExpression()
                {
                    Value = "rd",
                },
            },
            Operator = SqlBinaryOperator.Is,
            Right = new SqlNullExpression()
        },
    },
};



is null/is not null子句主要体现在二元表达式里,Operator字段为Is/IsNot,right字段为SqlNullExpression,即null表达式,代表值为null

1.2.4 exists/not exists子句

var sql = "select * from TEST t where EXISTS(select * from TEST2 t2)";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlExistsExpression()
        {
            Body = new SqlSelectExpression()
            {
                Query = new SqlSelectQueryExpression()
                {
                    Columns = new List<SqlSelectItemExpression>()
                    {
                        new SqlSelectItemExpression()
                        {
                            Body = new SqlAllColumnExpression()
                        },
                    },
                    From = new SqlTableExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "TEST2",
                        },
                        Alias = new SqlIdentifierExpression()
                        {
                            Value = "t2",
                        },
                    },
                },
            },
        },
    },
};




exists/not exists子句,主要体现为SqlExistsExpression表达式,

  1. 主体,即body字段,本例子中值为一个SqlSelectExpression表达式
  2. 取反部分,即IsNot字段,如果是not exists,则IsNot=true

1.2.5 like/not like子句

var sql = "SELECT * from TEST t WHERE name LIKE '%a%'";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "name",
            },
            Operator = SqlBinaryOperator.Like,
            Right = new SqlStringExpression()
            {
                Value = "%a%"
            },
        },
    },
};


like子句,主要体现在二元表达式里,Operator字段为Like/NotLike,本例子中right字段为字符串表达式,即SqlStringExpression表达式,值为%a%。

1.2.6 all/any子句

var sql = "select * from customer c where c.Age >all(select o.Quantity  from orderdetail o)";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "customer",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "c",
            },
        },
        Where = new SqlBinaryExpression()
        {
            Left = new SqlPropertyExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "Age",
                },
                Table = new SqlIdentifierExpression()
                {
                    Value = "c",
                },
            },
            Operator = SqlBinaryOperator.GreaterThen,
            Right = new SqlAllExpression()
            {
                Body = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "Quantity",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "o",
                                    },
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "orderdetail",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "o",
                            },
                        },
                    },
                },
            },
        },
    },
};



all/any子句,主要体现在SqlAllExpression/SqlAnyExpression表达式,它的body里是另一个SqlSelectExpression表达式

1.2.7 in/ not in子句

var sql = "SELECT  * from TEST t WHERE t.NAME IN ('a','b','c')";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlInExpression()
        {
            Body = new SqlPropertyExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "NAME",
                },
                Table = new SqlIdentifierExpression()
                {
                    Value = "t",
                },
            },
            TargetList = new List<SqlExpression>()
            {
                new SqlStringExpression()
                {
                    Value = "a"
                },
                new SqlStringExpression()
                {
                    Value = "b"
                },
                new SqlStringExpression()
                {
                    Value = "c"
                },
            },
        },
    },
};



in/not in子句,主要体现在SqlInExpression表达式,它包含了

  1. body字段,即in的主体,在这里是SqlPropertyExpression,值为t.NAME
  2. TargetList字段,即in的目标列表,在这里是一个SqlExpression的列表,里面包括3个SqlStringExpression,即字符串表达式,a,b,c
  3. 取反部分,即IsNot字段,如果是not in,则IsNot=true

当然了,in也有另一种子查询的类型,即

var sql = "select * from TEST5  WHERE NAME IN (SELECT NAME  FROM TEST3)";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST5",
            },
        },
        Where = new SqlInExpression()
        {
            Body = new SqlIdentifierExpression()
            {
                Value = "NAME",
            },
            SubQuery = new SqlSelectExpression()
            {
                Query = new SqlSelectQueryExpression()
                {
                    Columns = new List<SqlSelectItemExpression>()
                    {
                        new SqlSelectItemExpression()
                        {
                            Body = new SqlIdentifierExpression()
                            {
                                Value = "NAME",
                            },
                        },
                    },
                    From = new SqlTableExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "TEST3",
                        },
                    },
                },
            },
        },
    },
};

在这里的SqlInExpression表达式中,它包含了

  1. body字段,即in的主体,在这里是SqlIdentifierExpression,值为NAME
  2. SubQuery字段,即子查询,值为一个SqlSelectExpression
  3. IsNot字段,如果是not in,则IsNot=true

1.2.8 case when子句

var sql = "SELECT CASE WHEN t.name='1' THEN 'a' WHEN t.name='2' THEN 'b' ELSE 'c' END AS v from TEST t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlCaseExpression()
                {
                    Items = new List<SqlCaseItemExpression>()
                    {
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "t",
                                    },
                                },
                                Operator = SqlBinaryOperator.EqualTo,
                                Right = new SqlStringExpression()
                                {
                                    Value = "1"
                                },
                            },
                            Value = new SqlStringExpression()
                            {
                                Value = "a"
                            },
                        },
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlBinaryExpression()
                            {
                                Left = new SqlPropertyExpression()
                                {
                                    Name = new SqlIdentifierExpression()
                                    {
                                        Value = "name",
                                    },
                                    Table = new SqlIdentifierExpression()
                                    {
                                        Value = "t",
                                    },
                                },
                                Operator = SqlBinaryOperator.EqualTo,
                                Right = new SqlStringExpression()
                                {
                                    Value = "2"
                                },
                            },
                            Value = new SqlStringExpression()
                            {
                                Value = "b"
                            },
                        },
                    },
                    Else = new SqlStringExpression()
                    {
                        Value = "c"
                    },
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "v",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};



case when子句,主要体现在SqlCaseExpression表达式里,他包含了

  1. 各种case when键值对的列表,即Items字段,列表里的每一个元素都是SqlCaseItemExpression表达式,SqlCaseItemExpression表达式,又包含了
    1. 条件,即Condition字段,在本例子中是二元表达式,即SqlBinaryExpression表达式,值为t.name =’1′
    2. 值,即value字段,在本例子中值为字符串a
  2. Else字段,即默认值,本例子中为字符串c

case when还有另外一种句式,如下:

var sql = "select case t.name when 'a' then 1 else 2 end  from test t ";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlCaseExpression()
                {
                    Items = new List<SqlCaseItemExpression>()
                    {
                        new SqlCaseItemExpression()
                        {
                            Condition = new SqlStringExpression()
                            {
                                Value = "a"
                            },
                            Value = new SqlNumberExpression()
                            {
                                Value = 1M,
                            },
                        },
                    },
                    Else = new SqlNumberExpression()
                    {
                        Value = 2M,
                    },
                    Value = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "name",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t",
                        },
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
    },
};



在这种SqlCaseExpression表达式里,他包含了

  1. case条件的主体变量,即Value字段,本例子中值为SqlPropertyExpression,它的值为t.name
  2. 各种when then键值对的列表,即Items字段,列表里的每一个元素都是SqlCaseItemExpression表达式,SqlCaseItemExpression表达式,又包含了
    1. 条件,即Condition字段,在本例子中是字符串表达式SqlStringExpression,它的值为a
    2. 值,即Value字段,在本例子中值为SqlNumberExpression,它的值为1
  3. Else字段,即默认值,本例子中为数字2

1.2.9 not子句

var sql = "select * from TEST t WHERE not t.NAME ='abc'";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlNotExpression()
        {
            Body = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "NAME",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlStringExpression()
                {
                    Value = "abc"
                },
            },
        },
    },
};



not子句,主要体现在SqlNotExpression表达式里,它只有一个body字段,即代表否定的部分

1.2.10 变量子句

var sql = "select * from TEST t WHERE not t.NAME =:name";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Where = new SqlNotExpression()
        {
            Body = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "NAME",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlVariableExpression()
                {
                    Name = "name",
                    Prefix = ":",
                },
            },
        },
    },
};


变量子句,主要体现在SqlVariableExpression表达式里,它包括以下部分:

  1. 变量名,即字段Name,这里值为name
  2. 变量前缀,这里值为:

1.3 From数据源

在sql中,From关键字后面有多种形式来指定数据源。主要有以下几种

1.3.1 表名或者视图

select * from test

这个解析结果上面已经演示了。

1.3.2 子查询(子表)

var sql = "select * from (select * from test) t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlSelectExpression()
        {
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
            Query = new SqlSelectQueryExpression()
            {
                Columns = new List<SqlSelectItemExpression>()
                {
                    new SqlSelectItemExpression()
                    {
                        Body = new SqlAllColumnExpression()
                    },
                },
                From = new SqlTableExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "test",
                    },
                },
            },
        },
    },
};

在这个例子中,数据源From的值为一个SqlSelectExpression,即SqlSelectExpression中可以嵌套SqlSelectExpression,同时我们注意到内部的SqlSelectExpression有一个表别名的字段Alias,标识符的值为t,表示表别名为t;

1.3.3 连表查询(JOIN)

var sql = "select t1.id from test t1 left join test2 t2 on t1.id=t2.id right join test3 t3 on t2.id=t3.id";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "id",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t1",
                    },
                },
            },
        },
        From = new SqlJoinTableExpression()
        {
            Left = new SqlJoinTableExpression()
            {
                Left = new SqlTableExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "test",
                    },
                    Alias = new SqlIdentifierExpression()
                    {
                        Value = "t1",
                    },
                },
                JoinType = SqlJoinType.LeftJoin,
                Right = new SqlTableExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "test2",
                    },
                    Alias = new SqlIdentifierExpression()
                    {
                        Value = "t2",
                    },
                },
                Conditions = new SqlBinaryExpression()
                {
                    Left = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "id",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t1",
                        },
                    },
                    Operator = SqlBinaryOperator.EqualTo,
                    Right = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "id",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t2",
                        },
                    },
                },
            },
            JoinType = SqlJoinType.RightJoin,
            Right = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "test3",
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "t3",
                },
            },
            Conditions = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "id",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t2",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "id",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "t3",
                    },
                },
            },
        },
    },
};

在上面这个例子中,我们演示了连表查询是如何解析的,From字段的值为一个SqlJoinTableExpression,即连表查询表达式,他包含了

  1. 左边部分,即Left字段
  2. 右边部分,即Right字段
  3. 连接方式,即JoinType字段,值包括InnerJoin,LeftJoin,RightJoin,FullJoin,CrossJoin,CommaJoin
  4. 表关联条件,即Conditions字段。在这里,Conditions字段的值为一个二元表达式SqlBinaryExpression

在这个例子中,总共3张表联查,SqlJoinTableExpression中得left字段又是一个SqlJoinTableExpression,即SqlJoinTableExpression中可以嵌套SqlJoinTableExpression,无限套娃。

1.3.4 公用表表达式(CTE)

var sql = "with c1 as (select name from test t) , c2(name) AS (SELECT name FROM TEST2 t3 ) select *from c1 JOIN c2 ON c1.name=c2.name";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        WithSubQuerys = new List<SqlWithSubQueryExpression>()
        {
            new SqlWithSubQueryExpression()
            {
                Alias = new SqlIdentifierExpression()
                {
                    Value = "c1",
                },
                FromSelect = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlIdentifierExpression()
                                {
                                    Value = "name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "test",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "t",
                            },
                        },
                    },
                },
            },
            new SqlWithSubQueryExpression()
            {
                Alias = new SqlIdentifierExpression()
                {
                    Value = "c2",
                },
                FromSelect = new SqlSelectExpression()
                {
                    Query = new SqlSelectQueryExpression()
                    {
                        Columns = new List<SqlSelectItemExpression>()
                        {
                            new SqlSelectItemExpression()
                            {
                                Body = new SqlIdentifierExpression()
                                {
                                    Value = "name",
                                },
                            },
                        },
                        From = new SqlTableExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "TEST2",
                            },
                            Alias = new SqlIdentifierExpression()
                            {
                                Value = "t3",
                            },
                        },
                    },
                },
                Columns = new List<SqlIdentifierExpression>()
                {
                    new SqlIdentifierExpression()
                    {
                        Value = "name",
                    },
                },
            },
        },
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlJoinTableExpression()
        {
            Left = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "c1",
                },
            },
            JoinType = SqlJoinType.InnerJoin,
            Right = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "c2",
                },
            },
            Conditions = new SqlBinaryExpression()
            {
                Left = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "name",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "c1",
                    },
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "name",
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "c2",
                    },
                },
            },
        },
    },
};

公用表表达式(CTE),主要体现在SqlSelectQueryExpression的WithSubQuerys字段,他是一个SqlWithSubQueryExpression表达式列表,即公用表列表,它里面的每一个元素都是SqlWithSubQueryExpression表达式,此表达式,包含了

  1. 公共表的来源部分,即FromSelect字段,在本例子中,他的值是一个SqlSelectExpression表达式,即一个查询
  2. 公共表的表别名,即Alias字段,在本例子中,他的值是c1
  3. 公共表的列部分,即Columns字段,在本例子中只有一个列名,即name

1.3.5 函数返回的结果集

特定数据库支持从返回结果集的函数中查询,比如oracle中添加一个自定义函数splitstr,他的作用是将一个字符串根据;号进行分割,返回多行数据

var sql = "SELECT * FROM TABLE(splitstr('a;b',';'))";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlReferenceTableExpression()
        {
            FunctionCall = new SqlFunctionCallExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "TABLE",
                },
                Arguments = new List<SqlExpression>()
                {
                    new SqlFunctionCallExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "splitstr",
                        },
                        Arguments = new List<SqlExpression>()
                        {
                            new SqlStringExpression()
                            {
                                Value = "a;b"
                            },
                            new SqlStringExpression()
                            {
                                Value = ";"
                            },
                        },
                    },
                },
            },
        }
    },
};

函数返回的结果集主要体现在SqlReferenceTableExpression表达式,他的内部包含了一个FunctionCall字段,值为SqlFunctionCallExpression表达式,代表从函数调用的结果集中进行查询。

1.4 OrderBy排序语句

var sql = "select fa.FlowId  from FlowActivity fa order by fa.FlowId desc,fa.Id asc";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "FlowId"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "FlowActivity"
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "fa"
            },
        },
        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    Body =
                        new SqlPropertyExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "FlowId"
                            },
                            Table = new SqlIdentifierExpression()
                            {
                                Value = "fa"
                            },
                        },
                    OrderByType = SqlOrderByType.Desc
                },
                new SqlOrderByItemExpression()
                {
                    Body =
                        new SqlPropertyExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "Id"
                            },
                            Table = new SqlIdentifierExpression()
                            {
                                Value = "fa"
                            },
                        },
                    OrderByType = SqlOrderByType.Asc
                },
            },
        },
    },
};


OrderBy排序子句,值为SqlOrderByExpression表达式,表达式的内容也非常简单,只有一个Items,即一个排序子项表达式的列表,列表里的值为SqlOrderByItemExpression,即排序子项表达式,排序子项表达式里又包含了以下内容

  1. 排序依据,即Body字段,在这个例子中,排序依据是2个SqlPropertyExpression表达式,即根据fa.FlowId,fa.Id排序
  2. 排序类型,即OrderByType字段,值为Asc或者Desc,默认为asc,在这2个例子中,有asc和Desc
  3. 决定null排在前面或后面的NullsType字段,在oracle,pgsql,sqlite中我们可以指定null在排序中的位置,如以下sql
select * from TEST5 t order by t.NAME  desc nulls FIRST,t.AGE ASC NULLS  last 

那么我们的NullsType字段,他的值有SqlOrderByNullsType.First和SqlOrderByNullsType.Last,与之对应。

1.5 GroupBy分组语句

var sql = "select fa.FlowId  from FlowActivity fa group by fa.FlowId,fa.Id HAVING count(fa.Id)>1";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "FlowId"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "FlowActivity"
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "fa"
            },
        },
        GroupBy = new SqlGroupByExpression()
        {
            Items = new List<SqlExpression>()
            {
                new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "FlowId"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
                new SqlPropertyExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "Id"
                    },
                    Table = new SqlIdentifierExpression()
                    {
                        Value = "fa"
                    },
                },
            },
            Having = new SqlBinaryExpression()
            {
                Left = new SqlFunctionCallExpression()
                {
                    Name = new SqlIdentifierExpression()
                    {
                        Value = "count"
                    },
                    Arguments = new List<SqlExpression>()
                    {
                        new SqlPropertyExpression()
                        {
                            Name = new SqlIdentifierExpression()
                            {
                                Value = "Id"
                            },
                            Table = new SqlIdentifierExpression()
                            {
                                Value = "fa"
                            },
                        },
                    },
                },
                Operator = SqlBinaryOperator.GreaterThen,
                Right = new SqlNumberExpression()
                {
                    Value = 1M
                },
            },
        },
    },
};


GroupBy分组语句,值为SqlGroupByExpression表达式,他的内容如下

  1. 分组子项表达式的列表,即Items字段,列表里的值为SqlExpression,他的值是一个逻辑子句
  2. 分组过滤子句,即Having字段,他的值是一个逻辑子句,在本例子中,逻辑子句的值为一个SqlBinaryExpression

1.5 Limit分页子句

1.5.1 mysql,sqlite

var sql = "select * from test t limit 1,5";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        Limit = new SqlLimitExpression()
        {
            Offset = new SqlNumberExpression()
            {
                Value = 1M,
            },
            RowCount = new SqlNumberExpression()
            {
                Value = 5M,
            },
        },
    },
};


Limit分页子句,值为SqlLimitExpression表达式,他的内容如下

  1. 每页数量,即RowCount字段,这本例子中,值为5
  2. 跳过数量,即Offset字段,本例子中,值为1

1.5.2 oracle

var sql = "SELECT * FROM TEST3 t  ORDER BY t.NAME  DESC FETCH FIRST 2 rows ONLY";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            }
        },
        From = new SqlTableExpression()
        {
            Alias = new SqlIdentifierExpression()
            {
                Value = "t"
            },
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST3"
            }
        },

        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    OrderByType = SqlOrderByType.Desc,
                    Body = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression() { Value = "NAME" },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t"
                        }
                    }
                }
            }
        },
        Limit = new SqlLimitExpression()
        {
            RowCount = new SqlNumberExpression()
            {
                Value = 2
            }
        }
    }
};

1.5.3 pgsql

var sql = "select * from test5   t order by t.name limit 1 offset 10;";
var sqlAst = DbUtils.Parse(sql, DbType.Pgsql);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test5",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t",
            },
        },
        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    Body = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression()
                        {
                            Value = "name",
                        },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t",
                        },
                    },
                },
            },
        },
        Limit = new SqlLimitExpression()
        {
            Offset = new SqlNumberExpression()
            {
                Value = 10M,
            },
            RowCount = new SqlNumberExpression()
            {
                Value = 1M,
            },
        },
    },
};


1.5.4 sqlServer

var sql = "select * from test t order by t.name OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            }
        },
        From = new SqlTableExpression()
        {
            Alias = new SqlIdentifierExpression()
            {
                Value = "t"
            },
            Name = new SqlIdentifierExpression()
            {
                Value = "test"
            }
        },

        OrderBy = new SqlOrderByExpression()
        {
            Items = new List<SqlOrderByItemExpression>()
            {
                new SqlOrderByItemExpression()
                {
                    Body = new SqlPropertyExpression()
                    {
                        Name = new SqlIdentifierExpression() { Value = "name" },
                        Table = new SqlIdentifierExpression()
                        {
                            Value = "t"
                        }
                    }
                }
            }
        },
        Limit = new SqlLimitExpression()
        {
            Offset = new SqlNumberExpression()
            {
                Value = 5
            },
            RowCount = new SqlNumberExpression()
            {
                Value = 10
            }
        }
    }
};


1.6 ConnectBy层次查询语句(oracle专用)

var sql = "SELECT EMPLOYEEID , MANAGERID , LEVEL FROM EMPLOYEE e START WITH MANAGERID IS NULL CONNECT BY NOCYCLE PRIOR EMPLOYEEID = MANAGERID ORDER SIBLINGS BY EMPLOYEEID ";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "EMPLOYEEID",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "MANAGERID",
                },
            },
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "LEVEL",
                },
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "EMPLOYEE",
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "e",
            },
        },
        ConnectBy = new SqlConnectByExpression()
        {
            StartWith = new SqlBinaryExpression()
            {
                Left = new SqlIdentifierExpression()
                {
                    Value = "MANAGERID",
                },
                Operator = SqlBinaryOperator.Is,
                Right = new SqlNullExpression()
            },
            Body = new SqlBinaryExpression()
            {
                Left = new SqlIdentifierExpression()
                {
                    Value = "EMPLOYEEID",
                },
                Operator = SqlBinaryOperator.EqualTo,
                Right = new SqlIdentifierExpression()
                {
                    Value = "MANAGERID",
                },
            },
            IsNocycle = true,
            IsPrior = true,
            OrderBy = new SqlOrderByExpression()
            {
                Items = new List<SqlOrderByItemExpression>()
                {
                    new SqlOrderByItemExpression()
                    {
                        Body = new SqlIdentifierExpression()
                        {
                            Value = "EMPLOYEEID",
                        },
                    },
                },
                IsSiblings = true,
            },
        },
    },
};

ConnectBy层次查询子句,值为SqlConnectByExpression表达式,他的内容如下

  1. 指定层次查询的根节点条件,即StartWith字段,本例子中他的值为SqlBinaryExpression二元表达式
  2. 主体关联条件子句,即Body字段,本例子中他的值是一个SqlBinaryExpression二元表达式
  3. IsPrior字段用来指示层次结构中哪个列是父节点,如果sql中存在Prior关键字,则值为true
  4. IsNocycle字段,用来防止循环引用导致无限递归,如果sql中存在Nocycle则为true
  5. order by子句,用于排序

1.7 Into子句(sql server专用)

var sql = "SELECT name into test14 from TEST as t ";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlIdentifierExpression()
                {
                    Value = "name"
                },
            },
        },
        Into = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test14"
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "TEST"
            },
            Alias = new SqlIdentifierExpression()
            {
                Value = "t"
            },
        },
    },
};

into子句,在本例子中值为SqlTableExpression,即into到某张表里。

2. Insert插入语句

2.1 插入单个值

var sql = "insert into test11(name,id) values('a1','a2')";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlInsertExpression()
{
    Columns = new List<SqlExpression>()
    {
        new SqlIdentifierExpression()
        {
            Value = "name"
        },
        new SqlIdentifierExpression()
        {
            Value = "id"
        },
    },
    ValuesList = new List<List<SqlExpression>>()
    {
        new List<SqlExpression>()
        {
            new SqlStringExpression()
            {
                Value = "a1"
            },
            new SqlStringExpression()
            {
                Value = "a2"
            },
        },
    },
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test11"
        },
    },
};

如上例子,插入语句表现为一个SqlInsertExpression,他包含了

  1. 要插入的字段列表,即Columns字段,值为一个SqlExpression的列表,本例子中值为2个SqlIdentifierExpression,它们的值为name和id,即插入name和id字段
  2. 值列表,即ValuesList字段,值为一个List<SqlExpression>的列表,即列表里的元素是一个列表,每个元素代表一组待插入的数据,本例子中列表里只有一个List,并且子列表里的值为a1和a2,即待插入的值为a1和a2。
  3. 待插入数据的表,即Table字段,本例子中值为test11

为什么ValuesList字段是列表里嵌套列表呢?主要是因为可以插入多个值列表,让我们继续往下看

2.2 插入多个值

var sql = "insert into test11(name,id) values('a1','a2'),('a3','a4')";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlInsertExpression()
{
    Columns = new List<SqlExpression>()
    {
        new SqlIdentifierExpression()
        {
            Value = "name"
        },
        new SqlIdentifierExpression()
        {
            Value = "id"
        },
    },
    ValuesList = new List<List<SqlExpression>>()
    {
        new List<SqlExpression>()
        {
            new SqlStringExpression()
            {
                Value = "a1"
            },
            new SqlStringExpression()
            {
                Value = "a2"
            },
        },
        new List<SqlExpression>()
        {
            new SqlStringExpression()
            {
                Value = "a3"
            },
            new SqlStringExpression()
            {
                Value = "a4"
            },
        },
    },
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test11"
        },
    },
};

在本例子中,ValuesList字段中有2个子元素,即2个List列表,代表插入2组数据,值分别为(‘a1′,’a2’)和(‘a3′,’a4’)

2.3 待插入的值为一个子查询

var sql = "INSERT INTO TEST2(name) SELECT name AS name2 FROM TEST t";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlInsertExpression()
{
    Columns = new List<SqlExpression>()
    {
        new SqlIdentifierExpression()
        {
            Value = "name"
        },
    },
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "TEST2"
        },
    },
    FromSelect = new SqlSelectExpression()
    {
        Query = new SqlSelectQueryExpression()
        {
            Columns = new List<SqlSelectItemExpression>()
            {
                new SqlSelectItemExpression()
                {
                    Body = new SqlIdentifierExpression()
                    {
                        Value = "name"
                    },
                    Alias = new SqlIdentifierExpression()
                    {
                        Value = "name2"
                    },
                },
            },
            From = new SqlTableExpression()
            {
                Name = new SqlIdentifierExpression()
                {
                    Value = "TEST"
                },
                Alias = new SqlIdentifierExpression()
                {
                    Value = "t"
                },
            },
        },
    },
};

如上例子,插入语句表现为一个SqlInsertExpression,他包含了

  1. 要插入的字段列表,即Columns字段,值为一个SqlExpression的列表,本例子中值为name
  2. 子查询来源,即FromSelect字段,值为一个SqlSelectExpression,即一个子查询
  3. 待插入数据的表,即Table字段,本例子中值为TEST2

3. Update更新语句

var sql = "update test set name ='4',d='2024-11-22 08:19:47.243' where name ='1'";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlUpdateExpression()
{
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test"
        },
    },
    Where = new SqlBinaryExpression()
    {
        Left = new SqlIdentifierExpression()
        {
            Value = "name"
        },
        Operator = SqlBinaryOperator.EqualTo,
        Right = new SqlStringExpression()
        {
            Value = "1"
        },
    },
    Items = new List<SqlExpression>()
    {
        new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "name"
            },
            Operator = SqlBinaryOperator.EqualTo,
            Right = new SqlStringExpression()
            {
                Value = "4"
            },
        },
        new SqlBinaryExpression()
        {
            Left = new SqlIdentifierExpression()
            {
                Value = "d"
            },
            Operator = SqlBinaryOperator.EqualTo,
            Right = new SqlStringExpression()
            {
                Value = "2024-11-22 08:19:47.243"
            },
        },
    },
};

如上例子,更新语句表现为一个SqlUpdateExpression,他包含了

  1. 要更新的(字段-值)的列表,即Items字段,值为一个SqlExpression的列表,本例子中值为2个SqlBinaryExpression,即name=’4’和d=’2024-11-22 08:19:47.243′
  2. 条件过滤子句,即Where字段,代表过滤条件,本例子中值为一个SqlBinaryExpression,即name =’1′
  3. 待更新数据的表,即Table字段,本例子中值为test

4. Delete删除语句

var sql = "delete from test where name=4";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);

解析结果如下:

var expect = new SqlDeleteExpression()
{
    Table = new SqlTableExpression()
    {
        Name = new SqlIdentifierExpression()
        {
            Value = "test"
        },
    },
    Where = new SqlBinaryExpression()
    {
        Left = new SqlIdentifierExpression()
        {
            Value = "name"
        },
        Operator = SqlBinaryOperator.EqualTo,
        Right = new SqlNumberExpression()
        {
            Value = 4M
        },
    },
};

如上例子,删除语句表现为一个SqlDeleteExpression,他包含了

  1. 条件过滤子句,即Where字段,代表过滤条件,本例子中值为一个SqlBinaryExpression,即name=4
  2. 待删除数据的表,即Table字段,本例子中值为test

5. 注释处理

5.1 单行注释

var sql = @"select *--abc from test lbu WHERE a ='1'--aaaaaa
FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.SqlServer);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

如上例子,单行注释被正确忽视,解析正确。

5.2 多行注释

var sql = @"/*这
            是
            顶部*/
            select *--abc
            FROM test/*这
            是
            底部*/";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析结果如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

如上例子,多行注释被正确忽视,解析正确。

6. 如何解析ast抽象语法树

当我们通过

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析sql获取到抽象语法树以后,我们就要对这颗抽象语法树进行解析,获取我们想要的数据,此时就要用上访问者模式(visitor) .

6.1 访问者模式

访问者模式最大的特点就是结构与算法分离,结合本项目理解,就是ast抽象语法树这个结构已经解析出来了,你可以根据自己的需要写算法去任意解析这颗语法树。这是一个1-N的操作,即一个抽象语法树,可以对应N个解析算法,当我们要自定义算法去解析抽象语法树时,我们需要自定义一个Visitor类,并且实现IAstVisitor接口

public class CustomVisitor : IAcceptVisitor
{
    
}

但是实现这个接口要实现接口里的很多个方法,并且有些数据并不是我们关心的,所以我提供了一个实现了IAcceptVisitor接口的抽象类BaseAstVisitor用来简化操作,我们只需要继承这个抽象类,然后重写我们感兴趣的方法即可

public class CustomVisitor : BaseAstVisitor
{
    
}

在本项目中,我提供了2个基本的vistor供大家使用,UnitTestAstVisitor和SqlGenerationAstVisitor,大家可以参考这2个visitor去写自己的算法来解析抽象语法树。接下来,我将介绍这2个visitor的用法。

6.2 UnitTestAstVisitor

当我们通过

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);

解析sql获取到抽象语法树之后,sqlAst其实还是一个数据结构,我们可以通过vs监视这个变量来查看内部的结构,但是如果是非常复杂的sql,那这颗树会巨大无比,要靠我们手动去慢慢点开查看得累死,没错!写单元测试的时候,我刚开始都是用手写的结果去对比引擎解析出来的结果,后来我就被累死了,该说不说,这活真不是人干的,所以痛定思痛之后我就写了这个UnitTestAstVisitor来替我生成ast的结构字符串,接下来让我们看看用法

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
var unitTestAstVisitor = new UnitTestAstVisitor();
sqlAst.Accept(unitTestAstVisitor);
var result = unitTestAstVisitor.GetResult();

其中的result就是解析抽象语法树生成的字符串,如下:

var expect = new SqlSelectExpression()
{
    Query = new SqlSelectQueryExpression()
    {
        Columns = new List<SqlSelectItemExpression>()
        {
            new SqlSelectItemExpression()
            {
                Body = new SqlAllColumnExpression()
            },
        },
        From = new SqlTableExpression()
        {
            Name = new SqlIdentifierExpression()
            {
                Value = "test",
            },
        },
    },
};

然后把这个生成的字符串黏贴到vs里去和引擎生成的结果进行对比,

Assert.True(sqlAst.Equals(expect));

至此,我写单元测试的工作量大大减轻,同时对于生成的sqlAst语法树的结构也更加一目了然了。

6.2 SqlGenerationAstVisitor

我们通过解析sql生成了抽象语法树之后,如果我们想要给这颗抽象语法树添加一个where条件,比如添加test.name =’a’

var sql = @"select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
if (sqlAst is SqlSelectExpression sqlSelectExpression && sqlSelectExpression.Query is SqlSelectQueryExpression sqlSelectQueryExpression)
{
    sqlSelectQueryExpression.Where = new SqlBinaryExpression()
    {
        Left = new SqlPropertyExpression()
        {
            Table = new SqlIdentifierExpression()
            {
                Value = "test"
            },
            Name = new SqlIdentifierExpression()
            {
                Value = "name"
            }
        },
        Operator = SqlBinaryOperator.EqualTo,
        Right = new SqlStringExpression()
        {
            Value = "a"
        }
    };
}

好了,现在我们添加完了,接下来我们肯定是想着把抽象语法树转化为sql语句,此时,就需要用上SqlGenerationAstVisitor了,他就是负责把抽象语法树转化为sql的

var sqlGenerationAstVisitor = new SqlGenerationAstVisitor(DbType.Oracle);
sqlAst.Accept(sqlGenerationAstVisitor);
var newSql = sqlGenerationAstVisitor.GetResult();

我们获取到的newSql就是新的sql了,他的值为

select * from test where(test.name = 'a')

至此,我们的目的就达到了。

7. sql解析的理论基础

sql之所以能被我们解析出来,主要是因为sql是一种形式语言,自然语言和形式语言的一个重要区别是,自然语言的一个语句,可能有多重含义,而形式语言的一个语句,只能有一个语义;形式语言的语法是人为规定的,有了一定的语法规则,语法解析器就能根据语法规则,解析出一个语句的唯一含义。

项目感悟

  1. 解决嵌套问题的唯一方案,就是用递归
  2. 对于基础项目,单元测试非常非常重要,因为开发的过程中可能会不断地重构,那以前跑过的测试案例就有可能失败,如果此时需要靠人手工去回归测试验证的话,那工作量是天量的,做不完,根本做不完,所以正确的解决方案是写单元测试,新添加一个功能后,为这个功能写1-N个单元测试,确保新功能对各种情况都有覆盖到,然后再跑一遍所有单元测试,确保没有影响到旧的功能。当然了,跑单元测试最让我崩溃的是,跑一遍所有单元测试,红了(即失败)几十个,天都塌了。

开源地址,欢迎star

本项目基于MIT协议开源,地址为
https://github.com/TripleView/SqlParser.Net

同时感谢以下项目

  1. 阿里巴巴开源的druid

写在最后

如果各位靓仔觉得这个项目不错,欢迎一键三连(推荐,star,关注),同时欢迎加入三合的开源交流群,QQ群号:799648362