接触LLBL Gen Pro 对象关系映射框架后 前途变的一片光明
时间回到2010年,那时候还是熟悉代码生成+基础框架这种模式,基本的开发思路是通过代码生成器生成实体,再生成接口与实现类,最后拖拉控件,写界面数据绑定代码。基本上就是动软代码生成器给出的模式,或是微软的Repository Factory模式的实践,迷恋于微软的Enterprise Libray,这个框架是从Application Block演化而来。我也是算是.NET技术推广以来,第一批学习.NET技术的开发人员。
一直在寻找一种界面与逻辑分离的技术,也没有思路,上面代码生成造成的结果是逻辑代码分布在系统的各个地方,改一个字段或是增加字段都需要重新生成一次,给系统的稳定性带来困扰。用《企业应用架构模式》中的一种模式总结,就是事务脚本(Transaction Script),不过这种模式好理解,也没有复杂的技术堆栈,通过对这种模式的掌握,由.NET学习者变成熟练的.NET代码工人。
第一次看到LLBL Gen Pro,它长成这个样子:
LLBL Gen Pro 2.5/2.6是它发展历史上很经典的一个版本,查询接口稳定成熟,遇到问题了去tinyform上发个帖子,过一会就有专业的人员响应回复。经过大半年的学习,熟悉了这个ORM框架的用法,开始高级一点的定制开发,它的模板编辑器如下面的图所示:
LLBL Gen Pro从3.x开始,把原来二进制的项目文件lgp改成Xml格式的文件llblgenproj。这是一个很重要的变化,
因为数据库属性最终映射的实体属性可以在设计器中修改,所以必须读取LLBL Gen Pro的项目文件才能确定最终映射的属性名称。 我的辅助开发工具中也依赖于llblgenproj项目文件的这个特性,在LLBL Gen Pro 2.x时代这是不可能的。
当时我的同事做了一个基于ORM的代码生成工具,用于生成实体接口与实现代码,解释如下:
数据库表SalesOrder –> 实体SalesOrderEntity -> 接口ISalesOrderManager –> 接口实现SalesOrderManager
后面两个步骤就是需要做的工作,同事设计的工具的原型如下:
有接近3年的时间,我都迷恋于这个工具产生的接口与实现类代码。直到后来有客户不断提出对接口与实现中细节的修改,我慢慢无法忍受用.NET代码写代码生成器,还要编译的苦恼。当时同事们都极力推荐模板生成技术,于是用Code Smith写下了模板代码,一直延续到今天。分享一下Code Smith生成接口的代码:
<%@ CodeTemplate Language="C#" TargetLanguage="C#" Src="" Inherits="" Debug="True" Description="Template description here." %> <%@ Property Name="EntityPicker" Type="ISL.Extension.EntityPickerProperty" Optional="False" Category="Project" Description="This property uses a custom modal dialog editor." %> <%@ Property Name="AssemblyFile" Type="System.String" Default="" Optional="False" Category="Project" Description="" Editor="System.Windows.Forms.Design.FileNameEditor"%> <%@ Assembly Name="System.Data" %> <%@ Import Namespace="System.Data" %> <%@ Assembly Name="ISL.Empower.Extension" %> <%@ Import Namespace="ISL.Extension" %> <%@ Import Namespace="System.Collections.Generic" %> <%@ Assembly Name="SD.LLBLGen.Pro.ORMSupportClasses.NET20" %> <%@ Import Namespace="SD.LLBLGen.Pro.ORMSupportClasses" %> <script runat="template"> public string EntityName { get { return EntityPicker.EntityName; } } public string ShortEntityName { get { return EntityName.Substring(0,EntityName.Length-6); } } public string FullEntityName { get { return string.Format("{0}.EntityClasses.{1}", BusinessLogicProjectName, EntityName); } } private string _businessLogicProjectName; public string BusinessLogicProjectName { get { if(string.IsNullOrWhiteSpace(_businessLogicProjectName)) _businessLogicProjectName=EntityClassHelper.PrefixProjectName(AssemblyFile); return _businessLogicProjectName; } } public string EntityParamerList { get { IEntity2 policy = EntityClassHelper.GetEntityObject(AssemblyFile, EntityPicker.EntityName); string parm = string.Empty; List<string> parms=new List<string>(); foreach (IEntityField2 field in policy.PrimaryKeyFields) { parm = string.Format("{0} {1}", field.DataType.Name, field.Name); parms.Add(parm); } return string.Join(",", parms.ToArray()); } } public string EntityLowercaseName { get { return EntityPicker.EntityName.Substring(0, 1).ToLower() + EntityPicker.EntityName.Substring(1); } } </script> using System; using System.Collections.Generic; using System.Data; using System.Text; using SD.LLBLGen.Pro.ORMSupportClasses; using <%=BusinessLogicProjectName%>; using <%=BusinessLogicProjectName%>.FactoryClasses; using <%=BusinessLogicProjectName%>.EntityClasses; using <%=BusinessLogicProjectName%>.HelperClasses; using <%=BusinessLogicProjectName%>.InterfaceClasses; using <%=BusinessLogicProjectName%>.DatabaseSpecific; namespace <%=BusinessLogicProjectName%>.InterfaceClasses { public interface I<%=ShortEntityName%>Manager { <%=EntityName%> Get<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>); <%=EntityName%> Get<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>,IPrefetchPath2 prefetchPath); <%=EntityName%> Get<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>,IPrefetchPath2 prefetchPath,ExcludeIncludeFieldsList fieldList); EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket); EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket,ISortExpression sortExpression); EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket,ISortExpression sortExpression, IPrefetchPath2 prefetchPath); EntityCollection Get<%=ShortEntityName%>Collection(Guid sessionId,IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList); <%=EntityName%> Save<%=ShortEntityName%>(Guid sessionId,<%=EntityName%> <%=EntityLowercaseName%>); <%=EntityName%> Save<%=ShortEntityName%>(Guid sessionId,<%=EntityName%> <%=EntityLowercaseName%> ,EntityCollection entitiesToDelete); <%=EntityName%> Save<%=ShortEntityName%>(Guid sessionId,<%=EntityName%> <%=EntityLowercaseName%>, EntityCollection entitiesToDelete, string seriesCode); void Delete<%=ShortEntityName%>(Guid sessionId,<%=EntityName%> <%=EntityLowercaseName%>); bool Is<%=ShortEntityName%>Exist(Guid sessionId,<%=EntityParamerList %>); bool Is<%=ShortEntityName%>Exist(Guid sessionId,IRelationPredicateBucket filterBucket); int Get<%=ShortEntityName%>Count(Guid sessionId,IRelationPredicateBucket filterBucket); <%=EntityName%> Clone<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>); void Post<%=ShortEntityName%>(Guid sessionId,<%=EntityParamerList %>); void Post<%=ShortEntityName%>(Guid sessionId,<%=EntityName%> <%=EntityLowercaseName%>); } }
再后来微软推出了T4模板代码生成工具,曾经有一段时间想把Code Smith转换成T4的模板,Code Smith 5.x不支持.NET3.5,一些.NET类库写的扩展方法,Code Smith模板不能用,这是想转成T4代码模板的原因。然而在网上找一个带智能提示,语法高亮的T4模板编辑器相当困难,在国外找到一个也是试用版,国内也没有破解版,再后来就没有完全没有动力去折腾了。Code Smith 6.x完全支持.NET 3.5,一直延续用到今天。
借助于LLBL Gen Pro,再加上以前积累的一些公共代码类库,一套原始的ERP系统成型,参考下面的视图:
这个项目中,抽象出了三个公共基类库,公共方法Common,公共控件WinUI,公共程序Core。后来硬盘丢失,实在找不到这个项目的源代码,不过设计思路与项目的架构已经了然于胸。
到2012年的时候,接触到Infragistics界面控件包,它几乎重写了整个WinForms的控件,提供的属性非常丰富。当时公司购买了这套控件的许可,可查看到控件的所有源代码。不过大部分时间都没有去看源代码,只有遇到不可理解的错误时,才会跟踪进入源代码查看参数传递是否合理正确。
有了实体和支持强类型对象的控件,这两者的结合,深远的影响了后来的程序设计生涯。虽然现有偶尔也会用DataTable,但大面积使用的开发模式仍旧是使用实体+数据绑定。
.NET数据绑定是需要深入学习的另一个领域,有了数据绑定,下面代码可以省略:
//Get value from control string refNo=txtRefNo.Text; //set value to control txtRefNo.Text="SO201507190001";
只需要将实体绑定给BindingSource控件,整个界面上的控件就全都有了值,不用上面的代码逐个赋值。
protected override void BindControls(EntityBase2 entity) { base.BindControls(entity); InventoryMovementEntity inventoryMovement = (InventoryMovementEntity)entity; inventoryMovementBindingSource.DataSource = inventoryMovement; }
对于WinForms开发,大量的取值和赋值操作代码都省略了,减少了代码,提高系统可维护性。
基本上到这里,我已经可以独立开发系统,系统的各个部件都可以处理好,我的开发步骤如下:
1 设计数据库表。找过很多case工具以辅助生成SQL Server数据表,最后还是回归SQL Server Management Studio,这是最好用的最简洁的工具,也方便与同事交流。当两个人用的数据库设计工具不同,而发生一些微小的错误或差异时,常常会令人抓狂。
2 LLBL Gen Pro生成实体,设置实体间关系。基本上就是连接到数据库,刷新实体,生成或更新实体文件。
3 生成实体读写的接口与实现类。借用Code Smith模板,效率高
4 拖拉界面,绑定数据源控件。即使没有学过编程,也可以经过短暂的培训快速上手开发界面。
5 给实体增加业务逻辑代码,界面与逻辑分离。这是要手写代码的地方,写业务逻辑,包含计算逻辑与验证逻辑。