CodErator开发笔记

CodErator 取自 Code Generator,这么一结合,既有代码生成器的含义,又有代码操作者的意味,亦或是更多的含义。既然定位在轻量级代码生成工具,只要能完成任务,就是最好的了。

关于项目的启发

这个项目在建立之前,我有一段时间用过 CodeSmith 试用版,也写了两个使用 CodeSmith 生成 JavaEE 主流框架代码的模板(参见SSMGenerator,另一个尚未发布)。但众所周知,CodeSmith 可不是个亲近的家伙,收费,且新版(ver.7.1)不能使用 Visual Studio 编写模板(它自建了一个 Template Editor),所以 CodeSmith 使用成本就这么摆在这里了。

既然我想完成一个简单的代码生成工作,又不想专门为你出这个钱,而且你又把部分 DLL 的代码公布了,那我不如自己写一个轻便的生成工具,只要能完成工作就好了呗。就是这样的想法,促成了这个项目的产生。

开发设计

老实说,这个项目因为还有另一个目的,就是课程设计,花在这个项目初始版本上的时间就显得比较紧,所以并没有什么设计。

稍微对我过去开发的情况做了个估计,这个项目姑且算是利用 Build and Fix Model 完成开发,仅有一个思想驱使项目进程的发展:

我需要读到元数据,然后利用元数据封装成对象,通过模板引擎传送到模板文件最后生成目标代码。

然而。。。

最后却转变成了,通过对生成代码的操作流程去编写功能,而在此基础上对每一个流程可能会出现的误操作做出相应的限制动作,使得流程变得线性化,间接体现出易用性。

所以!

通过对流程的分析,可以知道使用者需要做的事情能够拆分成如下的动作:

  1. 连接到数据库,获取库下的 table 列表

  2. 使用者可以点击单个 table 列表项查看该表将要被生成的字段内容,此举是为了解决 CodeSmith 的某些“缺陷”,即你能看到 schemas 和 tables,但你看不到 columns

  3. 使用者在 table 列表中选择想要生成的一或多张表

  4. 使用者选择生成目标代码的语言和想要生成的层面

  5. 选择输出位置,开始生成

设计中出现的需求

既然提取出了“生成代码”用例的概要动作,那么就要对这些动作需要哪些技术、功能等进行分析:

  1. 连接到任意的数据库,收集连接数据(但不允许持久化这些连接数据)

  2. 获取 tables 和相应 table 的 columns

  3. 收集生成选项

  4. 封装查找到的 Table 和 Columns

  5. 使用模板文件去生成代码而不是硬编码到源代码中去生成

对于上述设计和需求,我采用了以下支持

  • 既然是连接到数据库,那么我就加入 MySQL 和 SQL Server 的支持吧,反正使用 Oracle 的程序猿不一定会用轻量级代码生成器的吧

  • 模板,就你了,Razor Engine

  • 界面?WinForm 咯,今后再移植 WPF 吧

  • 其他的?那还真没有什么技术可言了,就只是普通 C#而已

单独拿模板出来说一下事。我在考虑模板使用的时候,考虑过以下四种模板:

  1. CodeSmith 的 cst

  2. T4 文本模板(Text Template Transformation Toolkit)

  3. NVelocity(Velocity 的.Net 版)

  4. Razor Engine

cst 自然不用说,结合 CodeSmith 的 SchemaExplorer,可以省略复杂的客户端操作,直接在模板内声明几个参数然后在客户端给参数赋值,就可以完成生成。问题来了,CodeSmith 本身是收费软件,调用他们的 DLL 很容易,可是会给你跳出个他们家的产品注册窗口,这就很哭笑不得了。

T4 文本模板,是微软自家的代码生成文本模板,原本在我看来是个比较好的模板,而且写起来跟写 cst 没什么差别,但问题是,T4 文本模板本身就是一个可运行的“class”,这就显得代码不可控了,用户侧可能就会出现各种各样的问题。

NVelocity,其实就是 Velocity,只不过有人把它.Net 化了。不过这个扩展据我查找的资料,大多文章的发布年是在 2011 年和 2013 年,而该项目本身停留在了 0.4.2 版,发布日期还是 2003 年 10 月。想想现在 Velocity 都什么版本了,天差地别了吧。

Razor Engine,直到 2016 年还在维护的.Net 模板引擎。它的工作方式更像我所熟知的 Java Controller 传递对象到 jsp 等模板的方式,而且它是非侵入式的模板引擎,想用即可,几乎不需要任何配置,而且测试方便。

Razor Engine 的基本工作方式可以描述为如下的流程。首先,程序从文本读取到 cshtml 模板文件的内容,将这些内容保存到 string 类型的变量中,然后只需要通过:

1
Engine.Razor.RunCompile(template, unique_name, [modelType], model)

这个方法,让引擎获取模板内容和 model 数据,自动替换模板内容内占位符对应的 model 信息,最后返回结果字符串。

程序使用过程中可能会出现的搞事操作与处理办法

  1. 没有连接数据库就执行生成:检测到连接数据不完善,立即终止操作并弹出提示。

  2. 没有选择表:很少会出现的错误吧,真的出现的话就终止操作并提醒一下。

  3. 没有选择语言就选择层面,或者反过来没有选择层面只选择语言:一样终止操作,并提示缺少了什么没有选。

  4. 没有选择输出目录:终止操作,弹出消息框提醒。

  5. 数据库连接异常:故意输入错误的连接信息对吧?吃我个连接时异常信息提醒!(当然是友好的消息框啦)

  6. 模板文件异常:很遗憾的是,我没办法检测你的模板到底出了什么错,至少现在是没办法的,只能给你丢一个友善的提醒了。

对于数据的处理

上面提到了“获取 tables 和相应 table 的 columns”,加之 Razor 需要提供一个 model 来传递参数(当然,RunCompile 还能接受更多对象),那么肯定会想到封装了。然而我稍微了解了一下 System.Data 下的类,事情好像并不是那么容易,比如我需要一个 DataTable 去读取一个 table 下的字段元数据,但我却拿不到表名(不能更方便地拿到表名,需要写一长串调用关系,而事实上是可以的)。

于是,为了使封装后的数据调用起来更懒人化,我创建了两个 Model:

  1. Table

  2. Column

这就显得比较“粗暴”了,获取了表名交给 Table,获取了这个表的所有字段元数据,分别封装成 Column 交给 Table 里的 list。

这样一来,最后在模板内的调用就可以直接用属性去拿到信息了。