全站式代码生成器示例
全站式代码生成器示例
背景
一直在做企业应用,也一直在使用代码生成器,代码生成器分两个维度,一个维度是”主动或被动“,另外一个维度是”运行时或编译时“,这两种维度会有四种组合,每个组合都有其应用的场景,今天我就介绍一下Happy是如何使用代码生成器的。
概念介绍
主动:可以生成多次,会”主动“的合并生成代码和用户自定义代码,C#的部分类和ExtJs的扩展类就是,通过一些文本合并工具也是可以实现的。
被动:不可以生成多次,每次生成都会覆盖用户自定义的代码。
运行时:运行时的代码生成,也叫元编程,动态语言几乎都支持,静态语言可以使用动态编译。
编译时:编译时的代码生成,是狭义的代码生成器的代名词。
还有一点需要说明的,如果是基于应用框架的代码生成器,生成的代码会非常少。
示例
编译时主动+编译时被动代码生成器代码
这里的生成器我是基于NodeJs开发的,T4、CodeSmith和其它代码生成器也不错。
这里的编译时主动是指每次都会生成,用户如果希望个性化代码,就写C#的部分类或ExtJs的扩展类。
这里的编译时被动是指每次都会覆盖用户配置,当然你可以指定只生成一次。
生成后的项目
Application.Generator.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 using Happy.Command;
8 using Happy.Application;
9 using Happy.Query.PetaPoco;
10 using Demo.Domain;
11
12 namespace Demo.Application
13 {
14
15 public partial class TestGridService : AggregateService<IDemoUnitOfWork, ITestGridRepository, TestGrid> { }
16
17 public partial class TestGridCreateCommand : SimpleCreateCommand<TestGrid> { }
18
19 public partial class TestGridCreateCommandHandler : SimpleCreateCommandHandler<TestGridService, TestGrid, TestGridCreateCommand> { }
20
21 public partial class TestGridUpdateCommand : SimpleUpdateCommand<TestGrid> { }
22
23 public partial class TestGridUpdateCommandHandler : SimpleUpdateCommandHandler<TestGridService, TestGrid, TestGridUpdateCommand> { }
24
25 public partial class TestGridDeleteCommand : SimpleDeleteCommand<TestGrid> { }
26
27 public partial class TestGridDeleteCommandHandler : SimpleDeleteCommandHandler<TestGridService, TestGrid, TestGridDeleteCommand> { }
28
29 public partial class TestGridDynamicQueryService : PetaPocoDynamicQueryService
30 {
31 public TestGridDynamicQueryService() :
32 base ( @" Data Source=(LocalDB)\v11.0;AttachDbFilename= " + AppDomain.CurrentDomain.BaseDirectory + @" App_Data\TestDatabase.mdf;Integrated Security=True;Connect Timeout=30 " , " System.Data.SqlClient " , " TestGrids " )
33 { }
34 }
35
36 public partial class TestTreeService : AggregateService<IDemoUnitOfWork, ITestTreeRepository, TestTree> { }
37
38 public partial class TestTreeCreateCommand : SimpleCreateCommand<TestTree> { }
39
40 public partial class TestTreeCreateCommandHandler : SimpleCreateCommandHandler<TestTreeService, TestTree, TestTreeCreateCommand> { }
41
42 public partial class TestTreeUpdateCommand : SimpleUpdateCommand<TestTree> { }
43
44 public partial class TestTreeUpdateCommandHandler : SimpleUpdateCommandHandler<TestTreeService, TestTree, TestTreeUpdateCommand> { }
45
46 public partial class TestTreeDeleteCommand : SimpleDeleteCommand<TestTree> { }
47
48 public partial class TestTreeDeleteCommandHandler : SimpleDeleteCommandHandler<TestTreeService, TestTree, TestTreeDeleteCommand> { }
49
50 public partial class TestTreeDynamicQueryService : PetaPocoDynamicQueryService
51 {
52 public TestTreeDynamicQueryService() :
53 base ( @" Data Source=(LocalDB)\v11.0;AttachDbFilename= " + AppDomain.CurrentDomain.BaseDirectory + @" App_Data\TestDatabase.mdf;Integrated Security=True;Connect Timeout=30 " , " System.Data.SqlClient " , " TestTrees " )
54 { }
55 }
56
57 }
运行时代码生成
目前只做了基于JS的运行时代码生成。
Happy.metadata.Manager.js
1 /* *
2 * 元数据管理器,主要完成元数据的合并和根据元数据生成常用配置和类型。
3 *
4 * @class Manager
5 * @namespace Happy.metadata
6 * @constructor
7 * @param {Object} config
8 * @param {Array} config.metadatas 要合并的元数据对象数组,索引越大优先级越高。
9 */
10 Ext.define('Happy.metadata.Manager' , {
11 requires: [
12 'Happy.metadata.DatabaseTypeMapper' ,
13 'Happy.data.proxy.Ajax'
14 ],
15
16 /* *
17 * 要合并的元数据对象数组,索引越大优先级越高。
18 *
19 * @private
20 * @property metadatas
21 * @type Array
22 */
23
24 /* *
25 * 合并后的元数据。
26 *
27 * @private
28 * @property metadata
29 * @type Object
30 */
31
32 /* *
33 * 根据合并后的元数据生成的表单控件配置数组。
34 *
35 * @private
36 * @property formEditors
37 * @type Array
38 */
39
40 /* *
41 * 根据合并后的元数据生成的表格列配置数组。
42 *
43 * @private
44 * @property gridColumns
45 * @type Array
46 */
47
48 /* *
49 * 根据合并后的元数据生成的模型类。
50 *
51 * @private
52 * @property model
53 * @type Ext.data.Model
54 */
55
56 /* *
57 * 根据合并后的元数据生成的仓储类。
58 *
59 * @private
60 * @property store
61 * @type Ext.data.Store
62 */
63
64 /* *
65 * @method constructor
66 */
67 constructor: function (config) {
68 var me = this ;
69
70 me.metadatas = config.metadatas;
71
72 me.initMetadata();
73
74 me.initFormEditors();
75
76 me.initGridColumns();
77
78 if (me.isTree()) {
79 me.initTreeColumns();
80 }
81
82 me.defineModel();
83 me.defineStore();
84
85 if (me.isTree()) {
86 me.defineTreeModel();
87 me.defineTreeStore();
88 }
89 },
90
91 /* *
92 * 合并并初始化元数据。
93 * @private
94 * @method initMetadata
95 */
96 initMetadata: function () {
97 var me = this ;
98
99 me.metadata = {};
100
101 Ext.Array.each(me.metadatas || [], function (metadata) {
102 Ext.merge(me.metadata, metadata);
103 });
104 },
105
106 /* *
107 * 获取合并后的元数据。
108 * @method getMetadata
109 * @return {Object}
110 */
111 getMetadata: function () {
112 var me = this ;
113
114 return me.metadata;
115 },
116
117 /* *
118 * @private
119 * @method getMetadata
120 * @return {Object}
121 */
122 isTree: function () {
123 var me = this ;
124
125 return !!me.metadata.columns['NodePath' ];
126 },
127
128 /* *
129 * 初始化表单控件配置数组。
130 * @private
131 * @method initFormEditors
132 */
133 initFormEditors: function () {
134 var me = this ;
135
136 var columns = Ext.Object.getValues(me.metadata.columns);
137
138 me.formEditors = Ext.Array.map(columns, function (column) {
139 var dataTypeName = column.dataType.typeName;
140 var editorConfig = me.getDatabaseTypeMapper().getFormEditorConfig(dataTypeName);
141
142 return Ext.apply({
143 name: column.name,
144 fieldLabel: column.text || column.name
145 }, editorConfig);
146 });
147 },
148
149 /* *
150 * 获取表单控件配置数组。
151 * @method getFormEditors
152 * @return {Array}
153 */
154 getFormEditors: function () {
155 var me = this ;
156
157 return me.formEditors;
158 },
159
160 /* *
161 * 初始化表格列配置数组。
162 * @private
163 * @method initGridColumns
164 */
165 initGridColumns: function () {
166 var me = this ;
167
168 var columns = Ext.Object.getValues(me.metadata.columns);
169
170 me.gridColumns = Ext.Array.map(columns, function (column) {
171 var dataTypeName = column.dataType.typeName;
172 var columnConfig = me.getDatabaseTypeMapper().getGridColumnConfig(dataTypeName);
173
174 return Ext.apply({
175 dataIndex: column.name,
176 text: column.text || column.name
177 }, columnConfig);
178 });
179 },
180
181 /* *
182 * 获取表格列配置数组。
183 * @method getGridColumns
184 * @return {Array}
185 */
186 getGridColumns: function () {
187 var me = this ;
188
189 return me.gridColumns;
190 },
191
192 /* *
193 * 获取表单控件配置数组。
194 * @method getFormEditors
195 * @return {Array}
196 */
197 getFormEditors: function () {
198 var me = this ;
199
200 return me.formEditors;
201 },
202
203 /* *
204 * 初始化树表格列配置数组。
205 * @private
206 * @method initTreeColumns
207 */
208 initTreeColumns: function () {
209 var me = this ;
210
211 var columns = Ext.Object.getValues(me.metadata.columns);
212
213 me.treeColumns = Ext.Array.map(columns, function (column) {
214 var dataTypeName = column.dataType.typeName;
215 var columnConfig = me.getDatabaseTypeMapper().getGridColumnConfig(dataTypeName);
216
217 return Ext.apply({
218 dataIndex: column.name,
219 text: column.text || column.name
220 }, columnConfig);
221 });
222 },
223
224 /* *
225 * 获取树表格列配置数组。
226 * @method getTreeColumns
227 * @return {Array}
228 */
229 getTreeColumns: function () {
230 var me = this ;
231
232 return me.treeColumns;
233 },
234
235 /* *
236 * 定义模型类。
237 * @private
238 * @method defineModel
239 */
240 defineModel: function () {
241 var me = this ;
242
243 me.model = Ext.define(me.getModelName(), {
244 extend: 'Ext.data.Model' ,
245 fields: me.getModelFields(),
246 idProperty: 'Id' ,
247 proxy: {
248 type: 'happy-ajax' ,
249 api: {
250 create: '/' + me.metadata.singular + 'Command/Create' ,
251 read: '/' + me.metadata.singular + 'DynamicQuery/Page' ,
252 update: '/' + me.metadata.singular + 'Command/Update' ,
253 destroy: '/' + me.metadata.singular + 'Command/Delete'
254 },
255 reader: {
256 type: 'json' ,
257 root: 'items' ,
258 idProperty: 'Id' ,
259 messageProperty: 'message'
260 },
261 writer: {
262 type: 'json' ,
263 encode: true ,
264 root: 'item'
265 }
266 },
267
268 getTableName: function () {
269 return me.metadata.name;
270 }
271 });
272 },
273
274 /* *
275 * 获取定义的模型类。
276 * @method getModel
277 * @return {Ext.data.Model}
278 */
279 getModel: function () {
280 var me = this ;
281
282 return me.model;
283 },
284
285 /* *
286 * 定义树模型类。
287 * @private
288 * @method defineTreeModel
289 */
290 defineTreeModel: function () {
291 var me = this ;
292
293 var fields = me.getModelFields();
294
295 fields.push({
296 name: 'parentId' ,
297 type: 'string' ,
298 defaultValue: null ,
299 useNull: false ,
300 persist: false
301 });
302
303 fields.push({
304 name: 'leaf' ,
305 type: 'bool' ,
306 defaultValue: false ,
307 persist: false
308 });
309
310 me.treeModel = Ext.define(me.getTreeModelName(), {
311 extend: 'Ext.data.Model' ,
312 fields: fields,
313 idProperty: 'Id' ,
314 proxy: {
315 type: 'happy-ajax' ,
316 api: {
317 create: '/' + me.metadata.singular + 'Command/Create' ,
318 read: '/' + me.metadata.singular + 'DynamicQuery/Page' ,
319 update: '/' + me.metadata.singular + 'Command/Update' ,
320 destroy: '/' + me.metadata.singular + 'Command/Delete'
321 },
322 reader: {
323 type: 'json' ,
324 root: 'items' ,
325 idProperty: 'Id' ,
326 messageProperty: 'message'
327 },
328 writer: {
329 type: 'json' ,
330 encode: true ,
331 root: 'item'
332 }
333 },
334
335 getTableName: function () {
336 return me.metadata.name;
337 }
338 });
339 },
340
341 /* *
342 * 获取定义的树模型类。
343 * @method getTreeModel
344 * @return {Ext.data.Model}
345 */
346 getTreeModel: function () {
347 var me = this ;
348
349 return me.treeModel;
350 },
351
352 /* *
353 * 获取定义模型类需要的字段数据。
354 * @private
355 * @method getModelFields
356 * @return {Array}
357 */
358 getModelFields: function () {
359 var me = this ;
360
361 var columns = Ext.Object.getValues(me.metadata.columns);
362
363 return Ext.Array.map(columns, function (column) {
364 var dataTypeName = column.dataType.typeName;
365 var fieldConfig = me.getDatabaseTypeMapper().getModelFieldConfig(dataTypeName);
366
367 return Ext.apply({
368 name: column.name
369 }, fieldConfig);
370 });
371 },
372
373 /* *
374 * 定义仓储类。
375 * @private
376 * @method defineGridStore
377 */
378 defineStore: function () {
379 var me = this ;
380
381 me.store = Ext.define(me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.store.' + me.metadata.singular, {
382 extend: 'Ext.data.Store' ,
383 model: me.getModelName(),
384
385 getTableName: function () {
386 return me.metadata.name;
387 }
388 });
389 },
390
391 /* *
392 * 获取定义的仓储类。
393 * @method getGridStore
394 * @return {Ext.data.Store}
395 */
396 getStore: function () {
397 var me = this ;
398
399 return me.store;
400 },
401
402 /* *
403 * 定义树仓储类。
404 * @private
405 * @method defineTreeStore
406 */
407 defineTreeStore: function () {
408 var me = this ;
409
410 me.treeStore = Ext.define(me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.treestore.' + me.metadata.singular, {
411 extend: 'Ext.data.TreeStore' ,
412
413 defaultRootId: '00000000-0000-0000-0000-000000000000' ,
414 model: me.getTreeModelName(),
415 root: me.metadata.treeRoot || {
416 text: me.metadata.singular,
417 expanded: true
418 },
419 proxy: {
420 type: 'happy-ajax' ,
421 api: {
422 create: '/' + me.metadata.singular + 'Command/Create' ,
423 read: '/' + me.metadata.singular + 'DynamicQuery/ReadNode' ,
424 update: '/' + me.metadata.singular + 'Command/Update' ,
425 destroy: '/' + me.metadata.singular + 'Command/Delete'
426 },
427 reader: {
428 type: 'json' ,
429 root: 'items' ,
430 idProperty: 'Id' ,
431 messageProperty: 'message'
432 },
433 writer: {
434 type: 'json' ,
435 encode: true ,
436 root: 'item'
437 }
438 },
439
440 getTableName: function () {
441 return me.metadata.name;
442 }
443 });
444 },
445
446 /* *
447 * 获取定义的树仓储类。
448 * @method getTreeStore
449 * @return {Ext.data.Store}
450 */
451 getTreeStore: function () {
452 var me = this ;
453
454 return me.treeStore;
455 },
456
457 /* *
458 * 获取定义模型类需要的类名。
459 * @private
460 * @method getModelName
461 * @return {String}
462 */
463 getModelName: function () {
464 var me = this ;
465
466 return me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.model.' + me.metadata.singular;
467 },
468
469 /* *
470 * 获取定义树模型类需要的类名。
471 * @private
472 * @method getTreeModelName
473 * @return {String}
474 */
475 getTreeModelName: function () {
476 var me = this ;
477
478 return me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.treemodel.' + me.metadata.singular;
479 },
480
481 /* *
482 * 获取数据库类型映射器。
483 * @private
484 * @method getModelName
485 * @return {Happy.metadata.DatabaseTypeMapper}
486 */
487 getDatabaseTypeMapper: function () {
488 var me = this ;
489
490 return Happy.metadata.DatabaseTypeMapper;
491 }
492 });
运行效果
备注
刚开了个头,这篇文章只是介绍了代码生成器的一些使用场景,但是真正重要的是系统的架构风格,我的偏好是DDD + CQRS,因此最终的目标是支持DDD + CQRS,代码生成器只不过帮我写了一些代码,如果要做到支持DDD + CQRS的话,需要抽象出很多元数据,比如:聚合根、实体、值对象和他们的关系等等。
框架地址: http://happy.codeplex测试数据
博客地址: http://HdhCmsTestcnblogs测试数据/happyframework
作者: Leo_wl
出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息