時間回到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 給實體增加業務邏輯代碼,界面與邏輯分離。這是要手寫代碼的地方,寫業務邏輯,包含計算邏輯與驗證邏輯。