前段时间在CNBLOGS上面看到好多人谈论代码自动生成。这里所谓的代码自动生成是指自动根据数据库的内容身成数据访问存储过程以及对应的C#代码。比较典型的有CodePlus等(我最早看到的类似工具是DBHelper,大概是在CodeProject上,记不清楚了。功能强大而且完全开源)。
这些代码自动生成工具确实会帮助我们减少代码编写的麻烦,提高工作效率。不过,我在这里还是想对“代码自动生成”发表一下我的看法:
一、代码自动生成的言外之意是先有数据库再编代码
先设计数据库再根据数据库编写程序,很多人(包括我在内)都喜欢这样开发程序。因为这种开发模式似乎很对口味。不过问题也就随之而来了。
首先,数据库的设计方案是根据业务方案而来的,通常要进行完业务流程分析后进行数据流程分析,然后建立数据字典给出数据库设计方案(传统的管理信息系统分析于设计方式)。在没有充分进行业务流程分析的情况下就开始数据库的设计是一项很危险的事。
其次,我们使用的数据库通常是关系型数据库,而业务分析通常对应实体模型,为了更好的通过关系模型描述实体模型,数据库设计通常包括这么几个阶段:
- 需求分析,分析业务流程以及数据库流程
- 概念设计,包括局部设计以及集成设计反复迭代,给出E-R图(实体-关系图)
- 物理设计,包括确定数据库物理结构、字段方案、约束条件、索引等等,建立物理模型
- 数据库实施,通过DDL建立数据库
从分析的阶段上来看,也应当是先有实体而后有数据库。像Delphi 8中的ECO,在设计过程中首先进行对象的设计,然后通过Persist保存对象(可以存成XML,也可以存储成关系数据库)。
很多人(包括我在内)喜欢先设计数据库的根源在于过于看重编码而轻视了分析流程。导致一旦数据库底层发生变化(这是很可能的),所有上层内容,包括存储过程、O-R代码、业务对象乃至表现层都会发生变化。代码的自动生成缓解了写代码的压力,但没有从根源上解决问题。
软件开发的“依赖倒置原则”要求我们依赖于抽象而尽可能避免依赖于具体。在这里我们可以利用这种思想,将“抽象”提取出来。而最有可能担当“抽象”角色的就应当是“业务实体”本身。虽然客户需求的频繁变更倒置业务实体本身会发生变化,但是,这比依赖于数据库稳定的多。
陈震军的《典型的分层、接口和工厂的写法》描述的微软宠物商店的数据库设计,其中的抽象层定义了业务实体接口。一旦业务实体发生变化,两条并行工作可以同时进行:一是根据业务的实体的变化向下修改数据库;二是根据抽象业务实体接口向上修改商业逻辑以及界面等等。如果没有这层抽象,恐怕只能根据业务实体的变化调整数据库,然后从数据库开始修改数据访问层、实体层、商业逻辑层直到标识层。相比之下还是第一种做法好一些。(这只代表个人观点,欢迎大家讨论)
因此,所谓自动代码生成是根据关系模型生成实体模型呢还是根据实体模型生成关系模型,顺序问题值得讨论。
二、代码自动生成真的起到应有的作用了吗?
抛开O-R还是R-O问题,单纯从代码生成的角度来看,目前我看到的几种方案千篇一律,尤其是更新,生成的更新代码基本上没有抛开这个模式:
UPDATE ... SET ... WHERE 关键字段=.... AND 其它字段=....
数据库的更新如果真的这么简单就好了。不要忘记,现有的离线数据访问模式虽然提高了数据库的使用效率,但也带来了一系列的问题,其中最重要的一条就是并发操作(解决更新冲突问题)。并发操作的表现形式包括丢失的修改、不可重复读、读脏数据等(数据库原理里面有详细的说明以及解决对策),可到了代码自动生成这里,似乎这些并发问题根本不会出现。
就拿丢失的修改来说:
T1、甲售票员读出机票余额 16,存入本地缓存;
T2、乙售票员读出机票余额 16,也存入本地缓存;
T3、甲卖出一张,更新数据库 16-1 =15;
T4、乙也卖出一张,更新数据库 16-1 =15。
这时候发现数据库中有15张票,而实际卖出了2张票。
为了检测出并发异常,应当在更新时检测是否有数据库被其它人更改过。实现手段就是在UPDATE命令的WHERE子句中增加条件,判断数据是否还是当时读入数据时的数据。比如上面的问题在更新时应当写成:
UPDATE ... SET Ticket=15 WHERE 关键字段=... AND Ticket=16
其中后面的Ticket=16就是用来保护并发冲突的。16是当时读入数据的原始值。
而在CNBLOGS中我看到的代码自动生成工具,没有人考虑这些更新问题,也没有人考虑保留原始数据,而是将这些问题一并推给了DataSet对象。不知道用这些工具生成的代码是否真可以使用?
Visual FoxPro很早就把更新中WHERE短语生成策略考虑进来了。在VFP视图更新策略中,VFP(实际上Delphi也提供了类似的功能)提供了以下几种WHERE短语生成策略:
- 关键字
- 关键字和可更新字段
- 关键字和已更新字段
- 时间戳
虽然不能涵盖所有策略,但不失为一种好的解决方案。
三、更新成功后的后续操作怎么办?
如果再退一步,抛开一、二两个问题,更新真的可以成功。那么看下面的例子:
有张表有三个字段:学号、姓名、年龄。
一个人更新了年龄后,我更新了姓名(使用UPDATE Student SET Name = "aaa" WHERE id = 1 )。由于使用了离线数据库访问方式,这时候我不知道年龄也变了。正常情况下,在更新完成后要将数据库中的数据与目前缓冲中的数据库做一个同步,也就是说重新读入一次数据(在不同的应用系统中,这一步可能不是必须的)。但在自动生成代码的工具中没有人提供这种选项。
总结
上面就是我对代码自动生成工具的一些看法。随时欢迎大家讨论、批评、指正。