第十节:基于MVC5+Unity+EF+Log4Net的基础结构搭建
一. 前言
從本節開始,將陸續的介紹幾種框架搭建組合形式,分析每種搭建形式的優勢和弊端,剖析搭建過程中涉及到的一些思想和技巧。
(一). 技術選型
1. DotNet框架:4.6
2. 數據庫訪問:EF 6.2 (CodeFrist模式)
3. IOC框架:AutoFac 4.8.1 和 AutoFac.MVC5 4.0.2
4. 日志框架:log4net 2.0.8
5. 開發工具:VS2017
(二). 框架目標
1.?一個項目同時連接多個相同種類的數據庫,在一個方法中可以同時對多個數據進行操作。
2.?支持多種數據庫:SqlServer、MySQL、Oracle,靈活的切換數據庫。
3.?抽象成支持多種數據庫連接方式:EF、ADO.Net、Dapper。
?
二. 搭建思路
?1. 層次劃分
將框架分為:Ypf.Data、Ypf.IService、Ypf.Service、Ypf.DTO、Ypf.Utils、Ypf.AdminWeb 六個基本層(后續還會補充 Ypf.Api層),每層的作用分別為:
①.?Ypf.Data:存放連接數據庫的相關類,包括EF上下文類、映射的實體類、實體類的FluentApi模式的配置類。
②.?Ypf.IService:業務接口層,用來約束接口規范。
③.?Ypf.Service:業務層,用來編寫整套項目的業務方法,但需要符合Ypf.IService層的接口約束。
④.?Ypf.DTO: 存放項目中用到的自定義的實體類。
⑤.?Ypf.Utils: 工具類
⑥.?Ypf.AdminWeb: 表現層,系統的展示、接受用戶的交互,傳遞數據,業務對接。
PS:后續會補充Ypf.Api層,用于接受移動端、或其他客戶端接口數據,進行相應的業務對接處理。
2. Ypf.Data層的剖析
把EF封裝到Ypf.Data層,通過Nuget引入EF的程序集,利用FluentAPI的模式先進行配置(實際項目多種模式配合使用),該層的結構如下:
PS:EF的關閉默認策略、EF的DataAnnotations、EF的FluentAPI模式, 在關閉數據庫策略的情況下,無論哪種模式都需要顯式的 ToTable來映射表名,否則會提示該類找不到。
EF配置詳情參考:
? ? ? ? ?第十五節: EF的CodeFirst模式通過DataAnnotations修改默認協定
? ? ? ? ?第十六節: EF的CodeFirst模式通過Fluent API修改默認協定
3.?Service層和IService層簡單的封裝一下
【PS:這個地方是個關鍵點,需要考慮多種不同的寫法,然后進行封裝】
①.【Ypf.Service】層只有一個BaseService泛型類封裝,【Ypf.IService】層并沒有設置IBaseService接口,設置了一個IServiceSupport標識接口(沒有任何內容),需要“AutoFac注入”的所有子類IxxxService都要實現IServiceSupport接口。
②.【Ypf.Service】層中有很多自定義的 xxxService,每個xxxService都要實現【Ypf.IService】層的IxxxService層接口,這里的xxxService層劃分并不依賴表名劃分,自定義根據業務合理起名即可。
③. xxxService類中,利用using() 包裹EF的上下文“db”便于釋放,然后把EF上下文傳入到泛型的BaseService<T>類的構造函數中,可以調用其封裝的方法。
④.利用AutoFac實現在控制器中屬性的注入,相應的配置均寫在Global文件中。
⑤.控制器中的Action僅僅負責傳值和簡單的一些判斷,核心業務全部都寫在Service層中。
4. 利用AutoFac實現Ypf.AdminWeb層與Ypf.Service層解耦
利用AutoFac進行整合,使Ypf.AdminWeb層只需要引入YpfIService層即可,但需要改一下Ypf. Service輸出路徑使其程序集輸出到Ypf.AdminWeb層中。
?解析:利用AutoFac把Ypf.Service中的所有類注冊給它的全部實現接口(一個類可能實現了多個接口),并且把實現類中的屬性也進行注冊(實現類中也可能包含屬性的注入)。
AutoFac的配置詳情參考:?
? ? ? ? ?第二節:框架前期準備篇之AutoFac常見用法總結
5. 將Log4net整合到Ypf.Utils層中
解析:主要配置了兩種模式,輸出到“txt文本文檔”和“SQLServer數據庫中”。其中“文本文檔”又分了兩種模式,全部輸入到一個文檔中 和 不同類型的日志輸入到不同文檔下,在調用的時候通過傳入參數來區分存放在哪個文件夾下。
Log4net的配置詳情參考:
第一節:框架前期準備篇之Log4Net日志詳解
6.?完善【Ypf.Service】層中BaseService的封裝
? 封裝EF常用的增刪改查的方法,這里暫時先不擴展EF插件的方法,分享一下代碼。
?BaseService
?
三. 剖析核心
1. 如何實現同時操作多個相同類型的不同結構的數據庫。
首先【Ypf.Data】層中新建一個存放的實體的文件夾,如“EntityTest”,用來引入另外一個數據庫的存放實體和DbContext上下文,然后在【Ypf.Service】層中,雙Using,往BaseService類中傳入不同db上下文即可實現訪問不同的數據庫,如果要對多個數據庫開啟事務,手動開啟msdtc服務,然后使用Transactions包裹,進行事務一體操作。
詳細的使用步驟見:實戰測試。
2.?體會【Ypf.IService】層 和 引入IOC框架的作用
【PS:依賴倒置原則的核心就是:面向接口編程】
(1). 接口層的作用:
a. 便于開發人員分工開發,寫業務的單獨去寫業務,對接的單獨去對接,而且事先把接口協議定好,那么對接的人員就不需要等業務人員全部寫完代碼,就可以對接了,無非最后再測試而已。
b. 降低修改代碼造成的成本代價,使以接口為基礎搭建起來的框架更加穩健。
舉例1:?三層架構 數據庫訪問層、業務邏輯層、UI調用層。?(非此套框架的模式,后面考慮這么改進)
①. 數據庫訪問層中有一個 MySqlHelp類,提供鏈接MySQL數據增刪改查的方法。
②. 業務邏輯層有一個登錄業務 CheckLogin(MySqlHelp mysql,string userName,string pwd)。
③. UI調用層要調用CheckLogin方法,這時候實例化一個MySqlHelp對象,傳到CheckLogin方法中即可。
有一個天,要求支持oracle數據庫,所以數據庫訪問層中增加了一個oracleHelper類,UI調用層按照常規實例化了一個oracleHelper對象,傳到CheckLogin方法中,發現我的天!!!!CheckLogin竟然不支持oracleHelper對象,同時發現類似的所有業務層的方法都不支持oracleHelper類,這個時候悲劇就發生了,如果全部改業務層的方法,基本上完蛋。
所以根本的解決方案:依賴倒置原則,即面向接口編程。
①. 數據庫訪問層聲明一個接口IHelper,里面有增刪改查方法,MySqlHelp和oracleHelper都實現IHelper接口。
②. 業務邏輯層有一個登錄業務改為依賴接口IHelper, CheckLogin(IHelper iHelper,string userName,string pwd)。
③. UI調用層要調用CheckLogin方法,想連哪個數據,就實例化哪個 eg IHelper iHelper=new MySqlHelp(); 或者 IHelper iHelper=new oracleHelper(),此處考慮和IOC框架結合,連代碼都不用改,直接改配置文件就行了,就可以切換實例,然后調用CheckLogin即可。
舉例2:?類A,類B,類C。
類A中的方法需要傳入類B的實例,通常在類A中實例化一下類B,但如果想讓類A依賴類C,你會發現改動非常大,類A中的方法原先是類B的參數全部需要改。
所以解決方案:類B和類C都實現接口I,類A中方法的參數由原先的類B改為接口I,這樣類A想依賴誰,只需要 I i=new B() 或者 I i=new C(),所有的方法都不用改,也可以再升級一下,這里不直接實例化,利用IOC框架或者手寫反射,只需要改一下配置文件,就能控制 到底是 new B 還是 new C 。
?(2). 引入IOC框架的作用:
解決的問題1:現有的框架模式(Service層using引入EF上下文,傳入到BaseService類中),如何實現快速切換數據庫?
a.首先在【Ypf.Data】層引入MySQL數據庫所需要的程序集,配置文件也改成連接MySQL的。(此處需要詳細測試)
b. 新建一個【Ypf.Service2】層,同樣實現對應業務,只不過是連接不同類型的數據庫(比如它連接的是MySql數據庫),生成路徑也輸出到【Ypf.AdminWeb】層中,最后只需要改一下AutoFac讀取的配置文件“DllName”改為“Ypf.Services2”即可,就可以實現切換數據。
總結:該模式雖然能實現“相同業務、相同表”的不同類型的數據庫切換(比如SQLServer→MySQL),但是需要重新寫一個整層【Ypf.Service2】,雖然基本上是復制,但是有一定工作量的。但是另外通過手寫IOC也可以實現(反射+簡單工廠+配置文件),看不到IOC框架的優勢所在。
IOC強大之處在于框架本身為我們封裝好了很多便于開發的方法,拿AutoFac來說吧,能靈活的控制創建對象的(每次請求都創建、單例、一個Http請求內單例)
四. 實戰測試
這里準備兩個數據庫,分別是:YpfFrame_DB 和?YpfFrameTest_DB
①:YpfFrame_DB中,用到了表:T_SysUser 和?T_SysLoginLog,表結構如下
②.?YpfFrameTest_DB 表中用到了T_SchoolInfor,表結構如下
開始測試
1. 測試增刪改查,包括基本的事務一體。
在【Ypf.IService】層中新建ITestService接口,在【Ypf.Service】層中新建TestService類,實現ITestService接口, 定義TestBasicCRUD方法,進行測試,代碼如下。
1 /// <summary>2 /// 1.測試基本的增刪改查,事務一體3 /// </summary>4 /// <returns></returns>5 public int TestBasicCRUD()6 {7 using (DbContext db = new MyDBContext1())8 {9 BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db); 10 BaseService<T_SysLoginLog> T_SysLoginLogService = new BaseService<T_SysLoginLog>(db); 11 //1.增加操作 12 T_SysUser t_SysUser = new T_SysUser() 13 { 14 id = Guid.NewGuid().ToString("N"), 15 userAccount = "123456", 16 userPwd = "XXX", 17 userRealName = "XXX", 18 appLoginNum = 1, 19 addTime = DateTime.Now 20 }; 21 T_SysUserService.AddNo(t_SysUser); 22 23 //2.修改操作 24 T_SysLoginLog t_SysLoginLog = T_SysLoginLogService.Entities.Where(u => u.id == "1").FirstOrDefault(); 25 if (t_SysLoginLog != null) 26 { 27 t_SysLoginLog.userId = "xxx"; 28 t_SysLoginLog.userName = "xxx"; 29 T_SysLoginLogService.ModifyNo(t_SysLoginLog); 30 } 31 //3.提交操作 32 return db.SaveChanges(); 33 } 34 }2. 測試一個方法中查詢多個數據庫。
在ITestService接口中定義ConnectManyDB方法,并在TestService中實現該方法,代碼如下:
1 /// <summary>2 /// 2. 同時連接多個數據庫進行3 /// </summary>4 /// <param name="userList"></param>5 /// <param name="schoolList"></param>6 public void ConnectManyDB(out List<T_SysUser> userList, out List<T_SchoolInfor> schoolList)7 {8 using (DbContext db = new MyDBContext1())9 using (DbContext db2 = new MyDBContext2()) 10 { 11 BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db); 12 BaseService<T_SchoolInfor> T_SchoolInforService = new BaseService<T_SchoolInfor>(db2); 13 14 //執行數據庫查詢操作 15 userList = T_SysUserService.GetListBy(u => true); 16 schoolList = T_SchoolInforService.GetListBy(u => true); 17 } 18 }分析:想連接幾個數據庫,就需要先在【Ypf.Data】層中新建對應數據庫的實體、實體配置文件、EF上下文,然后在【Ypf.Service】層對應的方法中實例化對應的 EF上下文,然后傳入到BaseService類中即可。
3. 測試一個方法中事務一體處理多個數據庫的crud操作。
?在ITestService接口中定義ManyDBTransaction方法,并在TestService中實現該方法,代碼如下:
1 /// <summary>2 /// 3. 同時對多個數據庫進行事務一體的CRUD操作3 /// 注:需要手動開啟msdtc服務(net start msdtc)4 /// </summary>5 public void ManyDBTransaction()6 {7 using (TransactionScope trans = new TransactionScope())8 {9 try 10 { 11 DbContext db = new MyDBContext1(); 12 DbContext db2 = new MyDBContext2(); 13 14 BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db); 15 BaseService<T_SchoolInfor> T_SchoolInforService = new BaseService<T_SchoolInfor>(db2); 16 17 //執行業務操作 18 T_SysUserService.DelBy(u => u.id == "1"); 19 T_SchoolInforService.DelBy(u => u.id == "1"); 20 21 //最終提交事務 22 trans.Complete(); 23 } 24 catch (Exception ex) 25 { 26 var msg = ex.Message; 27 //事務回滾 28 Transaction.Current.Rollback(); 29 throw; 30 } 31 } 32 }分析:同時連接多個數據庫,并對多個數據庫進行事務性的crud操作,這個時候必須用 【TransactionScope事務】,前提要手動 【net start msdtc?】開啟對應服務,這樣整個事務通過“Complete”方法進行提交,通過Transaction.Current.Rollback()方法進行事務回滾,各自db的SaveChange不起作用,但還是需要SaveChange的。
4. 測試xxxSevice子類中也可以通過AutoFac進行IxxxService的模式進行屬性的注入。
?在【Ypf.IService】層中新建ITestService2接口,在【Ypf.Service】層中新建TestService2類,實現ITestService接口, 定義GetUserInfor方法,進行測試,代碼如下。
?View Code
在TestService中定義ITestService2屬性,如下:
在TestService中定義如下方法,內部用TestService2進行調用,可以調用成功,從而證明xxxSevice子類中也可以通過AutoFac進行IxxxService的模式進行屬性的注入。
5. 測試Log4net的分文件夾和不分文件的使用。
?先分享配置文件:
?View Code
分享對應的封裝類:
?View Code
代碼測試:
?
?
?
?
?
!
- 作???????者 :?Yaopengfei(姚鵬飛)
- 博客地址 :?http://www.cnblogs.com/yaopengfei/
- 聲?????明1 : 本人才疏學淺,用郭德綱的話說“我是一個小學生”,如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲?????明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
?
分類:?11-框架搭建
總結
以上是生活随笔為你收集整理的第十节:基于MVC5+Unity+EF+Log4Net的基础结构搭建的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 马斯克吐槽特斯拉车载浏览器太垃圾:还不如
- 下一篇: C#的变迁史10 - C# 5.0 之其