Rafy 領域實體框架示例(1) - 轉換傳統三層應用程式

BloodyAngel發表於2013-10-22

Rafy 領域實體框架釋出後,雖然有幫助文件,許多朋友還是反映學習起來比較複雜,希望能開發一個示例程式,展示如何使用 Rafy 領域實體框架所以,本文通過使用 Rafy 領域實體框架來改造一個傳統的三層架構應用程式——“服裝進銷存”系統,來講解如何使用 Rafy 領域實體框架進行資料庫應用程式的快速開發,以及替換為使用 Rafy 框架後帶來的一些新功能。

完整示例包下載地址:http://pan.baidu.com/s/1AB9TL,其中包含本次改造前、改造後的原始碼,以及轉換說明文件。(下載該示例程式碼後,只需要修改 app.config 檔案中的連線字串中的使用者名稱和密碼後,就可以直接執行示例,程式即會自動建立資料庫併成功執行!還沒有下載 Rafy 框架的同學,可以在《Rafy 框架釋出》文中下載完整安裝包。)

 

接下來,將說明如何進行程式碼轉換,使用 Rafy 來開發一個典型的資料庫應用程式。(以下內容拷貝自示例包中的 PDF 文件。)

 

原程式說明


考慮到要更好地演示如何使用 Rafy 框架來開發一個傳統的管理系統,決定挑選一個開源系統進行改造,而這個系統應該是簡單、常見的三層架構,這種系統大家都比較熟悉,這樣就可以更加快速的理解框架的使用了。

在開源網站上挑選了很久,免費的三層架構系統挺多,但是許多系統並不規範。一些系統雖然寫著使用三層架構,但是金玉其外,敗絮其中,看上去非常正式的系統,一開啟原始碼,介面層程式碼中就可以看到直接編寫的 SQL 語句。最終,我選用了《知名度服裝進銷存管理系統》,原始碼下載地址:http://www.51aspx.com/Code/ZhiMingDuClothesSys。該系統三層間的呼叫比較嚴格,業務也非常簡單。

系統功能描述:

  • 人員:操作員管理,供應商管理,顧客管理
  • 庫存:庫存管理,庫存檔點
  • 銷售:服裝銷售,服裝退貨
  • 服裝:服裝類別,服裝登記
  • 銷售:銷售統計,利潤統計

技術特點:使用了三層架構設計程式,更換底層資料庫型別方便。系統使用了 SqlLite 作為資料庫,下載後可以直接執行。

介面截圖 :

imageimageimage

 

程式轉換


轉換方案

原系統是簡單的三層架構:

image

而我們會使用 Rafy 推薦的架構,來改造整個系統:

image

對於一個依賴關係較為嚴格的三層系統來說,要使用 Rafy 框架來替換其中的資料訪問層、業務邏輯層以及介面查詢的功能,是比較簡單的。本次轉換,我按照以下步驟進行:

1. 理解系統需求,使用 UML 畫出領域實體間的關係。

2. 新增 Rafy 領域實體專案。

3. 根據實體的關係圖,在實體程式集中新增對應的實體及實體間的關係;同時也可以把舊錶中的屬性新增到實體中。

4. 把所有跨多表的業務邏輯轉換為領域服務。

5. 依次把歷史的實體刪除,轉而使用新的 Rafy 實體,以及其對應的實體查詢、領域服務。

接下來,就正式對程式碼進行轉換:

 

1. 使用 UML 進行領域建模

經分析,原系統擁有以下領域模型:

User:使用者;

Company:供應商;

Customer:顧客;

GoodCategory:商品類別;

Good:商品(服裝);

Stock:入庫資訊;

Regood:返庫資訊;

Bill 及 Sell:銷售單據及銷售明細。

它們的關係如下:

image

(雖然原系統中一些實體的名稱取得並不合理,但是為了簡化系統的轉換工作,新系統中的類命名還是保持和原系統一致。)

關於哪些關係應該使用組合關係來進行設計,大家可以檢視 Rafy 使用者嚮導文件中的“領域實體框架/領域實體/實體關係”章節。

 

2. 升級 .NET 版本

在開始轉換程式碼前,由於原程式使用的是 .NET 2.0 的執行時,而 Rafy 要求必須使用 .NET 4.0。所以我們需要把解決方案中的每個專案都轉換為 .NET 4.0 版本。

需要注意的是,由於原程式使用的 SqlLite 只支援 2.0 版本。同時,需要把 SqlLite 替換為 .NET 4.0 的版本。

 

3. 新增 Rafy 領域實體專案

在解決方案中新增一個 Rafy 領域實體專案,命名為 CS(為原系統名 ClothesSys 的縮寫)。

image

image

點選確定後生成的專案如下:

image

接下來,我們將會在這專案中新增領域實體與領域服務,來替換原程式中除介面專案以外的其它幾個專案:

image

 

4. 實體轉換

接下來,依次把歷史的實體刪除,轉而使用新的 Rafy 實體。這一步,需要按照依賴關係,儘量先轉換不依賴其他實體的實體,即按照以下順序進行轉換:User、Company、Customer、GoodCategory、Good、Stock、Regood、Bill 和 Sell。由於 Bill 和 Sell 有強聚合關係,所以放到最後一起轉換。

(在變更每一個實體時,原始碼中所有的 BLL 查詢,都需要在實體倉庫中編寫相關的程式碼支援;業務邏輯則需要編寫領域服務)

實體的轉換分為以下幾類:

  • 無關係實體的轉換
  • 有關係實體的轉換
  • 組合實體的轉換

 

5. 簡單實體的轉換

簡單實體沒有複雜的關係,只是對映一個簡單的表。在轉換為 Rafy 實體時,只需要把表中的所有屬性都新增到實體中就可以了。在編寫時,需要注意的是:

標識

轉換為 Rafy 實體後,所有的實體都統一繼承自 Entity 型別。Entity 類宣告瞭 int 型別的 Id 屬性作為所有實體的標識屬性,這個屬性會在資料庫中生成一個自增長的主鍵列。

舊實體類上的所有主鍵列、唯一列,在新實體中都變成了普通列。實體屬性的唯一性驗證,需要放到實體之上的業務邏輯層中來完成。

屬性

原實體的所有屬性,在 Rafy 實體中都使用屬性程式碼段來生成同名的實體屬性程式碼即可。

 

6. BLL、DAL 層程式碼轉換

  • 轉換查詢資料的程式碼

在原始碼中 BLL、DAL 兩層中,都有許多的查詢方法。這些方法都需要轉換為新程式碼中對應實體的實體倉庫中的查詢方法。例如,原程式中通過顧客編號查詢顧客的查詢方法:

   1:  public static Customer GetCustomerById(string id)
   2:  {
   3:      Customer ct = null;
   4:      SQLiteParameter[] sqlparams = new SQLiteParameter[]{
   5:          new SQLiteParameter("@customerId",id)
   6:      };
   7:      SQLiteDataReader sdr = SQLiteHelper.GetReader("select * from T_customer where customerid=@customerId", sqlparams);
   8:      if (sdr.Read())
   9:      {
  10:          ct = new Customer();
  11:          ct.CustomerID = sdr.GetValue(0).ToString();
  12:          ct.CustomerName = sdr.GetValue(1).ToString();
  13:          ct.Socre = Convert.ToInt32(sdr.GetValue(2));
  14:          ct.Remark = sdr.GetValue(3).ToString();
  15:      }
  16:      sdr.Close();
  17:   
  18:      return ct;           
  19:  }

 

需要轉換為 Rafy 實體倉庫中的新方法:

   1:  public Customer GetByCustomerId(string id)
   2:  {
   3:      return this.FetchFirst(new PropertiesMatchCriteria
   4:      {
   5:          { Customer.CustomerIDProperty, id },
   6:      });
   7:  }
  • 轉換業務邏輯程式碼

BLL、DAL 中,除了查詢方法以外,剩下的還有一些簡單對實體的增、刪、改操作。這些操作已經在實體倉庫基類中實現了,所以可以不用轉換。

除了簡單的 CRUD 操作外,系統中還有一些需要同時操作多個表的事務操作,原系統把這些業務邏輯都寫到了資料層中。例如 ReGoodService.ReGoodSumbit 方法:

   1:  public static bool ReGoodSumbit(ReGoods regoods)
   2:  {
   3:      SQLiteParameter[] sqlparams = new SQLiteParameter[]{
   4:          new SQLiteParameter("@regoodsId",regoods.ReGoodsID),
   5:          new SQLiteParameter("@regoodsNum",regoods.ReGoodsNum),
   6:          new SQLiteParameter("@regoodsPrice",regoods.ReGoodsPrice),
   7:          new SQLiteParameter("@reNeedPay",regoods.ReNeedPay),
   8:          new SQLiteParameter("@reRealpay",regoods.ReRealPay),
   9:          new SQLiteParameter("@regoodsResult",regoods.ReGoodResult),
  10:          new SQLiteParameter("@userId",regoods.UserId),
  11:          new SQLiteParameter("@sellId",regoods.SellId),
  12:          new SQLiteParameter("@regoodsTime",regoods.RegoodsTime.ToString("yyyy-MM-dd HH:mm:ss"))
  13:      };
  14:   
  15:      string sql = "insert into T_regoods(regoodsid,regoodsNum,regoodsPrice,reNeedPay,reRealPay,regoodsResult,userId,regoodsTime,sellId) ";
  16:      sql+=" values(@regoodsid,@regoodsNum,@regoodsPrice,@reNeedPay,@reRealPay,@regoodsResult,@userId,@regoodsTime,@sellId)";
  17:      try
  18:      {
  19:          SQLiteConnection con = SQLiteHelper.GetConnection();
  20:          SQLiteTransaction trans = con.BeginTransaction();
  21:   
  22:          SQLiteHelper.ExecuteNonQuery(trans, sql, sqlparams);
  23:   
  24:          CustomerService.DecreaseCustomerScore(trans, regoods);
  25:          StockService.IncreaseStocskNumByGoodId(regoods.ReGoodsID, regoods.ReGoodsNum);
  26:   
  27:          trans.Commit();
  28:          return true;
  29:      }
  30:      catch (Exception ex)
  31:      {
  32:          return false;
  33:          throw ex;
  34:      }
  35:  }

可以看到,這段程式碼中,不但有業務邏輯的控制,還有資料庫連線的控制,事務的控制,Sql 語句的拼裝。顯得非常混亂。而這種業務邏輯,在 Rafy 框架中,可以使用領域服務來實現。例如,剛才的邏輯,被替換為以下程式碼:

   1:  [Serializable]
   2:  public class SubmitRegoodService : Service
   3:  {
   4:      public Regood Regood { get; set; }
   5:   
   6:      protected override Result ExecuteTransaction()
   7:      {
   8:          if (Regood == null) throw new ArgumentNullException("Regood");
   9:          if (!Regood.IsNew) throw new ArgumentNullException("Regood");
  10:   
  11:          RF.Save(Regood);
  12:   
  13:          //修改庫存
  14:          Regood.Good.StockSum += Regood.ReGoodsNum;
  15:          RF.Save(Regood.Good);
  16:   
  17:          //減少客戶的積分
  18:          var ct = Regood.Sell.Customer;
  19:          if (ct != null)
  20:          {
  21:              ct.Score -= Regood.ReRealPay;
  22:              RF.Save(ct);
  23:          }
  24:   
  25:          return true;
  26:      }
  27:  }

可以看到,使用 Rafy 領域服務來實現後有以下好處:

  • 整個程式碼非常直接地表現了業務邏輯,沒有一點多餘的程式碼。
  • 使用了引用實體屬性的懶載入功能,使得程式可以直接使用如 Regood.Sell.Customer 這樣的強引用關係。
  • 方便通用程式碼的封裝。例如,事務的控制已經交給了服務基類來處理。
  • 業務邏輯獨立封裝。每一個單獨的業務都是一個服務物件,方便管理。為 SOA 提供了架構基礎。
  • 同時,使用領域服務還可以方便地直接使用 C/S 架構來部署。

 

7. 外來鍵關係的轉換

舊錶中的外來鍵引用關係,除了 Bill(銷售單) 與 Sell(銷售明細) 兩個表間的關係,在設計 UML 時,都設計為實體間的引用關係。先區分清楚引用關係的可空性,然後就可以在相應實體中編寫引用實體屬性了。例如,Stock(庫存)到 Good(商品)的關係,被轉換為下面這個引用實體屬性:

   1:  public static readonly RefIdProperty GoodIdProperty =
   2:      P<Stock>.RegisterRefId(e => e.GoodId, ReferenceType.Normal);
   3:  public int GoodId
   4:  {
   5:      get { return this.GetRefId(GoodIdProperty); }
   6:      set { this.SetRefId(GoodIdProperty, value); }
   7:  }
   8:  public static readonly RefEntityProperty<Good> GoodProperty =
   9:      P<Stock>.RegisterRef(e => e.Good, GoodIdProperty);
  10:  public Good Good
  11:  {
  12:      get { return this.GetRefEntity(GoodProperty); }
  13:      set { this.SetRefEntity(GoodProperty, value); }
  14:  }

 

8. 使用組合實體

Bill 和 Sell 分別表示銷售訂單、銷售明細項。設計為組合實體後,在使用時,可以直接以組合實體的方式構造、儲存、更新、刪除,非常方便。

例如,在新增銷售資訊介面中的程式碼如下:

   1:  var bill = new Bill();
   2:  bill.UserId = userId;
   3:  bill.BillTime = now;
   4:   
   5:  foreach (Sell sell in list)
   6:  {
   7:      sell.CustomerId = customerId;
   8:      sell.SellTime = now;
   9:   
  10:      bill.SellList.Add(sell);
  11:  }
  12:   
  13:  var svc = new AddBillService { Bill = bill };
  14:  svc.Invoke();
  15:  if (svc.Result)
  16:  {
  17:      MessageBox.Show("提交訂單成功!", "提示");
  18:      this.Close();
  19:      ucGSM.bindDgvSellRecordToday();
  20:  }

 

先構造了組合物件,然後提交給領域服務 AddBillService以執行新增銷售資訊邏輯。此服務程式碼如下:

   1:  [Serializable]
   2:  public class AddBillService : Service
   3:  {
   4:      public Bill Bill { get; set; }
   5:   
   6:      protected override Result ExecuteTransaction()
   7:      {
   8:          if (Bill == null) throw new ArgumentNullException("Bill");
   9:          if (!Bill.IsNew) throw new ArgumentException("Bill");
  10:   
  11:          //呼叫倉庫儲存整個銷售單
  12:          var repo = RF.Concrete<BillRepository>();
  13:          repo.Save(Bill);
  14:   
  15:          //修改庫存
  16:          foreach (var sell in Bill.SellList)
  17:          {
  18:              sell.Good.StockSum -= sell.SellNum;
  19:              RF.Save(sell.Good);
  20:          }
  21:   
  22:          //新增客戶的積分
  23:          var ctRepo = RF.Concrete<CustomerRepository>();
  24:          foreach (var sell in Bill.SellList)
  25:          {
  26:              var ct = sell.Customer;
  27:              if (ct != null)
  28:              {
  29:                  ct.Score += sell.RealPay;
  30:                  ctRepo.Save(ct);
  31:              }
  32:          }
  33:   
  34:          return true;
  35:      }
  36:  }

 

轉換後實體專案結構


待每一個實體修改並替換完畢後,再刪除原來的傳統三層專案後,解決方案中就只剩下了兩個專案,一個 Rafy 領域實體專案“CS”;一個原程式中的介面層專案 “ClothesSys”。

image

截止到現在,已經完成了 ClothesSys 的完整轉換。轉換後的系統已經可以正常的執行,實現了與原系統一致的功能。

下載該示例程式碼後,只需要修改 app.config 檔案中的連線字串中的使用者名稱和密碼後,就可以直接執行示例,程式即會自動建立資料庫併成功執行!

 

 

下一篇,將展示轉換為使用 Rafy 實體框架後,帶來的新功能。

相關文章