無論是何種產業,程式設計人員進行系統開發時,皆須謹慎評估與考量適當的程式語言、應用程式所產生的效能,及須注意與防範的問題。資通電腦委外人力主要運用 .Net 跟 java 開發技術。使用物件導向的觀念與方式,透過由『分離組件(Components)』的設置與使用,以降低類別或模組之間的耦合度,不僅能增加靈活度,更能確保未來的擴充性及調修的彈性。
而採用 ASP.Net MVC 5 + Entity Framework 6,佐以 Dependency Injection(DI;相依性注入)以及跟 DI 息息相關的 Inversion of Control (IoC;控制反轉)為架構的購物網站,日前已經陸陸續續通過 Fortify、WebInspect 以及壓力等測試,在程式開發與測試期間遭遇的問題,所幸都能一一克服,以下將與大家分享導入 DI/IoC 的緣由,以及如何避免 EF 重複執行查詢命令。
DI/IoC
首先來看專案背景,與本案的串接網站系統:
- 會員網站:平行開發,結案時間與本案差不多,會用到註冊、登入、計點等功能。開發前期幾乎無法與之交易,最快也只能在連結測試時進行整合。但可以事先拿到各功能的介面(Interface)。
- 銀行匯款:動工時,還不知道是哪家銀行及定案時間?不過接觸洽談的銀行會先行提供API(Application Programming Interface;應用程式介面)。
- 取貨方式:有超商及宅配等二種,但每種方式可搭配數家廠商,分別提供各自的服務 API。
DI/IoC 牽涉物件設計模式範圍廣泛,不妨上網搜尋查閱,在此不再贅述,然而其結果可以歸納出採用 DI/IoC 的目的不外乎寬鬆耦合(loose coupling)及動態繫結(Dynamic Binding),這也是此次導入採用的主因。面對上述不確定因素及複雜的功能進行抽象化、分離介面,逐一針對介面來撰寫具象類別程式。因此,先寫模擬程式(如登入、ATM & 信用卡等),讓整個購物流程更順暢,待相關程式完成後便可進行替換,達成寬鬆耦合的目標。
關於 DI/IoC 的現成簡單範例,可利用 Microsoft Visual Studio 新增 ASP.Net Web Application 專案裡的 Startup 為開端導引的 Startup.Auth,雖然是一個登入授權,其內容以 Manager 結尾命名的類別(Class)都是採用「建構式注入」(Constructor Injection)搭配 Factory、Decorator 等模式,最精彩的是在 ConfigureAuth 中最下方被註解的部分,各協力廠的登入服務通通都是 IAppBuilder 實作。
![OWIN](https://marketing.ares.com.tw/images/2017-06/t1.png)
另一個值得探討的課題是基於 DI 模式時,考量到物件生成、生命週期等因素需要寫相當多的介面,由於有太多 I 起頭的 Interface,造成閱讀程式碼的難度提高,除非逐步執行,否則往往會不知道究竟是在執行哪個物件。
Entity Framework
查詢就免不了 LINQ(Language Integrated Query)及 Lambda 運算式,支援型別就是 IEnumerable<T> 及衍生介面(例如 IQueryable<T>),通常會使用 var 關鍵字來避免泛型語法,它是「推斷」類型,指示編譯器從陳述式右側的運算式推斷變數的類型:如內建類型、匿名型別、使用者定義型別,或 .NET Framework 類別庫中定義的類型。
![Entity Framework](https://marketing.ares.com.tw/images/2017-06/t2.png)
上述查詢方法有一特性就是延遲執行,變數本身只會儲存查詢命令,非得等到 foreach 迴圈或是 ToList、ToArray 或彙整等指令時,才會真正去執行。而延遲的結果可能會導致兩個問題:
- 沒有回傳:查詢尚未執行,連線就被關閉,如圖(一)中的 Index01(),改善方式可以如 Index02()。
- 重複執行:如圖(一)中的 Index03(),呼叫者分別是 View 中的 Model.Count()、foreach(var item in Model)以及 Controller 的 result.Count(),改善方式可以如 Index04()。利用 SQL Server Profiler 監控命令的執行情形,如圖(二)與(三)。
![View](https://marketing.ares.com.tw/images/2017-06/t3.png)
![Count](https://marketing.ares.com.tw/images/2017-06/t4.png)
![Controller](https://marketing.ares.com.tw/images/2017-06/t5.png)
千萬不要貪圖便利,一味地要求強制執行,這可能招來反效果。如圖(四),若都加上 ToList,則結果如何?這樣會把 Tuple 內的物件通通執行一次,那麼「有用到時,才執行」的延遲效益就被抹煞了。
![Null](https://marketing.ares.com.tw/images/2017-06/t6.png)
其實程式在開發時,也都提防「延遲效應」,沒想到在進行壓力測試時,「指令重複執行」還是發生了。追究到底,錯誤的根源竟然是最令人想不到的地方:View!