接觸LLBL Gen Pro 對象關系映射框架后 前途變的一片光明


時間回到2010年,那時候還是熟悉代碼生成+基礎框架這種模式,基本的開發思路是通過代碼生成器生成實體,再生成接口與實現類,最后拖拉控件,寫界面數據綁定代碼。基本上就是動軟代碼生成器給出的模式,或是微軟的Repository Factory模式的實踐,迷戀於微軟的Enterprise Libray,這個框架是從Application Block演化而來。我也是算是.NET技術推廣以來,第一批學習.NET技術的開發人員。

一直在尋找一種界面與邏輯分離的技術,也沒有思路,上面代碼生成造成的結果是邏輯代碼分布在系統的各個地方,改一個字段或是增加字段都需要重新生成一次,給系統的穩定性帶來困擾。用《企業應用架構模式》中的一種模式總結,就是事務腳本(Transaction Script),不過這種模式好理解,也沒有復雜的技術堆棧,通過對這種模式的掌握,由.NET學習者變成熟練的.NET代碼工人。

第一次看到LLBL Gen Pro,它長成這個樣子:

clip_image002

LLBL Gen Pro 2.5/2.6是它發展歷史上很經典的一個版本,查詢接口穩定成熟,遇到問題了去tinyform上發個帖子,過一會就有專業的人員響應回復。經過大半年的學習,熟悉了這個ORM框架的用法,開始高級一點的定制開發,它的模板編輯器如下面的圖所示:

clip_image002[5]

LLBL Gen Pro從3.x開始,把原來二進制的項目文件lgp改成Xml格式的文件llblgenproj。這是一個很重要的變化,

因為數據庫屬性最終映射的實體屬性可以在設計器中修改,所以必須讀取LLBL Gen Pro的項目文件才能確定最終映射的屬性名稱。 我的輔助開發工具中也依賴於llblgenproj項目文件的這個特性,在LLBL Gen Pro 2.x時代這是不可能的。

 

當時我的同事做了一個基於ORM的代碼生成工具,用於生成實體接口與實現代碼,解釋如下:

數據庫表SalesOrder –> 實體SalesOrderEntity  -> 接口ISalesOrderManager –> 接口實現SalesOrderManager

后面兩個步驟就是需要做的工作,同事設計的工具的原型如下:

clip_image002[7]

有接近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系統成型,參考下面的視圖:

clip_image002[9]

這個項目中,抽象出了三個公共基類庫,公共方法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  給實體增加業務邏輯代碼,界面與邏輯分離。這是要手寫代碼的地方,寫業務邏輯,包含計算邏輯與驗證邏輯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM