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 作為數據庫,下載后可以直接運行。
界面截圖 :
程序轉換
轉換方案
原系統是簡單的三層架構:
而我們會使用 Rafy 推薦的架構,來改造整個系統:
對於一個依賴關系較為嚴格的三層系統來說,要使用 Rafy 框架來替換其中的數據訪問層、業務邏輯層以及界面查詢的功能,是比較簡單的。本次轉換,我按照以下步驟進行:
1. 理解系統需求,使用 UML 畫出領域實體間的關系。
2. 添加 Rafy 領域實體項目。
3. 根據實體的關系圖,在實體程序集中添加對應的實體及實體間的關系;同時也可以把舊表中的屬性添加到實體中。
4. 把所有跨多表的業務邏輯轉換為領域服務。
5. 依次把歷史的實體刪除,轉而使用新的 Rafy 實體,以及其對應的實體查詢、領域服務。
接下來,就正式對代碼進行轉換:
1. 使用 UML 進行領域建模
經分析,原系統擁有以下領域模型:
User:用戶;
Company:供應商;
Customer:顧客;
GoodCategory:商品類別;
Good:商品(服裝);
Stock:入庫信息;
Regood:返庫信息;
Bill 及 Sell:銷售單據及銷售明細。
它們的關系如下:
(雖然原系統中一些實體的名稱取得並不合理,但是為了簡化系統的轉換工作,新系統中的類命名還是保持和原系統一致。)
關於哪些關系應該使用組合關系來進行設計,大家可以查看 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 的縮寫)。
點擊確定后生成的項目如下:
接下來,我們將會在這項目中添加領域實體與領域服務,來替換原程序中除界面項目以外的其它幾個項目:
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”。
截止到現在,已經完成了 ClothesSys 的完整轉換。轉換后的系統已經可以正常的運行,實現了與原系統一致的功能。
下載該示例代碼后,只需要修改 app.config 文件中的連接字符串中的用戶名和密碼后,就可以直接運行示例,程序即會自動創建數據庫並成功運行!
下一篇,將展示轉換為使用 Rafy 實體框架后,帶來的新功能。