.Net Core应用框架Util介绍(二) - 何镇汐 - 博客园

mikel阅读(978)

来源: .Net Core应用框架Util介绍(二) – 何镇汐 – 博客园

Util的开源地址

https://github.com/dotnetcore/util

 

Util的开源协议

Util以MIT协议开源,这是目前最宽松的开源协议,你不仅可以用于商业项目,还能把Util的代码放进你的框架,放心使用。

 

Util的命名

  Util这个名字看上去不怎么高大上,很多人劝我换个霸气点的,比如USharp。

不过我还是坚持使用Util,因为他简短,也更好记忆,Util很多类名与.Net或第三方类库重名,需要使用完全限定名来调用它,比如Util.Helpers.String.Join()。

另一个经常被提到的问题是Util是一个常用词汇,容易与其它类库产生冲突,不过Util的定位是为小团队提供帮助,一般情况下,这不会产生问题,另外Util会封装大量常用类库,冲突会在Util内部处理。在极端的情况下,你还能使用using创建别名来自己解决。

 

Util的使用方法

Util尚未成熟,所以我还没有发布正式版,Nuget发布的是预览版,如果你想通过Nuget引用,请勾选“包括预发行版”。

尚未成熟并不表示Util Bug很多,还无法使用,只是还没达到我心目中理想的那个样子。对于中小项目,很多常用功能都已经封装,最新版本的Util已经在我的项目上使用,已知问题已全部修复。

被问得最多的问题是Util有文档吗,抱歉,还真没有。我的打算是待Util成熟后,再建立完整文档,并创建一个官网。

当然,我希望有更多人能参与进来一起分享技术,一个人精力和能力都是有限的,进度会非常缓慢。

由于目前没有文档,你只能通过查看源码进行学习,这有点痛苦,不过也能让你基础打得更扎实一点。

你如果对Util及Util引入的相关技术还不太了解,那么不应该将它应用在一个非常紧急的项目上,这会导致风险。

Util主要是我用来满足自己项目的产物,所以它可能不能完全满足你的需求,作为项目负责人,你必须有能力扩展它。

学习Util的起点是提供的Demo,我演示了CRUD的基本用法,不过这个Demo是用Angular开发的,环境上有点折腾,后续我会再提供一个ASP.NET Core Mvc基于JQuery的示例。

这个Angular Demo不仅演示了服务端CRUD的封装,同时也演示了TagHelper是如何与Angular配合起来让UI变得清晰省力。

当你把这个Demo运行起来,就可以通过查看源码的方式了解它的运行机制,基本了解清楚后,再自己做几个复杂点的Demo来练手,有把握再运用到你的项目上。

如果你在使用中发现Bug,请不要亲自动手,你应该到Github提交Issue,或直接到群里来找我,我会以迅雷不及掩耳之势搞定它。

如果你希望我为你扩展某些功能,也可以提Issue,不过我会评估是否具有价值,冷门需求只能靠你自己完成。

如果你喜欢折腾,推荐你创建自己的应用框架,将Util以及其它框架的源码慢慢吸收到你的框架中,这能让你最大限度的了解框架内部实现机制,并能大幅增强你的编码和设计能力。

对于完全不能或完全不想折腾的朋友,建议你关掉网页,好好享受生活,因为新的技术,特别是前端技术真的很折腾。

如果你有新项目,希望采用.Net Core开发,Util将是一个很好的起点,他将为你节省大把的时间,我最近几年踩过的坑都埋在了Util中。

 

Util的开发环境

 

.Net Core SDK

我们在使用.Net Framework时,通常是在更换VS时升级SDK。VS换代需要好几年,所以你可能并不太关注SDK的版本。

不过进入.Net Core,情况有所不同,一般几个月就会更新一次,Util会紧跟.Net Core最新版本,你必须关注SDK的版本问题。

如果你不知道在哪里下载SDK,在百度搜索可不是那么容易,下面介绍下载.Net Core SDK的正确姿势。

在任意项目上右键,弹出菜单中选择属性。

在“目标框架”选择“安装其它框架”,进入下载页面。

下载页面选择最新的正式版本SDK,SDK是给开发机器用的,Runtime是在部署到服务器上用的,SDK已经包含了Runtime,注意这个区别。

 

开发工具

  • VS 2017最新版本。

如果你还在用VS2015,但又想用最新技术,这可能让你吃尽苦头。不要担心,VS2015和VS2017可以共存,多安装一个就好了。

  • Resharper(R#)最新版本。

R#是一个VS上的重构插件,开发代码不一定需要R#,但你想开发出更优雅的代码,R#就是一个神兵利器。VS的代码提示功能很强,但R#能把VS的提示功能再提升几个档次。

R#的主要毛病是非常卡,如果你的电脑没有固态硬盘,内存连16G也没有,建议你不要用。

数据库

我现在主要用SQL Server来做测试,生产环境使用的是PgSQL

目前提供的Demo包含一个Sql Server的建库脚本,所以你需要安装它,尚未提供EF迁移和其它数据库脚本,后续有空会增加上来。

  • PostgreSql(PgSql)
  • MySql

前端环境

几年前,如果需要某个Js组件,我会从网上下载并把它放进项目,然后在页面上引用它。每当有版本更新,我就到它的官网下载一个新的。为了让页面加载得更快,我会在发布时用打包工具把多个Js文件打成一个,然后再用压缩工具给它瘦身。

如果只引用了少量几个Js文件,这样做没什么问题,简单易懂,是个人就能做。

随着前端需求的增加,你慢慢引入了十几个,几十个乃至数百个Js文件,维护它们就是一场噩梦。

如果能让文件下载,版本更新,打包压缩全部自动化,你就能从大堆Js文件中解脱出来。

NodeJs与它的生态系统解决了这个难题。

  • NodeJs

NodeJs是利用Google Chrome V8引擎来执行Js的运行环境,这样Js就可以脱离浏览器运行了。

首先查看你的机器是否已安装NodeJs,在命令行输入node -v 。

如果你跟我一样,对命令行十分反感,是时候作出一些改变了,某些场景命令行比UI操作更便利,还有一些情况必须使用命令行,比如Npm还原。采用.Net Core最大的亮点是可以跨平台部署,换句话说,你可以把代码部署到Linux, 为了节省服务器资源,通常不会在Linux服务器上启动UI,你必须能通过黑屏命令行操控它。

你可以看到我的机器上安装的NodeJs版本是8.9.3,如果你的NodeJs版本很低,可能会让后续操作失败,另外我听到群里有同学反馈,NodeJs版本过高也会出现问题,我还没有测试过,如果你环境上出现问题,可以安装我这个版本。

你可以通过这个地址http://nodejs.cn/download下载安装NodeJs。安装完成,记得重启电脑,这是怪异问题的必杀技之一。

 

 

 

  • Npm(Node Package Manager)

Npm是NodeJs包管理器,它类似于我们的Nuget。当安装完NodeJs,Npm会被默认安装。

前端非常混乱,包管理器就有很多,比如Bower,VS在之前的版本默认提供了对Bower的支持,不过后面删除了它,这也从侧面说明了Npm击败了竞争对手,成为前端事实上的标准包管理器。

当你需要某个Js组件时,不用再千里迢迢打开网站下载,只需打开Npm配置文件package.json,把你要下载的Js组件写上去就好了。

想知道某个Js组件有没有更新,只需在package.json移动鼠标即可。

或者直接在package.json的版本号位置敲代码提示快捷键,Ctrl + 空格 或 Ctrl + J。

 

当你把需要的Js组件添加到package.json,Ctrl + S保存以后,VS就开始自动下载了,这让你心情非常愉快,VS不愧是银河系最强的IDE。

过了一个小时,你吃完饭回来,发现它还在下载,难道是网络不好?这是由我国众所周知的原因导致的。

要想让Npm还原成功,你必须禁止VS Npm自动还原,先来设置下VS。

在国内如果没有特殊装置,Npm基本无法还原成功。

一种选择是使用淘宝镜像,这是我初期使用的方法。

另一种方法是使用Yarn来还原Npm,有人给我推荐了它,使用后发现这家伙的下载速度更快,我现在也强烈推荐给你。

先去这里https://yarnpkg.com/zh-Hans/docs/install#windows-stable下载一个Yarn安装包。

安装完成,命令行yarn –v,看看是否安装成功。

  •  Webpack

Webpack是NodeJs生态中用来进行自动化构建的工具。它非常强大,打包压缩不过是它的冰山一角。

前端的混乱来自开源项目的百家争鸣和低耦合的设计理念。

前端技术一片繁荣,这得益于开源项目的蓬勃发展,低耦合的设计理念让你在遇到相关问题只需寻找对应组件,集成上来就能结束战斗。

这确实很好,但却苦了我们这些码农。

你必须对各种框架,各种组件,以及这些组件中的插件非常了解,否则你无法工作。

这大幅增加了学习成本,专业前端人员薪资高确实是有道理的,看看别人吃了多少苦头。

还在怀念.Net前几年高度集成的开发方式吗?那个时代已经过去了。

在Webpack之前,还有Grunt,Gulp等构建工具,Webpack凭借递归查找依赖的能力打败了它们,成为前端事实上的标准自动化构建工具。

Webpack是一个插件体系,拥有丰富的插件,不论打包压缩,还是将Ts编译成Js,都有相应的插件支持。

Webpack表面上很易用,这是建立在你对它的插件很了解的基础上,如果你是个新手,想添加某个特性,但却不知道哪个插件具有这个功能,通过查看webpack官网并不一定能解决问题,因为很多插件的说明非常模糊,你只能自己多尝试。

虽然如此,你也大可不必泄气,你所使用的前端框架,会为你做好Webpack配置,基本不用操心。

一旦把Webpack配置好,它就会非常易用,自动化构建的整个流程被隐藏在一行npm命令中。

Webpack通过Npm安装,所以不需要单独安装它了。

 

Util Demo运行说明

开发环境确认

上面已经简要介绍了Util的开发环境,为了将Util Demo运行起来,我们再确认一遍。

  • 你安装了VS 2017最新版本。
  • 你安装了.Net Core SDK正式版最新版本。
  • 你安装了Sql Server 2012或更新版本。
  • 你安装了NodeJs。
  • 你安装了Yarn。
  • 你已经从Github下载了Util,并保证是最新版本。

运行数据库脚本

由于我开发采用的是Db First,所以给你提供了一个Sql Server的建库脚本,这也是我平时的测试方式。

不要担心,你完全可以使用Code First方式,这只是我的个人习惯问题。

请将Sample.sql建库脚本复制到Sql Server并执行。

下面,你需要修改Util.Samples.Webs项目数据库连接字符串,它在这里。

还原Npm

Util.Samples.Webs项目上右键,选择“在文件资源管理器中打开文件夹”,进入Util.Samples.Webs项目根目录。

按住Shift,在Util.Samples.Webs项目根目录空白位置右键,选择“在此处打开命令窗口”。这样做的目的是打开一个命令行,并且路径是Util.Samples.Webs项目根目录。

注意:不同的操作系统,菜单提示不同,我用的是Windows 2008 R2。

输入命令yarn,开始还原。

 

如果你人品爆发,可能没有看见任何错误就还原成功了。

不过在大部分情况下,你都会得到一个错误。这是由大名鼎鼎的巨无霸先生node-sass导致的。

node-sass是用来帮助将scss编译成css的编译工具,scss是一个css预处理器,给css增加了变量,控制结构等编程元素。Boostrap等现代前端框架都采用scss编写。

我观察到,node-sass从npm下载后,会运行脚本,然后访问github及其它Url,毫无疑问,这将被拦截在摇篮中。

安装node-sass的解决方案是cnpm,这是淘宝提供的npm包安装工具。

我们先来安装cnpm。

npm install -g cnpm --registry=https://registry.npm.taobao.org

下面用cnpm来安装node-sass。

cnpm install node-sass

如果你运气不是太差,npm包应该还原成功了。

如果你是第一次接触npm,在还原完成后,项目根目录会出现一个node_modules文件夹,你细数了一下文件的数目,大吃一惊,居然有10几万个文件,这就是低耦合的悲剧,习惯就好。

在cnpm还原node-sass的提示中,有一个版本警告信息,不要管它,目前我使用的是node-sass 4.7.2这个版本,更高版本有编码错误,如果在scss中存在中文注释,会编译失败。

我发现npm治愈了我的升级强迫症,你如果也有这个毛病,请小心测试后再升级。

当你发现cnpm如此强悍,连node-sass这个巨无霸都败在它的脚下,你可能会直接用cnpm还原所有npm包。

注意:cnpm还原的包与npm并不完全相同,特别是使用VS开发前端项目,问题尤其严重,在打开解决方案时,会卡住好几分钟,你只应该用cnpm来还原node-sass,其它的采用yarn

 

执行Webpack构建流程

你只需要一行npm命令就能把webpack构建流程跑起来。

npm run dev

如果这个过程没有报任何异常,说明构建成功了。

如果有异常,通常说明npm包尚未完全还原成功。

 

运行Demo

终于你可以把Demo运行起来了,F5把浏览器弹起来。

这是一个貌不惊人的Crud Demo,看到这个界面很多人可能大失所望,搞了半天就这么个东西?

不要急,我给你展示的只是最简单的东西,好让你快速上手,Util封装的是Angular官方提供的Material这套组件库,你可以到Material的官网https://material.angular.io/看看它包含哪些东西。

Material缺乏像Ng Alain这样的脚手架主界面,我项目上是前端人员自己搭建的。另外似乎国内用Material风格来开发管理后台的并不多,基于这个原因,我已经有封装Ng Alain + Ng-Zorro的计划。

另一个Demo页面展示了树型Crud操作,将向你展示Util是如何对树型关系进行封装的。

 

注意事项

如果你根据上面的介绍还是无法将npm还原成功,可能有以下原因。

  • VS进程锁定了node_modules目录,你在还原时最好把VS关掉。
  • 如果你之前已经还原过,可能存在垃圾文件干扰,这需要先删除node_modules目录,你可以直接删除文件夹,不过可能需要半小时以上时间,你应该通过命令行来删除它。
npm install rimraf -g
rimraf node_modules

删除node_modules目录也应该关掉VS,以免干扰。

  • 你需要特别关照node_sass,没事就cnpm搞它一下。

小结

本文介绍了Util的运行环境以及注意事项,初次接触现代前端环境会有点不适,不过挺过去你就能再多干几年。

未完待续,下一篇将对Util Demo的运行机制进行介绍。

  写文需要动力,请大家多多支持,点下推荐,Github点下星星。

Util应用框架交流一群: 24791014

版权所有,转载请注明出处 何镇汐的技术博客

.Net Core应用框架Util介绍(一) - 何镇汐 - 博客园

mikel阅读(853)

来源: .Net Core应用框架Util介绍(一) – 何镇汐 – 博客园

  距离上次发文,已经过去了三年半,这几年技术更新节奏异常迅猛,.Net进入了跨平台时代,前端也被革命性的颠覆。

回顾

2015年,正当我还沉迷于JQuery + EasyUi的封装时,突然意识到技术已经过时。

JQuery在面对更加复杂的UI需求时显得力不从心,EasyUi虽然组件比较完善,但界面风格老旧,响应速度慢,且是收费商业产品,在一个商业产品上投入精力封装并不划算,所以我果断弃坑了。

然后开始封装jQuery + Bootstrap,在一个SPA(单页应用)项目里暴露出很多问题,让我认识到jQuery不适合做SPA,我开始寻找新的方案。

AngularJs(ng 1.x)是谷歌出品的JS框架,几本书下肚以后,我开始琢磨着如何把AngularJs操作封装起来,为了封装成链式调用,甚至改了它的源码以支持服务定位器模式。

正当我准备封装基于AngularJs的UI组件时,发现它也过时了。由于UI封装工作量巨大,而前端环境异常混乱,不再敢轻举妄动,直到前端三大框架浮出水面。

2016年,.Net Core兴起,为了方便学习交流,Alexinea(刘怡)发起了.Net Core学习小组,Kiler(谢炀)、Lemon(刘浩杨)和我做为第一批成员加入了小组。

微软当时尚未提供.Net Core中文文档,为了方便国内.Net Core的学习和推广,.Net Core学习小组组织了一批.Net爱好者进行翻译,直到官方提供了中文文档为止。

2017年,.Net Core学习小组决定发布开源项目以推动.Net Core的发展,.Net Core学习小组也正式改名为.NET Core Community(.Net Core中国社区),简称NCC。

最初加入的项目有Lemon的AOP框架AspectCore,Savorboard(杨晓东)的分布式事务解决方案CAP框架,以及我的应用框架Util。随后一些优秀的开源项目加入了NCC,包括爬虫解决方案DotnetSpider,分布式微服务框架Surging等。

Util产生的背景

我长期混迹于小型软件公司,小公司资源有限,人少事多,水平也参差不齐,如何降低团队的学习成本,如何提高项目的开发进度,如何降低Bug率,是我的主要关注点。

Util应用框架是我在多年的项目实战中积累起来以解决上述问题的利器,实践证明,它在多个小型团队和多个中小项目上起到关键作用,甚至包括一些濒临流产的项目。

中小项目的瓶颈在哪

对于中小项目,简单CRUD(增删改查)占据大量篇幅,核心模块包含复杂业务逻辑,报表包含复杂查询,另外还需要权限控制、流程控制等,不管模块再多,大体不出这个范围。

前端UI体验变得越来越重要,工作量也越来越大。一个简单CRUD,服务端API开发半小时,UI可能需要折腾一天。

如何打破瓶颈

如果能够迅速拿下CRUD,并且能有效减轻前端开发任务,那么就能将更多精力投入核心业务,从而提高项目交付能力。

对于服务端CRUD,通过封装基类再配合代码生成就能很好解决,如何提升前端开发效率?

正确认识”前后分离”

一些人鼓吹“前后分离“原则,前端界面交给专业的前端人员,后端人员只负责提供API就好了,这似乎是提升前端开发效率的灵丹妙药。

问题在于小公司资金预算有限,而专业前端人员薪资要求很高,以低价招聘的前端人员,往往只会HTML + CSS,JS耍得并不溜,最后还得服务端程序员上场。

另一方面,前端还有前台和后台之分,前台是面向终端用户的网站,比如门户网站,商城一类,前台偏重展示,规律性不强,后台是面向用户或管理员的表单系统,偏重功能,规律性比较强。大多公司的前端人员主要开发前台网站,而管理后台还是服务端程序员开发,前端人员顶多帮助界面布局,功能还是后端人员完成。

“前后分离”不一定是人员的分离,也不能降低工作量,主要是指前后端依赖关系的分离,如果前端技术或后端技术可随意替换,而不是绑定在一起,就认为分离成功,这提升了项目的可维护性。

Js框架的选择

为了降低前端开发难度,选择一个好的Js框架显得特别重要。

对于一个复杂界面操作,jQuery同样可以完成任务,但与现代主流的Js框架相比,JQuery操作Dom的方式更加复杂,效率低下,且难以维护。

现代前端三大Js框架是Angular(ng 2+),Vue和React。通过学习,发现Angular更符合我对开发效率的追求。

要提升开发效率,最关键的特性就是代码提示。不相信??请在记事本上用C#写几句试试。

Angular是谷歌开发的Js框架,默认采用Typescript(Ts)语言开发,Typescript是微软开发的强类型脚本语言,它是Js的超集,在VS或VSCode上具有代码提示。

你可能会说,Js不也有代码提示?这完全不一样,Ts具有强类型的代码提示,你只能看见对象上明确定义的成员,提供了非常精确的提示,而Js的代码提示滚动条好几米长,和当前对象无关的信息也显示出来,这严重降低了代码提示的作用。

除了代码提示,Ts还填平了Js固有的一些设计缺陷。

很多开发人员不选择Angular的原因是需要多学一门脚本语言,认为成本高,这其实是一种误解。

Ts在语法上有点像Js + C# + Java的混合体,对于C#或Java程序员,上手成本非常低,开发起来让你爽不停。

作为微软系.Net程序员的我们更应该大力支持。

声明式编程

现在我们在开发服务端和前端脚本时都有了代码提示,工作效率得到提升,还能更进一步吗?

现代流行的前端框架都有组件或指令的概念,用来支持声明式编程,它通过扩展Html自定义标签或自定义属性的方式来调用Js

这种方式将Js隐藏在内部,将Js转化成了Html,形成更好的封装性。毫无疑问,使用Html编写的页面,比使用Js具有更强的表现力。

如果文本框需要非空验证,只需要在标签上加个属性,就像下面这样,你并不需要调用任何Js就完成了验证工作,这相当酷。

1
<input required=”true”>

不过我发现很多团队并不使用声明式编程方式,更愿意使用Js,原因在于Html是弱类型标签,自定义属性并没有代码提示,这确实是个大问题。

TagHelper – 编写Html的救命稻草

我在2012年学习Dwz这个前端框架时,接触到声明式编程的概念,也被Html无提示的问题所困扰,后面发现ASP.NET提供的HtmlHelper能够封装Html,提供强类型的代码提示,这正是我需要的特性。

2015年用HtmlHelper封装了EasyUi,虽然只完成了一些基础工作,但使用过的人都认为它大量提升了开发效率,它现在甚至仍然是一些公司的主打技术。

HtmlHelper也有缺陷,它看上去是C#代码,而不是标签,比如HtmlHelper封装的文本框长成这样。

如果需要前端同学来帮助你调整界面,这就不太友好了。

另外HtmlHelper与Html混在一起也让代码看上去不直观。

微软发现了这个问题,在ASP.NET Core推出了TagHelper,TagHelper与HtmlHelper类似,也是用来封装Html的强类型工具,不过它用起来是个标签,如果你不注意,根本看不出这是Html,还是TagHelper,这对前后端人员配合完成Html页面具有重大意义。

现在TagHelper封装的文本框长成这样了。

TagHelper与Angular结合的艰难之路

使用Angular或Vue框架,你通常不会从0开始工作,寻找现成的UI组件库是更明智的选择,比如Angular可以选择Angular Material或Ng-Zorro。

这些库都提供了大量组件和属性,如果直接使用Html编写,没有代码提示,你必须随时打开官网,四处翻上翻下以寻找你需要的属性,哪怕你曾经用过它,如果你记得不是十分精确,从官网复制粘贴会时刻伴随着你。

现在好了,你知道TagHelper是救命稻草,可能已经迫不急待想要动手封装了,不就是把Html拼接出来输出到页面,这能有多难?别急,小伙子,将Angular组件封装成TagHelper可不是你想的这么简单。

如果你关注过ASP.NET Core Angular这个模板项目,你会发现这个模板使用的依然是Html,而不是TagHelper,这是为什么?自家这么好的技术,你都不推广一下?

我凭借之前封装HtmlHelper所积累下来的经验,将Asp.Net Core 2.0 Angular这个模板项目改造成支持TagHelper,这是使用Angular提供的JIT编译特性做到的。JIT就是即时编译,会在运行时动态获取Html页面并编译成内部Js,我在Github上也发现国外有些人在使用这种方式,不过都处于Demo级别。

我用TagHelper封装了Angular Material组件库,并将它应用在项目上。

很快爆发了严重的性能问题,这时候项目上的页面也才上百个。

从启动到看见登录页面,需要半分钟,F5刷新还需要这么久,这个速度是无法接受的。

我观察到系统在启动时会请求所有页面,原来我虽然使用了Angular模块,但没有使用延迟加载,主模块直接引用了子模块,导致在启动时加载全部页面。

使用延迟加载缓解了这个问题,启动能在三秒左右完成,进入子模块也需要几秒,虽然能勉强使用,但性能和AOT编译相去甚远。

AOT编译是Angular提供的预编译,能在发布阶段把Html文件直接编译成Js,这样在运行时就不再访问Html页面,对性能具有极大的提升。

使用了TagHelper以后,并没有Html文件,AOT编译无法访问服务端Url,所以没办法使用。

鱼和熊掌都想兼得,如果能够得到TagHelper的编译时检查和代码提示功能,又能得到AOT编译的运行时性能,这该多好啊。

我当时寄希望于Asp.Net Core团队,希望他们能提供一点支持,在Github提了Issue之后,Asp.Net Core团队表示TagHelper与Angular这种Js框架水火不容,机制上的问题。我后面仔细想想,确实如此,TagHelper主要为服务端MVC提供支持,而使用了Angular这样的Js框架后,路由都走客户端,通过AOT编译后,服务端除了提供WEB API,真没它啥事。

由于TagHelper提高了我团队的开发效率,延迟加载能让这个机制勉强使用,我也就坚持了下来。直到有一天灵机一动,用TagHelper生成出Html文件不就好了吗,和玄冰同学折腾了一天,终于解决了这个问题。现在,TagHelper充当了代码生成器的角色,服务端MVC相关的特性被全部抛弃。

小结

前面介绍了Util的一些背景和动机,同时也解释了为何我花大把时间在UI封装的原因。

未完待续,下一篇介绍Util包含的主要功能,以及让你把TagHelper + Angular Material封装的Demo运行起来。

Util应用框架的下载地址为:https://github.com/dotnetcore/Util

写文需要动力,请大家多多支持,点下推荐,Github点下星星。

Util应用框架交流一群: 24791014

 

版权所有,转载请注明出处 何镇汐的技术博客

如何从C#中的MethodCallExpression调用方法

mikel阅读(828)

我有一个方法调用表达式,并尝试调用该方法。我想出了一种方法,但是在检索参数值时有问题,因为不是每个参数都用ConstantExpression描述。 

 

Expression<Action<T>> = t => t.DoSomething(Par0, Par1, Par2);
MethodCallExpression methodCallExpression = selector.Body 
                                               as MethodCallExpression;

// get the information which is needed to invoke the method from the provided 
// lambda expression.
MethodInfo methodInfo = methodCallExpression.Method;
object[] arguments = methodCallExpression.Arguments.OfType<ConstantExpression>()
                            .Select(p => p.Value).ToArray();

// invoke the expression on every item within the enumerable
foreach (TSource item in source)
{ 
    methodInfo.Invoke(item, arguments);
}

另外,我已经看到了一些其他的方法来调用该方法,现在我不知道该怎么做正确的方法。

 

var func = expression.Compile();
var success = func.Invoke();

所以我的问题是,如何从methodCallExpression.Arguments检索方法参数值?

还是有更简单的方法来实现我的目标?

您不需要担心自己检索参数并调用MethodInfo,您可以让.NET为您做。所有您需要做的是创建一个包含该方法的Lambda表达式。 

例如。

 

MethodCallExpression expression = GetExpressionSomeHow();
object result = Expression.Lambda(expression).Compile().DynamicInvoke();

这就是我在Linq提供者中处理嵌套查询的方式。

编辑:实际上,你可能已经在选择器变量中有一个LambdaExpression。在这种情况下,您应该可以直接编译和调用它:

 

object result = selector.Compile().DynamicInvoke();

Util应用程序框架公共操作类(七):Lambda表达式公共操作类 - 何镇汐 - 博客园

mikel阅读(687)

GetConstantValue

来源: Util应用程序框架公共操作类(七):Lambda表达式公共操作类 – 何镇汐 – 博客园

前一篇扩展了两个常用验证方法,本文将封装两个Lambda表达式操作,用来为下一篇的查询扩展服务。

Lambda表达式是一种简洁的匿名函数语法,可以用它将方法作为委托参数传递。在Linq中,大量使用Lambda表达式进行查询,不过这种Lambda表达式被Expression包装成表达式树。表达式树是解释器的一个实现,它的作用是将一种语法转换为另一种语法,比如将Lambda表达式解析为SQL语句。

使用SQL列名进行查询的主要问题是,列名是一个字符串,没有智能提示,如果输入错误,也没有编译时检查。使用Lambda表达式查询可以解决这些问题,这是使用强类型的主要好处,另外当列名与属性名不一致时,只需修改映射配置,业务代码不动,从而增强了系统的扩展性。

Lambda表达式的强类型在带来诸多好处的同时,也产生了一些问题,比如t => t.Name==”a”这个表达式,如果想把值”a”拿出来进行操作,怎么做到?值”a”对于查询条件来讲,是一个动态传入的参数,如果对表达式树完全不了解,这也不是一件轻松的事。另外还有动态查询的问题,这时候开始怀念弱类型的字符串了,微软提供了一个动态查询的辅助类来解决这个问题,待用到的时候我再介绍。

本文介绍的一个方法是GetValue,用来将Lambda谓词表达式中的值取出来,另一个方法是GetCriteriaCount,用来判断Lambda谓词表达式中条件的个数。这两个方法的具体应用将在下一篇介绍。

在Util项目中添加一个Lambda类,代码如下。

复制代码
using System.Linq;
using System.Linq.Expressions;

namespace Util {
    /// <summary>
    /// Lambda表达式操作
    /// </summary>
    public class Lambda {

        #region GetValue(获取值)

        /// <summary>
        /// 获取值,范例:t => t.Name == "A",返回 A
        /// </summary>
        /// <param name="expression">表达式,范例:t => t.Name == "A"</param>
        public static object GetValue( LambdaExpression expression ) {
            if ( expression == null )
                return null;
            BinaryExpression binaryExpression = GetBinaryExpression( expression );
            if ( binaryExpression != null )
                return GetBinaryValue( binaryExpression );
            var callExpression = expression.Body as MethodCallExpression;
            if ( callExpression != null )
                return GetMethodValue( callExpression );
            return null;
        }

        /// <summary>
        /// 获取二元表达式
        /// </summary>
        private static BinaryExpression GetBinaryExpression( LambdaExpression expression ) {
            var binaryExpression = expression.Body as BinaryExpression;
            if ( binaryExpression != null )
                return binaryExpression;
            var unaryExpression = expression.Body as UnaryExpression;
            if ( unaryExpression == null )
                return null;
            return unaryExpression.Operand as BinaryExpression;
        }

        /// <summary>
        /// 获取二元表达式的值
        /// </summary>
        private static object GetBinaryValue( BinaryExpression binaryExpression ) {
            var unaryExpression = binaryExpression.Right as UnaryExpression;
            if ( unaryExpression != null )
                return GetConstantValue( unaryExpression.Operand );
            return GetConstantValue( binaryExpression.Right );
        }

        /// <summary>
        /// 获取常量值
        /// </summary>
        private static object GetConstantValue( Expression expression ) {
            var constantExpression = expression as ConstantExpression;
            if ( constantExpression == null )
                return null;
            return constantExpression.Value;
        }

        /// <summary>
        /// 获取方法调用表达式的值
        /// </summary>
        private static object GetMethodValue( MethodCallExpression callExpression ) {
            var argumentExpression = callExpression.Arguments.FirstOrDefault();
            return GetConstantValue( argumentExpression );
        }

        #endregion

        #region GetCriteriaCount(获取谓词条件的个数)

        /// <summary>
        /// 获取谓词条件的个数
        /// </summary>
        /// <param name="expression">谓词表达式,范例:t => t.Name == "A"</param>
        public static int GetCriteriaCount( LambdaExpression expression ) {
            if ( expression == null )
                return 0;
            var result = expression.ToString().Replace( "AndAlso", "|" ).Replace( "OrElse", "|" );
            return result.Split( '|' ).Count();
        }

        #endregion
    }
}
复制代码

为了进行测试,需要创建一个测试样例类Test,代码如下。

复制代码
namespace Util.Tests.Samples {
    /// <summary>
    /// 测试
    /// </summary>
    public class Test {
        public string Name { get; set; }
        public int Age { get; set; }
        public int? NullableInt { get; set; }
        public decimal? NullableDecimal { get; set; }
        public TestA A { get; set; }
        public class TestA {
            public int Integer { get; set; }
            public string Address { get; set; }
            public TestB B { get; set; }
            public class TestB {
                public string Name { get; set; }
            }
        }
    }
}
复制代码

单元测试代码如下。

复制代码
using System;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Util.Tests.Samples;

namespace Util.Tests {
    /// <summary>
    /// Lambda表达式操作测试
    /// </summary>
    [TestClass]
    public class LambdaTest {

        #region GetValue(获取成员值)

        /// <summary>
        /// 获取成员值,委托返回类型为Object
        /// </summary>
        [TestMethod]
        public void TestGetValue_Object() {
            Expression<Func<Test, object>> expression = test => test.Name == "A";
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );
        }

        /// <summary>
        /// 获取成员值,委托返回类型为bool
        /// </summary>
        [TestMethod]
        public void TestGetValue_Boolean() {
            //空值返回null
            Assert.AreEqual( null, Lambda.GetValue( null ) );

            //一级返回值
            Expression<Func<Test, bool>> expression = test => test.Name == "A";
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );

            //二级返回值
            Expression<Func<Test, bool>> expression2 = test => test.A.Integer == 1;
            Assert.AreEqual( 1, Lambda.GetValue( expression2 ) );

            //三级返回值
            Expression<Func<Test, bool>> expression3 = test => test.A.B.Name == "B";
            Assert.AreEqual( "B", Lambda.GetValue( expression3 ) );
        }

        /// <summary>
        /// 获取可空类型的值
        /// </summary>
        [TestMethod]
        public void TestGetValue_Nullable() {
            //可空整型
            Expression<Func<Test, bool>> expression = test => test.NullableInt == 1;
            Assert.AreEqual( 1, Lambda.GetValue( expression ) );

            //可空decimal
            expression = test => test.NullableDecimal == 1.5M;
            Assert.AreEqual( 1.5M, Lambda.GetValue( expression ) );
        }

        /// <summary>
        /// 获取成员值,运算符为方法
        /// </summary>
        [TestMethod]
        public void TestGetValue_Method() {
            //1级返回值
            Expression<Func<Test, bool>> expression = t => t.Name.Contains( "A" );
            Assert.AreEqual( "A", Lambda.GetValue( expression ) );

            //二级返回值
            expression = t => t.A.Address.Contains( "B" );
            Assert.AreEqual( "B", Lambda.GetValue( expression ) );

            //三级返回值
            expression = t => t.A.B.Name.StartsWith( "C" );
            Assert.AreEqual( "C", Lambda.GetValue( expression ) );
        }

        #endregion

        #region GetCriteriaCount(获取谓词条件的个数)

        /// <summary>
        /// 获取谓词条件的个数
        /// </summary>
        [TestMethod]
        public void TestGetCriteriaCount() {
            //0个条件
            Assert.AreEqual( 0, Lambda.GetCriteriaCount( null ) );

            //1个条件
            Expression<Func<Test, bool>> expression = test => test.Name == "A";
            Assert.AreEqual( 1, Lambda.GetCriteriaCount( expression ) );

            //2个条件,与连接符
            expression = test => test.Name == "A" && test.Name == "B";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //2个条件,或连接符
            expression = test => test.Name == "A" || test.Name == "B";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //3个条件
            expression = test => test.Name == "A" && test.Name == "B" || test.Name == "C";
            Assert.AreEqual( 3, Lambda.GetCriteriaCount( expression ) );

            //3个条件,包括导航属性
            expression = test => test.A.Address == "A" && test.Name == "B" || test.Name == "C";
            Assert.AreEqual( 3, Lambda.GetCriteriaCount( expression ) );
        }

        /// <summary>
        /// 获取谓词条件的个数,运算符为方法
        /// </summary>
        [TestMethod]
        public void TestGetCriteriaCount_Method() {
            //1个条件
            Expression<Func<Test, bool>> expression = t => t.Name.Contains( "A" );
            Assert.AreEqual( 1, Lambda.GetCriteriaCount( expression ) );

            //2个条件,与连接
            expression = t => t.Name.Contains( "A" ) && t.Name == "A";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );

            //2个条件,或连接,包含导航属性
            expression = t => t.Name.Contains( "A" ) || t.A.Address == "A";
            Assert.AreEqual( 2, Lambda.GetCriteriaCount( expression ) );
        }

        #endregion
    }
}
复制代码

需要注意的是,GetValue方法不仅要能获取t=>t.Name==”a”这样的二元表达式,还要能获取方法调用表达式中的值,比如t=>t.Name.Contains(“a”)。

下面再增加一个扩展方法,在Util项目中添加名为Extensions.Expression的文件,代码如下。

复制代码
using System;
using System.Linq.Expressions;

namespace Util {
    /// <summary>
    /// 表达式扩展
    /// </summary>
    public static partial class Extensions {

        #region Value(获取lambda表达式的值)

        /// <summary>
        /// 获取lambda表达式的值
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        public static object Value<T>( this Expression<Func<T, bool>> expression ) {
            return Lambda.GetValue( expression );
        }

        #endregion
    }
}
复制代码

 

Lambda表达式不仅在查询上大展身手,而且在表现层,比如Mvc上也有大量的应用。本文只介绍下一篇基础查询扩展需要用到的两个方法,其它方法我会在需要用到的时候补充进来。

 

.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

如果需要下载代码,请参考Util应用程序框架公共操作类(六):验证扩展

Util应用程序框架公共操作类(十二):Lambda表达式公共操作类(三) - 何镇汐 - 博客园

mikel阅读(706)

来源: Util应用程序框架公共操作类(十二):Lambda表达式公共操作类(三) – 何镇汐 – 博客园

今天在开发一个简单查询时,发现我的Lambda操作类的GetValue方法无法正确获取枚举类型值,以至查询结果错误。

我增加了几个单元测试来捕获错误,代码如下。

复制代码
     /// <summary>
        /// 测试值为枚举
        /// </summary>
        [TestMethod]
        public void TestGetValue_Enum() {
            var test1 = new Test1();
            test1.NullableEnumValue = LogType.Error;

            //属性为枚举,值为枚举
            Expression<Func<Test1, bool>> expression = test => test.EnumValue == LogType.Debug;
            Assert.AreEqual( LogType.Debug.Value(), Lambda.GetValue( expression ) );

            //属性为枚举,值为可空枚举
            expression = test => test.EnumValue == test1.NullableEnumValue;
            Assert.AreEqual( LogType.Error, Lambda.GetValue( expression ) );

            //属性为可空枚举,值为枚举
            expression = test => test.NullableEnumValue == LogType.Debug;
            Assert.AreEqual( LogType.Debug, Lambda.GetValue( expression ) );

            //属性为可空枚举,值为可空枚举
            expression = test => test.NullableEnumValue == test1.NullableEnumValue;
            Assert.AreEqual( LogType.Error, Lambda.GetValue( expression ) );

            //属性为可空枚举,值为null
            test1.NullableEnumValue = null;
            expression = test => test.NullableEnumValue == test1.NullableEnumValue;
            Assert.AreEqual( null, Lambda.GetValue( expression ) );
        }
复制代码

单元测试成功捕获了Bug,我打开Lambda操作类,准备修改GetValue方法,代码见Util应用程序框架公共操作类(八):Lambda表达式公共操作类(二)

面对GetValue杂乱无章的代码,我顿时感觉无法下手,必须彻底重构它。

我之前也看过一些Lambda表达式解析的代码和文章,基本都是使用NodeType来进行判断。我一直没有使用这种方式,是因为NodeType数量庞大,并且多种NodeType可能转换为同一种Expression类型。我当时认为用switch判断NodeType工作量太大,所以直接采用As转换为特定表达式,再判断是否空值。

我把这种山寨方法称为瞎猫碰到死耗子,主要依靠单元测试来捕获需求,通过断点调试,我可以知道转换为哪种特定表达式。这种方法在前期看上去貌似很有效,比判断NodeType的代码要少,但由于使用表达式的方式千差万别,负担越来越重,以至无法维护了。

为了彻底重构GetValue方法,我需要补充一点表达式解析的知识,我打开开源框架linq2db,仔细观察他是如何解析的。终于看出点眉目,依靠NodeType进行递归判断。

我以前只知道使用NodeType进行判断,但不知道应该采用递归的方式,真是知其然不知其所以然。

我对GetValue进行了重构,代码如下。

复制代码
     /// <summary>
        /// 获取值,范例:t => t.Name == "A",返回 A
        /// </summary>
        /// <param name="expression">表达式,范例:t => t.Name == "A"</param>
        public static object GetValue( Expression expression ) {
            if ( expression == null )
                return null;
            switch ( expression.NodeType ) {
                case ExpressionType.Lambda:
                    return GetValue( ( (LambdaExpression)expression ).Body );
                case ExpressionType.Convert:
                    return GetValue( ( (UnaryExpression)expression ).Operand );
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.LessThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThanOrEqual:
                    return GetValue( ( (BinaryExpression)expression ).Right );
                case ExpressionType.Call:
                    return GetValue( ( (MethodCallExpression)expression ).Arguments.FirstOrDefault() );
                case ExpressionType.MemberAccess:
                    return GetMemberValue( (MemberExpression)expression );
                case ExpressionType.Constant:
                    return GetConstantExpressionValue( expression );
            }
            return null;
        }

        /// <summary>
        /// 获取属性表达式的值
        /// </summary>
        private static object GetMemberValue( MemberExpression expression ) {
            if ( expression == null )
                return null;
            var field = expression.Member as FieldInfo;
            if ( field != null ) {
                var constValue = GetConstantExpressionValue( expression.Expression );
                return field.GetValue( constValue );
            }
            var property = expression.Member as PropertyInfo;
            if ( property == null )
                return null;
            var value = GetMemberValue( expression.Expression as MemberExpression );
            return property.GetValue( value );
        }

        /// <summary>
        /// 获取常量表达式的值
        /// </summary>
        private static object GetConstantExpressionValue( Expression expression ) {
            var constantExpression = (ConstantExpression)expression;
            return constantExpression.Value;
        }
复制代码

运行了全部测试,全部通过,说明没有影响之前的功能。这正是自动化回归测试的威力,如果没有单元测试,我哪里敢重构这些代码呢。另外,修改Bug采用TDD的方式,能够一次修复,永绝后患,值得你拥有。

同时,我还重构了其它类似的代码,就不再贴出,下次我发放源码时,有兴趣可以看看。

.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

由浅入深表达式树(二)遍历表达式树 - 腾飞(Jesse) - 博客园

mikel阅读(785)

来源: 由浅入深表达式树(二)遍历表达式树 – 腾飞(Jesse) – 博客园

  为什么要学习表达式树?表达式树是将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。LINQ to SQL就是通过把表达式树翻译成SQL来实现的,所以了解表达树有助于我们更好的理解 LINQ to SQL,同时如果你有兴趣,可以用它创造出很多有意思的东西来。

表达式树是随着.NET 3.5推出的,所以现在也不算什么新技术了。但是不知道多少人是对它理解的很透彻, 在上一篇Lambda表达式的回复中就看的出大家对Lambda表达式和表达式树还是比较感兴趣的,那我们就来好好的看一看这个造就了LINQ to SQL以及让LINQ to Everything的好东西吧。

本系列计划三篇,第一篇主要介绍表达式树的创建方式。第二篇主要介绍表达式树的遍历问题。第三篇,将利用表达式树打造一个自己的LinqProvider。

本文主要内容:

上一篇由浅入深表达式树(一)我们主要讨论了如何根据Lambda表达式以及通过代码的方式直接创建表达式树。表达式树主要是由不同类型的表达式构成的,而在上文中我们也列出了比较常用的几种表达式类型,由于它本身结构的特点所以用代码写起来然免有一点繁琐,当然我们也不一定要从头到尾完全自己去写,只有我们理解它了,我们才能更好的去使用它。

在上一篇中,我们用代码的方式创建了一个没有返回值,用到了循环以及条件判断的表达式,为了加深大家对表达式树的理解,我们先回顾一下,看一个有返回值的例子。

有返回值的表达式树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 直接返回常量值
ConstantExpression ce1 = Expression.Constant(10);
            
// 直接用我们上面创建的常量表达式来创建表达式树
Expression<Func<int>> expr1 = Expression.Lambda<Func<int>>(ce1);
Console.WriteLine(expr1.Compile().Invoke());
// 10
// --------------在方法体内创建变量,经过操作之后再返回------------------
// 1.创建方法体表达式 2.在方法体内声明变量并附值 3. 返回该变量
ParameterExpression param2 = Expression.Parameter(typeof(int));
BlockExpression block2 = Expression.Block(
    new[]{param2},
    Expression.AddAssign(param2,Expression.Constant(20)),
    param2
    );
Expression<Func<int>> expr2 = Expression.Lambda<Func<int>>(block2);
Console.WriteLine(expr2.Compile().Invoke());
// 20
// -------------利用GotoExpression返回值-----------------------------------
LabelTarget returnTarget = Expression.Label(typeof(Int32));
LabelExpression returnLabel = Expression.Label(returnTarget,Expression.Constant(10,typeof(Int32)));
// 为输入参加+10之后返回
ParameterExpression inParam3=Expression.Parameter(typeof(int));
BlockExpression block3 = Expression.Block(
    Expression.AddAssign(inParam3,Expression.Constant(10)),
    Expression.Return(returnTarget,inParam3),
    returnLabel);
Expression<Func<int,int>> expr3 = Expression.Lambda<Func<int,int>>(block3,inParam3);
Console.WriteLine(expr3.Compile().Invoke(20));
// 30

我们上面列出了3个例子,都可以实现在表达式树中返回值,第一种和第二种其实是一样的,那就是将我们要返回的值所在的表达式写在block的最后一个参数。而第三种我们是利用了goto 语句,如果我们在表达式中想跳出循环,或者提前退出方法它就派上用场了。这们上一篇中也有讲到Expression.Return的用法。当然,我们还可以通过switch case 来返回值,请看下面的switch case的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//简单的switch case 语句
ParameterExpression genderParam = Expression.Parameter(typeof(int));
SwitchExpression swithExpression = Expression.Switch(
    genderParam,
    Expression.Constant("不详"), //默认值
    Expression.SwitchCase(Expression.Constant("男"),Expression.Constant(1)), 
Expression.SwitchCase(Expression.Constant("女"),Expression.Constant(0))
//你可以将上面的Expression.Constant替换成其它复杂的表达式,ParameterExpression, BinaryExpression等, 这也是表达式灵活的地方, 因为归根结底它们都是继承自Expression, 而基本上我们用到的地方都是以基类作为参数类型接受的,所以我们可以传递任意类型的表达式。
    );
Expression<Func<intstring>> expr4 = Expression.Lambda<Func<intstring>>(swithExpression, genderParam);
Console.WriteLine(expr4.Compile().Invoke(1)); //男
Console.WriteLine(expr4.Compile().Invoke(0)); //女
Console.WriteLine(expr4.Compile().Invoke(11)); //不详

有人说表达式繁琐,这我承认,可有人说表达式不好理解,恐怕我就没有办法认同了。的确,表达式的类型有很多,光我们上一篇列出来的就有23种,但使用起来并不复杂,我们只需要大概知道一些表达类型所代表的意义就行了。实际上Expression类为我们提供了一系列的工厂方法来帮助我们创建表达式,就像我们上面用到的Constant, Parameter, SwitchCase等等。当然,自己动手胜过他人讲解百倍,我相信只要你手动的去敲一些例子,你会发现创建表达式树其实并不复杂。

表达式的遍历

说完了表达式树的创建,我们来看看如何访问表达式树。MSDN官方能找到的关于遍历表达式树的文章真的不多,有一篇比较全的(链接),真的没有办法看下去。请问盖茨叔叔就是这样教你们写文档的么?

但是ExpressionVisitor是唯一一种我们可以拿来就用的帮助类,所以我们硬着头皮也得把它啃下去。我们可以看一下ExpressionVisitor类的主要入口方法是Visit方法,其中主要是一个针对ExpressionNodeType的switch case,这个包含了85种操作类型的枚举类,但是不用担心,在这里我们只处理44种操作类型,14种具体的表达式类型,也就是说只有14个方法我们需要区别一下。我将上面链接中的代码转换成下面的表格方便大家查阅。

认识了ExpressionVisitor之后,下面我们就来一步一步的看看到底是如果通过它来访问我们的表达式树的。接下来我们要自己写一个类继承自这个ExpressionVisitor类,然后覆盖其中的某一些方法从而达到我们自己的目地。我们要实现什么样的功能呢?

1
2
3
4
5
6
7
8
9
List<User> myUsers = new List<User>();
var userSql = myUsers.AsQueryable().Where(u => u.Age > 2);
Console.WriteLine(userSql);
// SELECT * FROM (SELECT * FROM User) AS T WHERE (Age>2)
List<User> myUsers2 = new List<User>();
var userSql2 = myUsers.AsQueryable().Where(u => u.Name=="Jesse");
Console.WriteLine(userSql2);
// SELECT * FROM (SELECT * FROM USER) AS T WHERE (Name='Jesse')

我们改造了IQueryable的Where方法,让它根据我们输入的查询条件来构造SQL语句。

要实现这个功能,首先我们得知道IQueryable的Where 方法在哪里,它是如何实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }
        if (predicate == null)
        {
            throw new ArgumentNullException("predicate");
        }
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod())
            .MakeGenericMethod(new Type[] { typeof(TSource) }),
            new Expression[] { source.Expression, Expression.Quote(predicate) }));
    }
}

通过F12我们可以跟到System.Linq下有一个Querable的静态类,而我们的Where方法就是是扩展方法的形势存在于这个类中(包括其的GroupBy,Join,Last等有兴趣的同学可以自行Reflect J)。大家可以看到上面的代码中,实际上是调用了Queryable的Provider的CreateQuery方法。这个Provider就是传说中的Linq Provider,但是我们今天不打算细说它,我们的重点在于传给这个方法的参数被转成了一个表达式树。实际上Provider也就是接收了这个表达式树,然后进行遍历解释的,那么我们可以不要Provider直接进行翻译吗? I SAY YES! WHY CAN’T?

1
2
3
4
5
6
7
8
9
10
11
12
13
public static class QueryExtensions
{
    public static string Where<TSource>(this IQueryable<TSource> source,
        Expression<Func<TSource, bool>> predicate)
    {
        var expression = Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod())
        .MakeGenericMethod(new Type[] { typeof(TSource) }),
        new Expression[] { source.Expression, Expression.Quote(predicate) });
        var translator = new QueryTranslator();
        return translator.Translate(expression);
    }
}

上面我们自己实现了一个Where的扩展方法,将该Where方法转换成表达式树,只不过我们没有调用Provider的方法,而是直接让另一个类去将它翻译成SQL语句,然后直接返回该SQL语句。接下来的问题是,这个类如何去翻译这个表达式树呢?我们的ExpressionVisitor要全场了!

1
2
3
4
5
6
7
8
9
class QueryTranslator : ExpressionVisitor
{
    internal string Translate(Expression expression)
    {
        this.sb = new StringBuilder();
        this.Visit(expression);
        return this.sb.ToString();
    }
}

首先我们有一个类继承自ExpressionVisitor,里面有一个我们自己的Translate方法,然后我们直接调用Visit方法即可。上面我们提到了Visit方法实际上是一个入口,会根据表达式的类型调用其它的Visit方法,我们要做的就是找到对应的方法重写就可以了。但是下面有一堆Visit方法,我们要要覆盖哪哪些呢? 这就要看我们的表达式类型了,在我们的Where扩展方法中,我们传入的表达式树是由Expression.Call方法构造的,而它返回的是MethodCallExpression所以我们第一步是覆盖VisitMethodCall。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected override Expression VisitMethodCall(MethodCallExpression m)
{
    if (m.Method.DeclaringType == typeof(QueryExtensions) && m.Method.Name == "Where")
    {
        sb.Append("SELECT * FROM (");
        this.Visit(m.Arguments[0]);
        sb.Append(") AS T WHERE ");
        LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
        this.Visit(lambda.Body);
        return m;
    }
    throw new NotSupportedException(string.Format("方法{0}不支持", m.Method.Name));
}

代码很简单,方法名是Where那我们就直接开始拼SQL语句。重点是在这个方法里面两次调用了Visit方法,我们要知道它们会分别调用哪两个具体的Visit方法,我们要做的就是重写它们。

第一个我们就不说了,大家可以下载源代码自己去调试一下,我们来看看第二个Visit方法。很明显,我们构造了一个Lambda表达式树,但是注意,我们没有直接Visit这Lambda表达式树,它是Visit了它的Body。它的Body是什么?如果我的条件是Age>7,这就是一个二元运算,不是么?所以我们要重写VisitBinary方法,Let’s get started。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected override Expression VisitBinary(BinaryExpression b)
{
    sb.Append("(");
    this.Visit(b.Left);
    switch (b.NodeType)
    {
        case ExpressionType.And:
            sb.Append(" AND ");
            break;
        case ExpressionType.Or:
            sb.Append(" OR");
            break;
        case ExpressionType.Equal:
            sb.Append(" = ");
            break;
        case ExpressionType.NotEqual:
            sb.Append(" <> ");
            break;
        case ExpressionType.LessThan:
            sb.Append(" < ");
            break;
        case ExpressionType.LessThanOrEqual:
            sb.Append(" <= ");
            break;
        case ExpressionType.GreaterThan:
            sb.Append(" > ");
            break;
        case ExpressionType.GreaterThanOrEqual:
            sb.Append(" >= ");
            break;
        default:
            throw new NotSupportedException(string.Format(“二元运算符{0}不支持”, b.NodeType));
    }
    this.Visit(b.Right);
    sb.Append(")");
    return b;
}

我们根据这个表达式的操作类型转换成对应的SQL运算符,我们要做的就是把左边的属性名和右边的值加到我们的SQL语句中。所以我们要重写VisitMember和VisitConstant方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
protected override Expression VisitConstant(ConstantExpression c)
{
    IQueryable q = c.Value as IQueryable;
    if (q != null)
    {
        // 我们假设我们那个Queryable就是对应的表
        sb.Append("SELECT * FROM ");
        sb.Append(q.ElementType.Name);
    }
    else if (c.Value == null)
    {
        sb.Append("NULL");
    }
    else
    {
        switch (Type.GetTypeCode(c.Value.GetType()))
        {
            case TypeCode.Boolean:
                sb.Append(((bool)c.Value) ? 1 : 0);
                break;
            case TypeCode.String:
                sb.Append("'");
                sb.Append(c.Value);
                sb.Append("'");
                break;
            case TypeCode.Object:
                throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
            default:
                sb.Append(c.Value);
                break;
        }
    }
    return c;
}
protected override Expression VisitMember(MemberExpression m)
{
    if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
    {
        sb.Append(m.Member.Name);
        return m;
    }
    throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
}

到这里,我们的来龙去脉基本上就清楚了。来回顾一下我们干了哪些事情。

  1. 重写IQuerable的Where方法,构造MethodCallExpression传给我们的表达式访问类。
  2. 在我们的表达式访问类中重写相应的具体访问方法。
  3. 在具体访问方法中,解释表达式,翻译成SQL语句。

实际上我们并没有干什么很复杂的事情,只要了解具体的表达式类型和具体表访问方法就可以了。看到很多园友说表达式树难以理解,我也希望能够尽我的努力去把它清楚的表达出来,让大家一起学习,如果大家觉得哪里不清楚,或者说我表述的方式不好理解,也欢迎大家踊跃的提出来,后面我们可以继续完善这个翻译SQL的功能,我们上面的代码中只支持Where语句,并且只支持一个条件。我的目地的希望通过这个例子让大家更好的理解表达式树的遍历问题,这样我们就可以实现我们自己的LinqProvider了,请大家关注,我们来整个Linq To 什么呢?有好点子么? 之间想整个Linq to 博客园,但是好像博客园没有公开Service。

点这里面下载文中源代码。

参考引用:

http://msdn.microsoft.com/en-us/library/bb397951(v=vs.120).aspx
http://msdn.microsoft.com/en-us/library/system.linq.expressions.aspx
http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx
http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx

 

作者:Jesse 出处: http://jesse2013.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

由浅入深表达式树(一)创建表达式树 - 腾飞(Jesse) - 博客园

mikel阅读(1339)

来源: 由浅入深表达式树(一)创建表达式树 – 腾飞(Jesse) – 博客园

  为什么要学习表达式树?表达式树是将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。LINQ to SQL就是通过把表达式树翻译成SQL来实现的,所以了解表达树有助于我们更好的理解 LINQ to SQL,同时如果你有兴趣,可以用它创造出很多有意思的东西来。

表达式树是随着.NET 3.5推出的,所以现在也不算什么新技术了。但是不知道多少人是对它理解的很透彻, 在上一篇Lambda表达式的回复中就看的出大家对Lambda表达式和表达式树还是比较感兴趣的,那我们就来好好的看一看这个造就了LINQ to SQL以及让LINQ to Everything的好东西吧。

本系列计划三篇,第一篇主要介绍表达式树的创建方式。第二篇主要介绍表达式树的遍历问题。第三篇,将利用表达式树打造一个自己的LinqProvider。

本文主要内容:

创建一个简单的Lambda表达式树

在 上一篇Lambda表达式中我们提到了可以直接根据Lambda表达式来创建表达式树,这应该是最直接的创建表达式树的方式了。

1
2
3
4
5
6
Expression<Func<intint>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)
// 下面的代码编译不通过
Expression<Func<intintint>> expr2 = (x, y) => { return x + y; };
Expression<Action<int>> expr3 = x => {  };

但是别想象的太美好,这种方式只能创建最简单的表达式树,复杂点的编译器就不认识了。

右边是一个Lambda表达式,而左边是一个表达式树。为什么可以直接赋值呢?这个就要多亏我们的Expression<TDelegate>泛型类了。而Expression<TDelegate>是直接继承自LambdaExpression的,我们来看一下Expression的构造函数:

1
2
3
4
internal Expression(Expression body, string name, bool tailCall, ReadOnlyCollection<ParameterExpression> parameters)
    base(typeof(TDelegate), name, body, tailCall, parameters)
{
}

实际上这个构造函数什么也没有做,只是把相关的参数传给了父类,也就是LambdaExpression,由它把我们表达式的主体,名称,以及参数保存着。

1
2
3
4
5
6
7
8
9
10
11
12
13
Expression<Func<intint>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)
var lambdaExpr = expr as LambdaExpression;
Console.WriteLine(lambdaExpr.Body);   // (x + 1)
Console.WriteLine(lambdaExpr.ReturnType.ToString());  // System.Int32
foreach (var parameter in lambdaExpr.Parameters)
{
    Console.WriteLine("Name:{0}, Type:{1}, ",parameter.Name,parameter.Type.ToString());
}
//Name:x, Type:System.Int32

简单的来说,Expression<TDelegate>泛型类做了一层封装,方便我们根据Lambda表达式来创建Lambda表达式树。它们中间有一个转换过程,而这个转换的过程就发生在我们编译的时候。还记得我们Lambda表达式中讲的么?Lambda表达式在编译之后是普通的方法,而Lambda式树是以一种树的结构被加载到我们的运行时的,只有这样我们才可以在运行时去遍历这个树。但是为什么我们不能根据Expression<TDelegate>来创建比较复杂的表达式树呢?您请接着往下看。

创建一个复杂的Lambda表达式树

下面我们就来一步一步的创建一个复杂的表达式树,你们准备好了么?上面我们讲到直接由Lambda表达式的方式来创建表达式树,可惜只限于一种类型。下面我们就来演示一下如何创建一个无参无返回值的表达式树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 下面的方法编译不能过
/*
Expression<Action> lambdaExpression2 = () =>
{
    for (int i = 1; i <= 10; i++)
    {
        Console.WriteLine("Hello");
    }
};
*/
       
// 创建 loop表达式体来包含我们想要执行的代码
LoopExpression loop = Expression.Loop(
    Expression.Call(
        null,
        typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) }),
        Expression.Constant("Hello"))
        );
// 创建一个代码块表达式包含我们上面创建的loop表达式
BlockExpression block = Expression.Block(loop);
// 将我们上面的代码块表达式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

上面我们通过手动编码的方式创建了一个无参的Action,执行了一组循环。代码很简单,重要的是我们要熟悉这些各种类型的表达式以及他们的使用方式。上面我们引入了以下类型的表达式:

看起来神密的表达式树也不过如此嘛?如果大家去执行上面的代码,就会陷入死循环,我没有为loop加入break的条件。为了方便大家理解,我是真的一步一步来啊,现在我们就来终止这个循环。就像上面那一段不能编译通过的代码实现的功能一样,我们要输出10个”Hello”。

上面我们先写了一个LoopExpression,然后把它传给了BlockExpresson,从而形成的的一块代码或者我们也可以说一个方法体。但是如果我们有多个执行块,而且这多个执行块里面需要处理同一个参数,我们就得在block里面声明这些参数了。

1
2
3
4
5
6
7
8
9
10
11
ParameterExpression number=Expression.Parameter(typeof(int),"number");
            
BlockExpression myBlock = Expression.Block(
    new[] { number },
    Expression.Assign(number, Expression.Constant(2)),
    Expression.AddAssign(number, Expression.Constant(6)),
    Expression.DivideAssign(number, Expression.Constant(2)));
Expression<Func<int>> myAction = Expression.Lambda<Func<int>>(myBlock);
Console.WriteLine(myAction.Compile()());
// 4

我们声明了一个int的变量并赋值为2,然后加上6最后除以2。如果我们要用变量,就必须在block的你外面声明它,并且在block里面把它引入进来。否则在该表达式树时会出现,变量不在作用域里的错。

下面我们继续我们未完成的工作,为循环加入退出条件。为了让大家快速的理解loop的退出机制,我们先来看一段伪代码:

1
2
3
4
5
6
7
LabelTarget labelBreak = Expression.Label();
Expression.Loop(
    "如果 条件 成功"
        "执行成功的代码"
    "否则"
        Expression.Break(labelBreak) //跳出循环
    , labelBreak); 

我们需要借助于LabelTarget 以及Expression.Break来达到退出循环的目地。下面我们来看一下真实的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
LabelTarget labelBreak = Expression.Label();
ParameterExpression loopIndex = Expression.Parameter(typeof(int), "index");
BlockExpression block = Expression.Block(
new[] { loopIndex },
// 初始化loopIndex =1
    Expression.Assign(loopIndex, Expression.Constant(1)),
    Expression.Loop(
        Expression.IfThenElse(
            // if 的判断逻辑
            Expression.LessThanOrEqual(loopIndex, Expression.Constant(10)),
            // 判断逻辑通过的代码
            Expression.Block(
                Expression.Call(
                    null,
                    typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) }),
                    Expression.Constant("Hello")),
                Expression.PostIncrementAssign(loopIndex)),
            // 判断不通过的代码
            Expression.Break(labelBreak)
            ),labelBreak));
// 将我们上面的代码块表达式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

希望上面的代码没有阻止你学习表达式树的决心J 。

好吧,我们又学了几个新的类型的表达式,来总结一下:

到这里,我想大家应该对表达式树的构建有了一个清楚的认识。至于为什么不允许我们直接基于复杂的Lambda表达式来创建表达式树呢?

  • 这里的Lambda表达式实际上是一个Expression Body。
  • 这个Expression Body实际上就是我们上面讲到的Expression中的一种。
  • 也就是说编译器需要时间去分析你到底是哪一种?
  • 最简单的x=> x+1之类的也就是Func<TValue,TKey> 是很容易分析的。
  • 实际这里面允许的Expression Body只有BinaryExpression。

最后,我们来完整的看一下.NET都为我们提供了哪些类型的表达式(下面这些类都是继承自Expression)。

TypeBinaryExpression

1
2
3
4
5
6
7
TypeBinaryExpression typeBinaryExpression =
    Expression.TypeIs(
        Expression.Constant("spruce"),
        typeof(int));
Console.WriteLine(typeBinaryExpression.ToString());
// ("spruce" Is Int32)

IndexExpression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ParameterExpression arrayExpr = Expression.Parameter(typeof(int[]), "Array");
ParameterExpression indexExpr = Expression.Parameter(typeof(int), "Index");
ParameterExpression valueExpr = Expression.Parameter(typeof(int), "Value");
Expression arrayAccessExpr = Expression.ArrayAccess(
    arrayExpr,
    indexExpr
);
Expression<Func<int[], intintint>> lambdaExpr = Expression.Lambda<Func<int[], intintint>>(
        Expression.Assign(arrayAccessExpr, Expression.Add(arrayAccessExpr, valueExpr)),
        arrayExpr,
        indexExpr,
        valueExpr
    );
Console.WriteLine(arrayAccessExpr.ToString());
// Array[Index]
Console.WriteLine(lambdaExpr.ToString());
// (Array, Index, Value) => (Array[Index] = (Array[Index] + Value))
Console.WriteLine(lambdaExpr.Compile().Invoke(new int[] { 10, 20, 30 }, 0, 5));
// 15

NewExpression

1
2
3
NewExpression newDictionaryExpression =Expression.New(typeof(Dictionary<intstring>));
Console.WriteLine(newDictionaryExpression.ToString());
// new Dictionary`2()

InvocationExpression

1
2
3
4
5
6
7
8
9
10
Expression<Func<intintbool>> largeSumTest =
    (num1, num2) => (num1 + num2) > 1000;
InvocationExpression invocationExpression= Expression.Invoke(
    largeSumTest,
    Expression.Constant(539),
    Expression.Constant(281));
Console.WriteLine(invocationExpression.ToString());
// Invoke((num1, num2) => ((num1 + num2) > 1000),539,281)

今天我们演示了如何通过代码的方式去创建表达式树,然后总结了一下.NET为我们提供的表达式类型。下一篇,我们将继续研究表达式树的遍历问题,敬请期待,如果对于表达式树有兴趣的同学欢迎持续关注~,

作者:Jesse 出处: http://jesse2013.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

由浅入深表达式树(完结篇)重磅打造 Linq To 博客园 - 腾飞(Jesse) - 博客园

mikel阅读(634)

来源: 由浅入深表达式树(完结篇)重磅打造 Linq To 博客园 – 腾飞(Jesse) – 博客园

  一个多月之后,由浅入深表达式系列的最后一篇终于要问世了。想对所有关注的朋友说声:“对不起,我来晚了!” 希望最后一篇的内容对得起这一个月时间的等待。在学习完表达式树的创建和遍历之后,我们要利用它的特性来写一个我们自己的Linq Provider。人家都有Linq to Amazon为什么我们不能有Linq to cnblogs呢?今天我们就来一步一步的打造自己的Linq Provider,文章未尾已附上源码下载地址。如果对于表达式树的创建和遍历还是熟悉的话,建议先看前面两篇:

创建表达式树
http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html

遍历表达式树
http://www.cnblogs.com/jesse2013/p/expressiontree-part2.html

更新:之前没有描述清楚本篇博客的意图,导致很多朋友的误解表示抱歉。本系列重在理解表达式目录树,以及Linq Provider。最后一篇是Linq Provider的实现,之所有会写这么多的代码去做一件简单的事(拉取博客园首页文章列表)完全是为了有一个生动的例子去展示如何实现自己的Linq Provider。和我们项目中的三层架构,或者直接序列化到本地是没有可比性的。

  当然,表达式目录树以及Linq Provider的强大也远非这个小小的Demo能体现得了的,如果你真正知道Linq Provider和表达式树目录树是什么,用来干什么的,也许你就能明白本篇博客的意图了。如果不了解的,建议读完前面两篇之后再做评论。因为你在自己不理解的情况下就直接去评论其它的领域,你就失去了一个了解它的机会。:)

目录

实现目标

我们实现的目标就像Linq to SQL一样,可以用Linq查询语句来查询数据,我们这里面的数据用到了博客园官方的Service去查询到最新的发布到首页的博客信息。看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
var provider = new CnblogsQueryProvider();
var queryable = new Query<Post>(provider);
var query =
    from in queryable
    where p.Diggs >= 10 &&
    p.Comments > 10 &&
    p.Views > 10 &&
    p.Comments < 20
    select p;
var list = query.ToList();

作为实时访问远程的Service,我们还应该具体以下几点要求:

  • 延迟加载,即到最后使用的时候才真正的去请求数据
  • 只返回需要的数据,不能把所有的数据全下载过来再到本地过滤,那就没有意义了

最后实现的结果:

数据准备

根据博客园公开的API显示,获取首页文章列表非常容易,大家可以点下面的URL来体检一把。我们最后给的参数是100000,当然真实返回肯定是没有那么多的,我们是希望把能够取回来的都取回来。

http://wcf.open.cnblogs.com/blog/sitehome/recent/100000

点击完上面的URL之后呢,问题就来了,它只有一个参数。我并不能传给它查询条件,比如说根据标题来搜索,或者根据评论数,浏览量来过滤。难道我的计划就此要泡汤了么,刚开始我很不开心,为什么博客园就不能提供灵活一点的Service呢?但是事实就是这样,咋是程序员呀,需求摆在这,怎么着还得实现是不?没有办法,我给它封装了一层。在它的基础上做了一个自己的Service。

封装博客园Service

我们如何在博客园公开Service的基础上加一层实现条件查询呢?主要思路是这样的:

  • 为文章建立实体类(Post)
  • 将博客园Service返回的数据解析成Post的集合,我们可以加上自己的缓存机制,可以采用1分钟才到博客园取一次数据
  • 把我们上面创建的post集合当作数据库,建立查询Service

我们首先要做的就是为博客园的博客建立实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Post
{
    // Id
    public int Id { getset; }
    // 标题
    public string Title { getset; }
    // 发布时间
    public DateTime Published { getset; }
    // 推荐数据
    public int Diggs { getset; }
    // 访问人数
    public int Views { getset; }
    // 评论数据
    public int Comments { getset; }
    // 作者
    public string Author { getset; }
    // 博客链接
    public string Href { getset; }
    // 摘要
    public string Summary { getset; }
}

接着,我们要将博客园返回给我们的XML数据转换成我们要的post集合,所以我们要用到Linq to XML。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
_list = new List<CnblogsLinqProvider.Post>();
var document = XDocument.Load(
    "http://wcf.open.cnblogs.com/blog/sitehome/recent/100000"
    );
var elements = document.Root.Elements();
var result = from entry in elements
                where entry.HasElements == true
                select new CnblogsLinqProvider.Post
                {
                    Id = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "id").Value),
                    Title = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "title").Value,
                    Published = Convert.ToDateTime(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "published").Value),
                    Diggs = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "diggs").Value),
                    Views = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "views").Value),
                    Comments = Convert.ToInt32(entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "comments").Value),
                    Summary = entry.Elements()
                        .SingleOrDefault(x=>x.Name.LocalName=="summary").Value,
                    Href = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "link")
                        .Attribute("href").Value,
                    Author = entry.Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "author")
                        .Elements()
                        .SingleOrDefault(x => x.Name.LocalName == "name").Value
                };
_list.AddRange(result);

然后就到了我们的查询了,实际上我们有了IEnumrable<Post>的数据就可以直接在本地用Linq去查询它了。但是这不是我们想要的,因为我们上面的步骤是把所有的数据一次性全部下载下来了,而不是根据我们的需求返回数据。另外我们这里面是在博客园Service的基础上做一层封装,实现通过Url直接查询首页的文章。为什么要通过Url来查询?因为我们最后会通过我们自己的LinqProvider将Linq查询语句直接翻译成Url这样就能够实现远程的返回数据了。来看看我们对Url参数的定义:

利用JsonResult 返回json数据来创建我们的Service

作为Service,我们返回Json或者XML格式的数据都是可以的。当然实现这个需求的方法有很多种,我们这里面有选了一种最简单方便又比较适合我们需求方式。不需要WCF Service也不需要Web API,直接用MVC里面的Action返回JsonResult就可以了。

利用Action来做这种Service还有一个好处就是我们不需要一个一个的声明查询参数,只需要把所有的参数放到一个model中就可以了。剩下的事就交给Model Binder吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SearchCriteria
   {
       public string Title { getset; }
       public string Author { getset; }
       
       public DateTime? Start { getset; }
       public DateTime? End { getset; }
       public int MinDiggs { getset; }
       public int MaxDiggs { getset; }
       public int MinViews { getset; }
       public int MaxViews { getset; }
       public int MinComments { getset; }
       public int MaxComments { getset; }
   }

如果大家想更熟悉这个Service的功能,可以参考上面的参数自己去体验一下(用IE会直接下载.json的文件,用Chrom是可以直接在浏览器里面看数据的)。但是我没有做任何安全性的措施,希望大侠高抬贵手,别把网站整挂了就行。

认识IQueryable和IQueryProvider接口

有了上面的Service之后,我们要做的事情就简单多了,但是在我们真正开始动手写自己的Linq Provider之前,先来看看IQueryable和IQueryProvider这两个重要的接口。

IQueryable

IQueryable本身并没有包含多少的东西,它只有三个属性:

  • ElementType 代表当然这个Query所对应的类型
  • Expression 包含了我们当然Query所执行的所有查询或者是其它的操作
  • IQueryProvider则是负责处理上面的Expression的实现

更为重要的是,在IQueryable这个接口之上,.net为我们提供了很多的扩展方法:

我们平常用到的Where,Select,Max,Any都包括在其中,具体的方法大家可以到System.Linq.Queryable这个静态类下去看。大家注意一下,传给Where方法的正是我们现在学习的Expression。

在另外一个很重要的接口IEnumrable下,也有着同样的扩展方法:

这些扩展方法来自System.Linq.Enumrable这个静态类下。我们可以看到两组扩展方法的不同之处在于IQueryable下传入的Expression类型,而IEnumrable下传入的是委托。这样做的用意是什么呢?您请接着往下看。

 

IQueryProvider

我们上面讲到了Enumrable和Queryable这两个静态类下的扩展方法,对于Enumrable下的扩展方法来说他们传入的是委托,对于委托而言直接执行就可以了。

1
2
3
4
5
6
7
8
9
10
11
public static IEnumerable<T> Where<T>(this IEnumerable<T> list, Func<T, bool> predicate)
{
    var result = new List<T>();
    foreach (var element in list)
    {
        // 调用委托是验证这个元素是否符合条件
        if(predicate(element))
            result.Add(element);
    }
    return result;
}

上面的代码给大家作一个参考,相信不难理解,所以Enumrable下的静态方法都是操作本地数据的。而对于Queryable下的静态方法而言,他们接收的是表达式,还记得表达式的最大特征吗?可以在运行时去遍历解释然后执行,那么这样就可以将表达式转换成各种其它的方式去获取数据,伟大的Linq to SQL就是这么实现的。而这背后的大功臣就是我们的Linq Provider了,而IQueryProvider就是LinqProvider的接口。

IQueryProvider只有两个操作,CreateQuery和Execute分别有泛型版本和非泛型版本。 CreatQuery用于构造一个IQueryable<T>的对象,这个类其实没有任何实现,只是继承了IQueryable和IEnumrable接口。主要用于计算指定表达式目录树所表示的查询,返回的结果是一个可枚举的类型。 而Execute会执行指定表达式目录树所表示的查询,返回指定的结果。所有的内幕就在这个Execute方法里面,拿我们要进行的Linq to cnblogs方法来举例,我们将把传入的表达式目录树翻译成一个URL就是指向我们封装好的Service的URL,通过发起web request到这个URL,拿到response进行解析,最终得到我们所要的数据,这就是我们Linq to cnblogs的思路。

Linq to cnblogs的实现

有了前面的数据准备和一些实现的大致思路以后,我们就可以着手开始实现我们的CnblogsQueryProvider了。我们的思路大致是这样的:

  1. 实现自己的ExpressionVisitor类去访问表达式目录数,将其翻译成可以访问Service的Url
  2. 调用WebRequest去访问这个Url
  3. 将上面返回的Response解析成我们要的对象

实现PostExpressionVisitor

关于表达式树的访问,我们在第二篇中已经有了比较详细的介绍。如果对于表达式树的遍历不清楚的,可以去第二篇《遍历表达式》中查阅。在这里,我们创建一个我们自己的ExpressionVisitor类,去遍历表达式树。我们暂时只需要生成一个SearchCriteria(我们上面已经定义好了,对于查询条件建的模)对象即可。

复制代码
  1 public class PostExpressionVisitor
  2 {
  3 private SearchCriteria _criteria;
  4 
  5 // 入口方法
  6 public SearchCriteria ProcessExpression(Expression expression)
  7 {
  8     _criteria = new SearchCriteria();
  9     VisitExpression(expression);
 10     return _criteria;
 11 }
 12 
 13 private void VisitExpression(Expression expression)
 14 {
 15     switch (expression.NodeType)
 16     { 
 17         // 访问 &&
 18         case ExpressionType.AndAlso:
 19             VisitAndAlso((BinaryExpression)expression);
 20             break;
 21         // 访问 等于
 22         case ExpressionType.Equal:
 23             VisitEqual((BinaryExpression)expression);
 24             break;
 25         // 访问 小于和小于等于
 26         case ExpressionType.LessThan:
 27         case ExpressionType.LessThanOrEqual:
 28             VisitLessThanOrEqual((BinaryExpression)expression);
 29             break;
 30         // 访问大于和大于等于
 31         case ExpressionType.GreaterThan:
 32         case ExpressionType.GreaterThanOrEqual:
 33             GreaterThanOrEqual((BinaryExpression)expression);
 34             break;
 35         // 访问调用方法,主要有于解析Contains方法,我们的Title会用到
 36         case ExpressionType.Call:
 37             VisitMethodCall((MethodCallExpression)expression);
 38             break;
 39         // 访问Lambda表达式
 40         case ExpressionType.Lambda:
 41             VisitExpression(((LambdaExpression)expression).Body);
 42             break;
 43     }
 44 }
 45 
 46 // 访问  &&
 47 private void VisitAndAlso(BinaryExpression andAlso)
 48 {
 49     VisitExpression(andAlso.Left);
 50     VisitExpression(andAlso.Right);
 51 }
 52 
 53 // 访问 等于
 54 private void VisitEqual(BinaryExpression expression)
 55 {
 56     // 我们这里面只处理在Author上的等于操作
 57     // Views, Comments, 和 Diggs 我们都是用的大于等于,或者小于等于
 58     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 59         (((MemberExpression)expression.Left).Member.Name == "Author"))
 60     {
 61         if (expression.Right.NodeType == ExpressionType.Constant)
 62             _criteria.Author = 
 63                 (String)((ConstantExpression)expression.Right).Value;
 64 
 65         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
 66             _criteria.Author = 
 67                 (String)GetMemberValue((MemberExpression)expression.Right);
 68 
 69         else
 70             throw new NotSupportedException("Expression type not supported for author: " + 
 71                 expression.Right.NodeType.ToString());
 72     }
 73 }
 74 
 75 // 访问大于等于
 76 private void GreaterThanOrEqual(BinaryExpression expression)
 77 {
 78     // 处理 Diggs >= n  推荐人数
 79     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 80         (((MemberExpression)expression.Left).Member.Name == "Diggs"))
 81     {
 82         if (expression.Right.NodeType == ExpressionType.Constant)
 83             _criteria.MinDiggs = 
 84                 (int)((ConstantExpression)expression.Right).Value;
 85 
 86         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
 87             _criteria.MinDiggs =
 88                 (int)GetMemberValue((MemberExpression)expression.Right);
 89 
 90         else
 91             throw new NotSupportedException("Expression type not supported for Diggs:"
 92                 + expression.Right.NodeType.ToString());
 93     }
 94     // 处理 Views>= n   访问人数
 95     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
 96     (((MemberExpression)expression.Left).Member.Name == "Views"))
 97     {
 98         if (expression.Right.NodeType == ExpressionType.Constant)
 99             _criteria.MinViews = 
100                 (int)((ConstantExpression)expression.Right).Value;
101 
102         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
103             _criteria.MinViews =
104                 (int)GetMemberValue((MemberExpression)expression.Right);
105 
106         else
107             throw new NotSupportedException("Expression type not supported for Views: " 
108                 + expression.Right.NodeType.ToString());
109     }
110     // 处理 comments >= n   评论数
111     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
112     (((MemberExpression)expression.Left).Member.Name == "Comments"))
113     {
114         if (expression.Right.NodeType == ExpressionType.Constant)
115             _criteria.MinComments =
116                 (int)((ConstantExpression)expression.Right).Value;
117 
118         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
119             _criteria.MinComments = 
120                 (int)GetMemberValue((MemberExpression)expression.Right);
121 
122         else
123             throw new NotSupportedException("Expression type not supported for Comments: "
124                 + expression.Right.NodeType.ToString());
125     }
126     // 处理 发布时间>=
127     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
128     (((MemberExpression)expression.Left).Member.Name == "Published"))
129     {
130         if (expression.Right.NodeType == ExpressionType.Constant)
131             _criteria.Start = 
132                 (DateTime)((ConstantExpression)expression.Right).Value;
133 
134         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
135             _criteria.Start = 
136                 (DateTime)GetMemberValue((MemberExpression)expression.Right);
137 
138         else
139             throw new NotSupportedException("Expression type not supported for Published: " 
140                 + expression.Right.NodeType.ToString());
141     }
142 }
143 
144 // 访问 小于和小于等于
145 private void VisitLessThanOrEqual(BinaryExpression expression)
146 {
147     // 处理 Diggs <= n  推荐人数
148     if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
149         (((MemberExpression)expression.Left).Member.Name == "Diggs"))
150     {
151         if (expression.Right.NodeType == ExpressionType.Constant)
152             _criteria.MaxDiggs =
153                 (int)((ConstantExpression)expression.Right).Value;
154 
155         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
156             _criteria.MaxDiggs =
157                 (int)GetMemberValue((MemberExpression)expression.Right);
158 
159         else
160             throw new NotSupportedException("Expression type not supported for Diggs: " 
161                 + expression.Right.NodeType.ToString());
162     }
163     // 处理 Views<= n   访问人数
164     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
165     (((MemberExpression)expression.Left).Member.Name == "Views"))
166     {
167         if (expression.Right.NodeType == ExpressionType.Constant)
168             _criteria.MaxViews = 
169                 (int)((ConstantExpression)expression.Right).Value;
170 
171         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
172             _criteria.MaxViews =
173                 (int)GetMemberValue((MemberExpression)expression.Right);
174 
175         else
176             throw new NotSupportedException("Expression type not supported for Views: " 
177                 + expression.Right.NodeType.ToString());
178     }
179     // 处理 comments <= n   评论数
180     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
181     (((MemberExpression)expression.Left).Member.Name == "Comments"))
182     {
183         if (expression.Right.NodeType == ExpressionType.Constant)
184             _criteria.MaxComments =
185                 (int)((ConstantExpression)expression.Right).Value;
186 
187         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
188             _criteria.MaxComments = 
189                 (int)GetMemberValue((MemberExpression)expression.Right);
190 
191         else
192             throw new NotSupportedException("Expression type not supported for Comments: "
193                 + expression.Right.NodeType.ToString());
194     }
195 
196         // 处理发布时间 <= 
197     else if ((expression.Left.NodeType == ExpressionType.MemberAccess) &&
198     (((MemberExpression)expression.Left).Member.Name == "Published"))
199     {
200         if (expression.Right.NodeType == ExpressionType.Constant)
201             _criteria.End = 
202                 (DateTime)((ConstantExpression)expression.Right).Value;
203 
204         else if (expression.Right.NodeType == ExpressionType.MemberAccess)
205             _criteria.End =
206                 (DateTime)GetMemberValue((MemberExpression)expression.Right);
207 
208         else
209             throw new NotSupportedException("Expression type not supported for Published: " 
210                 + expression.Right.NodeType.ToString());
211     }
212 }
213 
214 // 访问 方法调用
215 private void VisitMethodCall(MethodCallExpression expression)
216 {
217     if ((expression.Method.DeclaringType == typeof(Queryable)) &&
218         (expression.Method.Name == "Where"))
219     {
220         VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
221     }
222     else if ((expression.Method.DeclaringType == typeof(String)) &&
223         (expression.Method.Name == "Contains"))
224     {
225         // Handle Title.Contains("")
226         if (expression.Object.NodeType == ExpressionType.MemberAccess)
227         {
228             MemberExpression memberExpr = (MemberExpression)expression.Object;
229             if (memberExpr.Expression.Type == typeof(Post))
230             {
231                 if (memberExpr.Member.Name == "Title")
232                 {
233                     Expression argument;
234                     argument = expression.Arguments[0];
235                     if (argument.NodeType == ExpressionType.Constant)
236                     {
237                         _criteria.Title = 
238                             (String)((ConstantExpression)argument).Value;
239                     }
240                     else if (argument.NodeType == ExpressionType.MemberAccess)
241                     {
242                         _criteria.Title = 
243                             (String)GetMemberValue((MemberExpression)argument);
244                     }
245                     else
246                     {
247                         throw new NotSupportedException("Expression type not supported: " 
248                             + argument.NodeType.ToString());
249                     }
250                 }
251             }
252         }
253     }
254     else
255     {
256         throw new NotSupportedException("Method not supported: "
257             + expression.Method.Name);
258     }
259 }
260 
261 // 获取属性值
262 private Object GetMemberValue(MemberExpression memberExpression)
263 {
264     MemberInfo memberInfo;
265     Object obj;
266 
267     if (memberExpression == null)
268         throw new ArgumentNullException("memberExpression");
269 
270 
271     if (memberExpression.Expression is ConstantExpression)
272         obj = ((ConstantExpression)memberExpression.Expression).Value;
273     else if (memberExpression.Expression is MemberExpression)
274         obj = GetMemberValue((MemberExpression)memberExpression.Expression);
275     else
276         throw new NotSupportedException("Expression type not supported: "
277             + memberExpression.Expression.GetType().FullName);
278 
279     memberInfo = memberExpression.Member;
280     if (memberInfo is PropertyInfo)
281     {
282         PropertyInfo property = (PropertyInfo)memberInfo;
283         return property.GetValue(obj, null);
284     }
285     else if (memberInfo is FieldInfo)
286     {
287         FieldInfo field = (FieldInfo)memberInfo;
288         return field.GetValue(obj);
289     }
290     else
291     {
292         throw new NotSupportedException("MemberInfo type not supported: " 
293             + memberInfo.GetType().FullName);
294     }
295 }
296 }
复制代码

 实现CnblogsQueryProvider

有了上面的访问类之后,我们的CnblogsQueryProvider就非常简单了。

复制代码
 1 public class CnblogsQueryProvider:QueryProvider
 2 {
 3     public override String GetQueryText(Expression expression)
 4     {
 5         SearchCriteria criteria;
 6 
 7         // 翻译查询条件
 8         criteria = new PostExpressionVisitor().ProcessExpression(expression);
 9 
10         // 生成URL
11         String url = PostHelper.BuildUrl(criteria);
12 
13         return url;
14     }
15 
16     public override object Execute(Expression expression)
17     {
18         String url = GetQueryText(expression);
19         IEnumerable<Post> results = PostHelper.PerformWebQuery(url);
20         return results;
21     }
22 }
复制代码

我们里面只覆盖了基类的两个方法,GetQueryText和Execute。

  • GetQueryText根据访问类得到的SearchCriteria去和成访问Service的Url
  • Execute访问则负责去请求这个Url拿到数据返回即可

PostHelper请求数据

我们这里面有一个帮助类PostHelper,就负责了根据criteria生成Url以及请求Url获取数据的功能。

复制代码
 1 static internal string BuildUrl(SearchCriteria criteria,string url=null)
 2 {
 3     if (criteria == null)
 4         throw new ArgumentNullException("criteria");
 5 
 6     var sbUrl = new StringBuilder(url ?? "http://linqtocnblogs.cloudapp.net/");
 7     var sbParameter = new StringBuilder();
 8 
 9     if (!String.IsNullOrEmpty(criteria.Title))
10         AppendParameter(sbParameter, "Title", criteria.Title);
11                 
12     if (!String.IsNullOrEmpty(criteria.Author))
13         AppendParameter(sbParameter, "Author", criteria.Author);
14 
15     if (criteria.Start.HasValue)
16         AppendParameter(sbParameter, "Start", criteria.Start.Value.ToString());
17 
18     if (criteria.End.HasValue)
19         AppendParameter(sbParameter, "End", criteria.End.Value.ToString());
20 
21     if (criteria.MinDiggs > 0)
22         AppendParameter(sbParameter, "MinDiggs", criteria.MinDiggs.ToString());
23 
24     if (criteria.MinViews > 0)
25         AppendParameter(sbParameter, "MinViews", criteria.MinViews.ToString());
26 
27     if (criteria.MinComments> 0)
28         AppendParameter(sbParameter, "MinComments",
29             criteria.MinComments.ToString());
30 
31     if (criteria.MaxDiggs > 0)
32         AppendParameter(sbParameter, "MaxDiggs", criteria.MaxDiggs.ToString());
33 
34     if (criteria.MaxViews > 0)
35         AppendParameter(sbParameter, "MaxViews", criteria.MaxViews.ToString());
36 
37     if (criteria.MaxComments > 0)
38         AppendParameter(sbParameter, "MaxComments",
39             criteria.MaxComments.ToString());
40 
41     if (sbParameter.Length > 0)
42         sbUrl.AppendFormat("?{0}", sbParameter.ToString());
43 
44     return sbUrl.ToString();
45 }
46 
47 static internal void AppendParameter(StringBuilder sb,string name,string value)
48 {
49     if (sb.Length > 0)
50         sb.Append("&");
51 
52     sb.AppendFormat("{0}={1}",name,value);
53 }
54 
55 static internal IEnumerable<Post> PerformWebQuery(string url)
56 {
57     var request = WebRequest.Create(url);
58     request.Credentials = CredentialCache.DefaultCredentials;
59 
60     var response = (HttpWebResponse)request.GetResponse();
61     using(var reader= new StreamReader(response.GetResponseStream()))
62     {
63         var body = reader.ReadToEnd();
64         return JsonConvert.DeserializeObject<List<Post>>(body);
65     }
66 }
67 }
复制代码

因为我们的Service是返回的Json数据,所以这里,我们借助了Json.Net将其转成我们所要的List<Post>的数据。

就是这么简单,我们的Linq to cnblogs就大功告成了。

点击这里下载源码:http://pan.baidu.com/s/1gd85l1T 

作者:Jesse 出处: http://jesse2013.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

sql server 2008 R2 x86下载及安装说明_muxixi1_的博客-CSDN博客_sqlserver2008 x86

mikel阅读(650)

来源: sql server 2008 R2 x86下载及安装说明_muxixi1_的博客-CSDN博客_sqlserver2008 x86

软件链接:链接:http://pan.baidu.com/s/1hrMUwYC 密码:6jeq

安装说明请参考http://jingyan.baidu.com/article/49711c61486072fa441b7cb2.html

温馨提示:链接失效可以关注我微信公众号  amxash  ,发送  SQLServer2008r2 索要最新链接。

备注:该SQLServer版本为 SQLServer 2008 r2   x86版本,亲测在windows xp电脑安装成功。
————————————————
版权声明:本文为CSDN博主「muxixi1_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/muxixi1_/article/details/52089564

sql server几种读写分离方案对比_tiantian1980的专栏-CSDN博客

mikel阅读(646)

来源: sql server几种读写分离方案对比_tiantian1980的专栏-CSDN博客

读写分离方案

实时同步

副本数据是否直接可读

副本数

最小粒度

副本建立索引

环境

缺点

镜像

否(需要开启快照,只读)

1

域/非域(使用证书)

在高安全模式下对主库性能有一定影响

log shipping

是(只读)

N

UNC方式可访问

副本库在做resotre时会断开已连接用户连接/可能影响常规日志备份

发布订阅

是(读写,但写可能会产生数据不一致)

N

表(查询)

域/非域

在主库上有大量DML操作时,对分发服务器会有一定影响,且订阅数据库可能有数据同步延迟

always on

是(只读)

4(SQL 2012)

8(SQL 2014)

非域环境无法使用

前端的oltp业务很繁忙,但是需要对这些运营数据进行olap,为了不影响前端正常业务,所以需要将数据库进行读写分离。

这里我将几种可以用来进行读写分离的方案总结一下,这里并不考虑数据库是否高可用,只针对读写分离场景,方案本身并无优劣可言,只看是否适合业务使用场景,所以只把几个方案的特点罗列出来,遇到具体的问题时按自己需求和环境综合考虑后再进行取舍。

 

注:oltp也称为面向交易的处理过程,其基本特征是前台接收的用户数据可以立即传送到计算中心进行处理,并在很短的时间内给出处理结果,是对用户操作快速响应的方式之一。

DDL:数据库模式定义语言,关键字:create

DML:数据操纵语言,关键字:Insert、delete、update

DCL:数据库控制语言 ,关键字:grant、remove

DQL:数据库查询语言,关键字:select