通过调用指定目录下的所有模板,逐一按照数据表生成独立的代码文件。支持多模板调用、支持所有数据表生成或批量指定多个生成、支持自动的文件目录结构、支持代码文件格式化命名等。
背景:最近一个新项目一高兴选了mysql 8,结果出了好几个麻烦。 toad for mysql 连不上了,习惯的动软代码生成器也连不上了。 还是挺喜欢动软的傻瓜式操作的,现在没办法了试试codesmith,折腾了半天终于算是出坑了, 做个笔记免得下次再跳。
一、需求分析
大概的需求:
1.批量选择表:先选择数据库,然后选择这个数据库中想生成的表,不选择则认为是生成全部。
2.批量选择模板: 首先想到的是像选择数据表一样,后来决定通过选择目录的方式,这样可以将模板按照一定的目录结构放在一个总目录中,生成的代码文件仿照对应模板的目录结构。
3.自动生成文件:通过模板的rendertofile方法自动写入到文件,不再需要从弹出窗口中复制。
4.文件名按照一个规则自定义:每个模板可以定义一个名称规则,如上文的 {0}model.cs 则会生成文件 usermodel.cs 。
5.统一设置命名空间:如上例可以在弹出框中设置,但多个模板同时调用的情况下,只需设置一次使多个模板同时生效。
二、数据源连接
点击右上角的数据源配置,添加一个:
弹出框点击add,出现下面的
根据对应的数据库类型选择即可,mysql连不上的请看上一篇文章:codesmith连接mysql。
三、创建模板
首先创建一个最简单的模板,用于生成实体类,在右上角的template explorer中右键新建一个csharp的模板model.cst:
<%@ codetemplate language="c#" targetlanguage="c#" debug="false" %>
<%@ property name="sourcetable" type="schemaexplorer.tableschema" category="context" description="tablename" %>
<%@ property name="filenameformat" type="string" category="context" default="{0}model.cs" description="filename formatstr" optional="true" %>
<%@ property name="namespace" type="system.string" default="" category="namespace" description="namespace" optional="true" %>
<%@ map name="csharpalias" src="system-csharpalias" description="system to c# type map" %>
<%@ assembly name="schemaexplorer" %>
<%@ import namespace="schemaexplorer" %>
using system;
namespace <%= string.isnullorempty(namespace)?"":namespace + "." %>wx.model
{
public class <%= stringutil.topascalcase(sourcetable.name) %>
{
<% foreach (columnschema column in this.sourcetable.columns) { %>
public <%= csharpalias[column.systemtype.fullname] %> <%= stringutil.topascalcase(column.name) %> { get; set; }
<% } %>
}
}
右键点击它选择execute,会弹出对话框让选择一个数据表,选择后点击生成按钮,会生成类似如下代码:
我要的肯定不是每次选择一个表,也不想一个个生成之后复制粘贴到项目中去。而且我也不是只需要这样的一个模板,我要自动生成的有好多。
四、制作用于批量调用的模板
首先仿照上例新建了几个模板,例如生成controller的、viewmodel的,目录结构如下:
这些都是具体的“子模板”,然后按照上面的需求新建了一个名为generate.cst的模板。
1 <%@ codetemplate language="c#" targetlanguage="c#" debug="true" description="模板输出" %>
2
3 <%@ property name="namespace" type="system.string" default="" category="namespace" optional="true" description="项目的命名空间" %>
4
5 <%@ property name="sourcedatabase" type="databaseschema" default="" optional="true" category="数据源" description="选择数据库" %>
6 <%@ property name="sourcetables" type="schemaexplorer.tableschemacollection" default="" optional="true" category="数据源" description="选择数据表,默认空则为全部表" %>
7
8
9 <%@ property name="templatedirectory" type="string" editor="system.windows.forms.design.foldernameeditor" editorbase="system.drawing.design.uitypeeditor" optional="false" category="模板" description="--模板目录--"%>
10 <%@ property name="exceptprefix" type="string" default="" optional="true" category="模板" description="排除前缀为__的" %>
11
12 <%@ property name="outputdirectory" type="string" editor="system.windows.forms.design.foldernameeditor" editorbase="system.drawing.design.uitypeeditor" optional="false" category="输出目录" description="--输出目录--"%>
13
14
15 <%@ assembly name="system.design" %>
16 <%@ assembly name="schemaexplorer" %>
17 <%@ import namespace="schemaexplorer" %>
18
19 <%@ import namespace="system.io" %>
20 <%@ import namespace="system.xml" %>
21 <%@ import namespace="system.text" %>
22 <%@ import namespace="system.windows.forms.design" %>
23 <%@ import namespace="system.text.regularexpressions" %>
24 <%@ import namespace="system.collections.specialized" %>
25 <%@ import namespace="system.collections.generic" %>
26
27
28
29 <script runat="template">
30 //模板列表
31 dictionary<codetemplate, string> templates = new dictionary<codetemplate, string>();
32
33 //入口
34 public void generate()
35 {
36 gettemplates(templatedirectory); //读取模板
37
38 int tableindex = 0;
39 int templateindex = 0;
40 tableschemacollection tables = sourcetables != null && sourcetables.count > 0 ? sourcetables : sourcedatabase.tables; //若未手动选择数据表,则默认为全数据库
41 foreach(tableschema sourcetable in tables)
42 {
43 tableindex++;
44
45 response.write(string.format("{0}.table {1}",tableindex, sourcetable.name));
46 response.writeline();
47 templateindex = 0;
48 foreach(var template in templates)
49 {
50 templateindex++;
51 response.write(string.format(" {0}.template {1}",templateindex, template.key.codetemplateinfo.filename));
52 response.writeline();
53 template.key.setproperty("namespace",namespace); //设置统一的命名空间
54 template.key.setproperty("sourcetable",sourcetable); //传入数据表的名称
55
56 //读取模板的文件命名格式,生成文件名
57 string filename = template.key.getproperty("filenameformat") == null ? sourcetable.name : string.format(template.key.getproperty("filenameformat").tostring(),sourcetable.name);
58 template.key.rendertofile(path.combine(template.value, filename), true); //按照模板的目录层级生成文件
59 }
60 response.writeline();
61 }
62 }
63
64 private void gettemplates(string directorystr)
65 {
66 directoryinfo directoryinfo = directory.createdirectory(directorystr);
67 gettemplates(directoryinfo);
68 }
69
70 //递归方式读取所有满足要求的模板,记录模板的目录结构,生成代码文件时参照此时的目录结构
71 private void gettemplates(directoryinfo directoryinfo)
72 {
73 foreach (var file in directoryinfo.getfiles())
74 {
75 if (!file.extension.tolower().equals(".cst") || (!string.isnullorempty(exceptprefix) && file.name.startswith(exceptprefix)))
76 {
77 continue;
78 }
79
80 codetemplatecompiler compiler = new codetemplatecompiler(file.fullname);
81 compiler.compile(); //编译子模板
82 if (compiler.errors.count == 0)
83 {
84 templates.add(compiler.createinstance(),directoryinfo.fullname.replace(templatedirectory, outputdirectory));
85 }
86 else
87 {
88 response.writeline("编译模板" + file.fullname + "错误!");
89 }
90 }
91
92 foreach (var directory in directoryinfo.getdirectories())
93 {
94 gettemplates(directory);
95 }
96 }
97 </script>
98
99 <% this.generate(); %>
最上面是各种属性设置:
1. namespace: 用于设置项目统一的命名空间前缀。
2. sourcedatabase:可以弹出的对话框中选择已经配置好的数据库。
3. sourcetables:批量选择数据表,可以按住ctrl或shift多选。
4. templatedirectory:模板所在目录,支持树状目录结构。
5. exceptprefix:排除模板前缀,前缀为这样的模板不会被获取。
6. outputdirectory:生成文件存储目录,其子目录结构会按照模板目录结构。
此模板文件的入口是generate(),它首先会调用gettemplates方法读取属性templatedirectory指定的目录下的所有模板,并记录各个模板所在的目录结构。然后判断属性sourcetables是否手动选择了数据表,如果没有则为所有表。
准备工作做好之后,就是遍历数据表、遍历模板,生成对应的代码文件了。具体情况已在代码中注释。