解析大型.NET ERP系統 界面與邏輯分離

Windows Forms程序實現界面與邏輯分離的關鍵是數據綁定技術(Data Binding),這與微軟推出的ASP.NET MVC的原理相同,分離業務代碼與界面層,提升系統的可維護性。html

數據綁定 Data Binding

數據綁定技術的主要內容:數據源(Data Source),控件(Control),綁定方式(Binding)。經過綁定控件,將數據源中的屬性綁定到界面控件中,並能夠實現雙向的數據綁定。當界面控件中的值發生改變時,能夠經過數據綁定控件,更新控件綁定的對象,當經過代碼直接改變對象值後,數據綁定控件能夠將值更新到它所綁定的界面控件中。數據庫

ERP系統選擇LLBL Gen ORM框架做爲數據訪問技術基礎,數據源爲實體類型。主要綁定如下幾種類型:app

IEntity  數據庫表的實體映射,每一個屬性。綁定時,控件的類型與實體的屬性類型嚴格匹配與驗證。框架

/// <summary> The TotLdiscAmt property of the Entity PurchaseOrder<br/>
/// Mapped on  table field: "PUORDH"."TOT_LDISC_AMT"<br/>
/// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/>
/// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false<br/><br/>
/// ReadOnly: <br/></summary>
/// <remarks>Mapped on  table field: "PUORDH"."TOT_LDISC_AMT"<br/>
/// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/>
/// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false</remarks>
public virtual System.Decimal  SalesAmount
{
    get { return (System.Decimal)GetValue((int)PurchaseOrderFieldIndex.TotLdiscAmt, true); }
    set    { SetValue((int)PurchaseOrderFieldIndex.TotLdiscAmt, value); }
}

/// <summary> The TotAtaxAmt property of the Entity PurchaseOrder<br/>
/// Mapped on  table field: "PUORDH"."TOT_ATAX_AMT"<br/>
/// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/>
/// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false<br/><br/>
/// ReadOnly: <br/></summary>
/// <remarks>Mapped on  table field: "PUORDH"."TOT_ATAX_AMT"<br/>
/// Table field type characteristics (type, precision, scale, length): Decimal, 16, 2, 0<br/>
/// Table field behavior characteristics (is nullable, is PK, is identity): true, false, false</remarks>
public virtual System.String CustomerNo
{
    get { return (System.Decimal)GetValue((int)PurchaseOrderFieldIndex.TotAtaxAmt, true); }
    set    { SetValue((int)PurchaseOrderFieldIndex.TotAtaxAmt, value); }
}

上面的代碼例子定義了二個屬性:數值類型的銷售金額,字符串類型的客戶編號。在作數據綁定設計時,須要分別選擇NumbericEditor和TextEditor。ide

 

控件Control  簡單的數據綁定,好比TextEditor,NumbericEditor,CheckedEdidtor只綁定一個屬性,複雜的數據定好比Grid綁定一個對象的多個屬性,根據實體的屬性自動設定綁定控件的屬性。fetch

數據源控件BindingSource  負責將控件的屬性和對象的屬性關聯起來。對象的屬性值會被自動傳遞個控件的屬性,而控件的屬性值更改後也會直接傳回對象的屬性(雙向綁定)。
簡單綁定綁定  下面的代碼的含義是將客戶編號綁定到txtCustomerNo控件的Text屬性。this

CustomerEntity customer=......
txtCustomerNo.DataBindings.Add("Text", customer, "CustomerNo");  
 

複雜數據綁定 下面的代碼將客戶集合(EntityCollection)綁定到網格控件。spa

EntityCollection<CustomerEntity> customers=......
customerBindingSource.DataSource = customers;
gridCustomer.SetDataBinding(customerBindingSource, "Customer", true, true);
 

下拉選擇框(Combox)的數據綁定,下面是綁定到枚舉類型的例子:設計

comboDepartment.InitializeValueFromEnum<CustomerType>();

代碼中將枚舉的值反射出來,建立爲ValueList綁定到下拉框中。雙向綁定

 

業務邏輯 Business Logic

從ERP的技術層面考慮,ERP包含如下四種邏輯:

1 自動賦值邏輯。在採購訂單輸入窗體中,輸入供應商編號,自動帶入供應商名稱,供應商付款貨幣和付款條款的值到當前採購訂單單據中。

//PurchaseOrderEntity.cs
private void OnChangeVendorNo(string originalValue)
{
         if (string.CompareOrdinal(this.VendorNo, originalValue) == 0)
              return;

          IVendorManager vendorMan = ClientProxyFactory.CreateProxyInstance<IVendorManager>();
          VendorEntity vendor = vendorMan.GetValidVendor(Shared.CurrentUserSessionId, this.VendorNo);

          this.VendorName = vendor.VendorName.;
          this.PayTerms = vendor.PayTerms;
          this.PayCurrency = vendor.PayCurrency;
}

2 計算邏輯。輸入單價和數量後,自動計算出物料金額金額,再輸入折扣率後,計算出折扣金額和應付款金額。

//PurchaseOrderEntity.cs
private void OnChangeQty(decimal? originalValue)
{
        if (!originalValue.HasValue || this.Qty != originalValue.Value)
        {
              this.Amount=this.Qty * this.UnitPrice;
         }
}

3 數據驗證。出倉時,庫存餘額不能是負數。付款金額必須大於等於訂單金額時,訂單才能完成付款。

//Shipment.cs
public bool ValidateQtyShiped(ShipmentEntity shipment, decimal value)
{
   IInventoryBalanceManager  balanceManager = CreateProxyInstance<IInventoryBalanceManager>();
   decimal  qtyRemaining =  balanceManager.GetItemBalance(shipment.ItemNo);
   if (qtyRemaining < 0| qtyRemaining<value )
          throw new FieldValidationException("Negative item balance detected");

   return true;
}

4 業務關聯。送貨單要依據銷售訂單的訂單數量爲依據來送貨,採購驗貨的結果中,合格和不合格數量要等於採購訂單要驗貨的數量。

//PurchaseInspectionEntity.cs
private void OnChangeGrnNo(string originalValue)
{
      if (Shared.StringCompare(this.GrnNo, originalValue) == 0) 
                return;

      IPrefetchPath2 prefetchPath = new PrefetchPath2(EntityType.GrnEntity);
      prefetchPath.Add(GrnEntity.PrefetchPathGrnOrderDetails);

      IGrnManager grnManager =CreateProxyInstance<IGrnManager>();
      GrnEntity grn = grnManager.GetGrn(Shared.CurrentUserSessionId, this.GrnNo, prefetchPath);

      //待檢驗數量= 訂單數量 - 已檢驗數量
      this.Qty=grn.QtyOrder -  grn.QtyInspected;
}
 

界面設計 Interface Design

幾乎全部的界面綁定業務實體到控件中的方法都一致,參考下面的方法:

protected override void BindControls(EntityBase2 entity)
{
       base.BindControls(entity);
       this.purchaseOrderBindingSource.DataSource = entity;
}

界面中讀取數據的方法LoadData,讀取數據後綁定到數據源控件BindingSource控件中。

//PurchaseOrderEntry.cs 
protected override EntityBase2 LoadData(Dictionary<string, string> refNo)
{      
    string orderNo;
    if (refNo.TryGetValue("OrderNo", out orderNo))
    {
          PurchaseOrderEntity result = _purchaseOrderManager.GetPurchaseOrder(orderNo);
          this._purchaseOrder=result;
    }

    return _purchaseOrder;
}
 

以上是界面層中數據綁定的兩個核心方法,讀取數據並將數據綁定到界面控件中。注意到方法是經過參數實體EntityBase2操做的,因此它是通用的方法實現,被框架調用實現界面與邏輯分離。

相關文章
相關標籤/搜索