第九節:基於MVC5+AutoFac+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插件的方法,分享一下代碼。

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Data.Entity;
  4 using System.Data.SqlClient;
  5 using System.Linq;
  6 using System.Linq.Expressions;
  7 using System.Reflection;
  8 using System.Text;
  9 using System.Threading.Tasks;
 10 using Ypf.Data;
 11 
 12 namespace Ypf.Service.BaseClass
 13 {
 14     public class BaseService<T> where T : class
 15     //public class BaseService
 16     {
 17         private DbContext db;
 18 
 19         //子類通過構造函數來傳入EF上下文
 20         public BaseService(DbContext db)
 21         {
 22             this.db = db;
 23         }
 24 
 25         /****************************************下面進行方法的封裝***********************************************/
 26         //1. 直接提交數據庫
 27 
 28         #region 01-數據源
 29         public IQueryable<T> Entities
 30         {
 31             get
 32             {
 33                 return db.Set<T>();
 34             }
 35         }
 36         #endregion
 37 
 38         #region 02-新增
 39         public int Add(T model)
 40         {
 41             DbSet<T> dst = db.Set<T>();
 42             dst.Add(model);
 43             return db.SaveChanges();
 44 
 45         }
 46         #endregion
 47 
 48         #region 03-刪除(適用於先查詢后刪除 單個)
 49         /// <summary>
 50         /// 刪除(適用於先查詢后刪除的單個實體)
 51         /// </summary>
 52         /// <param name="model">需要刪除的實體</param>
 53         /// <returns></returns>
 54         public int Del(T model)
 55         {
 56             db.Set<T>().Attach(model);
 57             db.Set<T>().Remove(model);
 58             return db.SaveChanges();
 59         }
 60         #endregion
 61 
 62         #region 04-根據條件刪除(支持批量刪除)
 63         /// <summary>
 64         /// 根據條件刪除(支持批量刪除)
 65         /// </summary>
 66         /// <param name="delWhere">傳入Lambda表達式(生成表達式目錄樹)</param>
 67         /// <returns></returns>
 68         public int DelBy(Expression<Func<T, bool>> delWhere)
 69         {
 70             List<T> listDels = db.Set<T>().Where(delWhere).ToList();
 71             listDels.ForEach(d =>
 72             {
 73                 db.Set<T>().Attach(d);
 74                 db.Set<T>().Remove(d);
 75             });
 76             return db.SaveChanges();
 77         }
 78         #endregion
 79 
 80         #region 05-單實體修改
 81         /// <summary>
 82         /// 修改
 83         /// </summary>
 84         /// <param name="model">修改后的實體</param>
 85         /// <returns></returns>
 86         public int Modify(T model)
 87         {
 88             db.Entry(model).State = EntityState.Modified;
 89             return db.SaveChanges();
 90         }
 91         #endregion
 92 
 93         #region 06-批量修改(非lambda)
 94         /// <summary>
 95         /// 批量修改(非lambda)
 96         /// </summary>
 97         /// <param name="model">要修改實體中 修改后的屬性 </param>
 98         /// <param name="whereLambda">查詢實體的條件</param>
 99         /// <param name="proNames">lambda的形式表示要修改的實體屬性名</param>
100         /// <returns></returns>
101         public int ModifyBy(T model, Expression<Func<T, bool>> whereLambda, params string[] proNames)
102         {
103             List<T> listModifes = db.Set<T>().Where(whereLambda).ToList();
104             Type t = typeof(T);
105             List<PropertyInfo> proInfos = t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();
106             Dictionary<string, PropertyInfo> dicPros = new Dictionary<string, PropertyInfo>();
107             proInfos.ForEach(p =>
108             {
109                 if (proNames.Contains(p.Name))
110                 {
111                     dicPros.Add(p.Name, p);
112                 }
113             });
114             foreach (string proName in proNames)
115             {
116                 if (dicPros.ContainsKey(proName))
117                 {
118                     PropertyInfo proInfo = dicPros[proName];
119                     object newValue = proInfo.GetValue(model, null);
120                     foreach (T m in listModifes)
121                     {
122                         proInfo.SetValue(m, newValue, null);
123                     }
124                 }
125             }
126             return db.SaveChanges();
127         }
128         #endregion
129 
130         #region 07-根據條件查詢
131         /// <summary>
132         /// 根據條件查詢
133         /// </summary>
134         /// <param name="whereLambda">查詢條件(lambda表達式的形式生成表達式目錄樹)</param>
135         /// <returns></returns>
136         public List<T> GetListBy(Expression<Func<T, bool>> whereLambda)
137         {
138             return db.Set<T>().Where(whereLambda).ToList();
139         }
140         #endregion
141 
142         #region 08-根據條件排序和查詢
143         /// <summary>
144         /// 根據條件排序和查詢
145         /// </summary>
146         /// <typeparam name="Tkey">排序字段類型</typeparam>
147         /// <param name="whereLambda">查詢條件</param>
148         /// <param name="orderLambda">排序條件</param>
149         /// <param name="isAsc">升序or降序</param>
150         /// <returns></returns>
151         public List<T> GetListBy<Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
152         {
153             List<T> list = null;
154             if (isAsc)
155             {
156                 list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda).ToList();
157             }
158             else
159             {
160                 list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda).ToList();
161             }
162             return list;
163         }
164         #endregion
165 
166         #region 09-分頁查詢
167         /// <summary>
168         /// 根據條件排序和查詢
169         /// </summary>
170         /// <typeparam name="Tkey">排序字段類型</typeparam>
171         /// <param name="pageIndex">頁碼</param>
172         /// <param name="pageSize">頁容量</param>
173         /// <param name="whereLambda">查詢條件</param>
174         /// <param name="orderLambda">排序條件</param>
175         /// <param name="isAsc">升序or降序</param>
176         /// <returns></returns>
177         public List<T> GetPageList<Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
178         {
179 
180             List<T> list = null;
181             if (isAsc)
182             {
183                 list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda)
184                .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
185             }
186             else
187             {
188                 list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda)
189               .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
190             }
191             return list;
192         }
193         #endregion
194 
195         #region 10-分頁查詢輸出總行數
196         /// <summary>
197         /// 根據條件排序和查詢
198         /// </summary>
199         /// <typeparam name="Tkey">排序字段類型</typeparam>
200         /// <param name="pageIndex">頁碼</param>
201         /// <param name="pageSize">頁容量</param>
202         /// <param name="whereLambda">查詢條件</param>
203         /// <param name="orderLambda">排序條件</param>
204         /// <param name="isAsc">升序or降序</param>
205         /// <returns></returns>
206         public List<T> GetPageList<Tkey>(int pageIndex, int pageSize, ref int rowCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
207         {
208             int count = 0;
209             List<T> list = null;
210             count = db.Set<T>().Where(whereLambda).Count();
211             if (isAsc)
212             {
213                 var iQueryList = db.Set<T>().Where(whereLambda).OrderBy(orderLambda)
214                    .Skip((pageIndex - 1) * pageSize).Take(pageSize);
215 
216                 list = iQueryList.ToList();
217             }
218             else
219             {
220                 var iQueryList = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda)
221                  .Skip((pageIndex - 1) * pageSize).Take(pageSize);
222                 list = iQueryList.ToList();
223             }
224             rowCount = count;
225             return list;
226         }
227         #endregion
228 
229 
230         //2. SaveChange剝離出來,處理事務
231 
232         #region 01-批量處理SaveChange()
233         /// <summary>
234         /// 事務批量處理
235         /// </summary>
236         /// <returns></returns>
237         public int SaveChange()
238         {
239             return db.SaveChanges();
240         }
241         #endregion
242 
243         #region 02-新增
244         /// <summary>
245         /// 新增
246         /// </summary>
247         /// <param name="model">需要新增的實體</param>
248         public void AddNo(T model)
249         {
250             db.Set<T>().Add(model);
251         }
252         #endregion
253 
254         #region 03-刪除
255         /// <summary>
256         /// 刪除
257         /// </summary>
258         /// <param name="model">需要刪除的實體</param>
259         public void DelNo(T model)
260         {
261             db.Entry(model).State = EntityState.Deleted;
262         }
263         #endregion
264 
265         #region 04-根據條件刪除
266         /// <summary>
267         /// 條件刪除
268         /// </summary>
269         /// <param name="delWhere">需要刪除的條件</param>
270         public void DelByNo(Expression<Func<T, bool>> delWhere)
271         {
272             List<T> listDels = db.Set<T>().Where(delWhere).ToList();
273             listDels.ForEach(d =>
274             {
275                 db.Set<T>().Attach(d);
276                 db.Set<T>().Remove(d);
277             });
278         }
279         #endregion
280 
281         #region 05-修改
282         /// <summary>
283         /// 修改
284         /// </summary>
285         /// <param name="model">修改后的實體</param>
286         public void ModifyNo(T model)
287         {
288             db.Entry(model).State = EntityState.Modified;
289         }
290         #endregion
291 
292 
293         //3. EF調用sql語句
294 
295         #region 01-執行增加,刪除,修改操作(或調用存儲過程)
296         /// <summary>
297         /// 執行增加,刪除,修改操作(或調用存儲過程)
298         /// </summary>
299         /// <param name="sql"></param>
300         /// <param name="pars"></param>
301         /// <returns></returns>
302         public int ExecuteSql(string sql, params SqlParameter[] pars)
303         {
304             return db.Database.ExecuteSqlCommand(sql, pars);
305         }
306 
307         #endregion
308 
309         #region 02-執行查詢操作
310         /// <summary>
311         /// 執行查詢操作
312         /// </summary>
313         /// <typeparam name="T"></typeparam>
314         /// <param name="sql"></param>
315         /// <param name="pars"></param>
316         /// <returns></returns>
317         public List<T> ExecuteQuery<T>(string sql, params SqlParameter[] pars)
318         {
319             return db.Database.SqlQuery<T>(sql, pars).ToList();
320         }
321         #endregion
322 
323 
324 
325     }
326 }
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方法,進行測試,代碼如下。

 1  public class TestService2 : ITestService2
 2     {
 3         /// <summary>
 4         /// 獲取用戶信息
 5         /// </summary>
 6         /// <returns></returns>
 7         public List<T_SysUser> GetUserInfor()
 8         {
 9             using (DbContext db=new MyDBContext1())
10             {
11                 BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db);
12                 return T_SysUserService.GetListBy(u => true);
13             }
14         }
15     }
View Code

在TestService中定義ITestService2屬性,如下:

在TestService中定義如下方法,內部用TestService2進行調用,可以調用成功,從而證明xxxSevice子類中也可以通過AutoFac進行IxxxService的模式進行屬性的注入

5. 測試Log4net的分文件夾和不分文件的使用。

 先分享配置文件:

  1 <?xml version="1.0" encoding="utf-8" ?>
  2 <configuration>
  3   <!-- 一. 添加log4net的自定義配置節點-->
  4   <configSections>
  5     <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  6   </configSections>
  7   <!--二. log4net的核心配置代碼-->
  8   <log4net>
  9     <!--1. 輸出途徑(一) 將日志以回滾文件的形式寫到文件中-->
 10     
 11     <!--模式一:全部存放到一個文件夾里-->
 12     <appender name="log0" type="log4net.Appender.RollingFileAppender">
 13       <!--1.1 文件夾的位置(也可以寫相對路徑)-->
 14       <param name="File"  value="D:\MyLog\" />
 15       <!--相對路徑-->
 16       <!--<param name="File"  value="Logs/" />-->
 17       <!--1.2 是否追加到文件-->
 18       <param name="AppendToFile" value="true" />
 19       <!--1.3 使用最小鎖定模型(minimal locking model),以允許多個進程可以寫入同一個文件 -->
 20       <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
 21       <!--1.4 配置Unicode編碼-->
 22       <Encoding value="UTF-8" />
 23       <!--1.5 是否只寫到一個文件里-->
 24       <param name="StaticLogFileName" value="false" />
 25       <!--1.6 配置按照何種方式產生多個日志文件 (Date:日期、Size:文件大小、Composite:日期和文件大小的混合方式)-->
 26       <param name="RollingStyle" value="Composite" />
 27       <!--1.7 介紹多種日志的的命名和存放在磁盤的形式-->
 28       <!--1.7.1 在根目錄下直接以日期命名txt文件 注意&quot;的位置,去空格 -->
 29       <param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
 30       <!--1.7.2 在根目錄下按日期產生文件夾,文件名固定 test.log  -->
 31       <!--<param name="DatePattern" value="yyyy-MM-dd/&quot;test.log&quot;"  />-->
 32       <!--1.7.3 在根目錄下按日期產生文件夾,這是按日期產生文件夾,並在文件名前也加上日期  -->
 33       <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd&quot;-test.log&quot;"  />-->
 34       <!--1.7.4 在根目錄下按日期產生文件夾,這再形成下一級固定的文件夾  -->
 35       <!--<param name="DatePattern" value="yyyyMMdd/&quot;OrderInfor/test.log&quot;"  />-->
 36       <!--1.8 配置每個日志的大小。【只在1.6 RollingStyle 選擇混合方式與文件大小方式下才起作用!!!】可用的單位:KB|MB|GB。不要使用小數,否則會一直寫入當前日志,
 37       超出大小后在所有文件名后自動增加正整數重新命名,數字最大的最早寫入。-->
 38       <param name="maximumFileSize" value="10MB" />
 39       <!--1.9 最多產生的日志文件個數,超過則保留最新的n個 將value的值設置-1,則不限文件個數 【只在1.6 RollingStyle 選擇混合方式與文件大小方式下才起作用!!!】
 40         與1.8中maximumFileSize文件大小是配合使用的-->
 41       <param name="MaxSizeRollBackups" value="5" />
 42       <!--1.10 配置文件文件的布局格式,使用PatternLayout,自定義布局-->
 43       <layout type="log4net.Layout.PatternLayout">
 44         <conversionPattern value="記錄時間:%date %n線程ID:[%thread] %n日志級別:%-5level %n出錯類:%logger property: [%property{NDC}] - %n錯誤描述:%message%newline %n%newline"/>
 45       </layout>
 46     </appender>
 47 
 48     <!--模式二:分文件夾存放-->
 49     <!--文件夾1-->
 50     <appender name="log1" type="log4net.Appender.RollingFileAppender">
 51       <param name="File"  value="D:\MyLog\OneLog\" />
 52       <param name="AppendToFile" value="true" />
 53       <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
 54       <Encoding value="UTF-8" />
 55       <param name="StaticLogFileName" value="false" />
 56       <param name="RollingStyle" value="Composite" />
 57       <param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
 58       <param name="maximumFileSize" value="10MB" />
 59       <param name="MaxSizeRollBackups" value="5" />
 60       <layout type="log4net.Layout.PatternLayout">
 61         <conversionPattern value="%message%newline" />
 62       </layout>
 63       <!--下面是利用過濾器進行分文件夾存放,兩種過濾器進行配合-->
 64       <!--與Logger名稱(OneLog)匹配,才記錄,-->
 65       <filter type="log4net.Filter.LoggerMatchFilter">
 66         <loggerToMatch value="OneLog" />
 67       </filter>
 68       <!--阻止所有的日志事件被記錄-->
 69       <filter type="log4net.Filter.DenyAllFilter" />
 70     </appender>
 71     <!--文件夾2-->
 72     <appender name="log2" type="log4net.Appender.RollingFileAppender">
 73       <param name="File"  value="D:\MyLog\TwoLog\" />
 74       <param name="AppendToFile" value="true" />
 75       <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
 76       <Encoding value="UTF-8" />
 77       <param name="StaticLogFileName" value="false" />
 78       <param name="RollingStyle" value="Composite" />
 79       <param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
 80       <param name="maximumFileSize" value="10MB" />
 81       <param name="MaxSizeRollBackups" value="5" />
 82       <layout type="log4net.Layout.PatternLayout">
 83         <conversionPattern value="%message%newline" />
 84       </layout>
 85       <!--下面是利用過濾器進行分文件夾存放,兩種過濾器進行配合-->
 86       <!--與Logger名稱(TwoLog)匹配,才記錄,-->
 87       <filter type="log4net.Filter.LoggerMatchFilter">
 88         <loggerToMatch value="TwoLog" />
 89       </filter>
 90       <!--阻止所有的日志事件被記錄-->
 91       <filter type="log4net.Filter.DenyAllFilter" />
 92     </appender>
 93 
 94 
 95     <!--2. 輸出途徑(二) 記錄日志到數據庫-->
 96     <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
 97       <!--2.1 設置緩沖區大小,只有日志記錄超設定值才會一塊寫入到數據庫-->
 98       <param name="BufferSize" value="1" />
 99       <!--2.2 引用-->
100       <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
101       <!--2.3 數據庫連接字符串-->
102       <connectionString value="data source=localhost;initial catalog=LogDB;integrated security=false;persist security info=True;User ID=sa;Password=123456" />
103       <!--2.4 SQL語句插入到指定表-->
104       <commandText value="INSERT INTO LogInfor ([threadId],[log_level],[log_name],[log_msg],[log_exception],[log_time]) VALUES (@threadId, @log_level, @log_name, @log_msg, @log_exception,@log_time)" />
105       <!--2.5 數據庫字段匹配-->
106       <!-- 線程號-->
107       <parameter>
108         <parameterName value="@threadId" />
109         <dbType value="String" />
110         <size value="100" />
111         <layout type="log4net.Layout.PatternLayout">
112           <conversionPattern value="%thread" />
113         </layout>
114       </parameter>
115       <!--日志級別-->
116       <parameter>
117         <parameterName value="@log_level" />
118         <dbType value="String" />
119         <size value="100" />
120         <layout type="log4net.Layout.PatternLayout">
121           <conversionPattern value="%level" />
122         </layout>
123       </parameter>
124       <!--日志記錄類名稱-->
125       <parameter>
126         <parameterName value="@log_name" />
127         <dbType value="String" />
128         <size value="100" />
129         <layout type="log4net.Layout.PatternLayout">
130           <conversionPattern value="%logger" />
131         </layout>
132       </parameter>
133       <!--日志信息-->
134       <parameter>
135         <parameterName value="@log_msg" />
136         <dbType value="String" />
137         <size value="5000" />
138         <layout type="log4net.Layout.PatternLayout">
139           <conversionPattern value="%message" />
140         </layout>
141       </parameter>
142       <!--異常信息  指的是如Infor 方法的第二個參數的值-->
143       <parameter>
144         <parameterName value="@log_exception" />
145         <dbType value="String" />
146         <size value="2000" />
147         <layout type="log4net.Layout.ExceptionLayout" />
148       </parameter>
149       <!-- 日志記錄時間-->
150       <parameter>
151         <parameterName value="@log_time" />
152         <dbType value="DateTime" />
153         <layout type="log4net.Layout.RawTimeStampLayout" />
154       </parameter>
155     </appender>
156 
157 
158     <!--(二). 配置日志的的輸出級別和加載日志的輸出途徑-->
159     <root>
160       <!--1. level中的value值表示該值及其以上的日志級別才會輸出-->
161       <!--OFF > FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息)  > ALL  -->
162       <!--OFF表示所有信息都不寫入,ALL表示所有信息都寫入-->
163       <level value="ALL"></level>
164       <!--2. append-ref標簽表示要加載前面的日志輸出途徑代碼  通過ref和appender標簽的中name屬性相關聯-->
165       
166       <appender-ref ref="log0"></appender-ref>
167       <appender-ref ref="log1"></appender-ref>
168       <appender-ref ref="log2"></appender-ref>
169 
170       <!--<appender-ref ref="AdoNetAppender"></appender-ref>-->
171     </root>
172   </log4net>
173 
174 </configuration>
View Code

分享對應的封裝類:

  1 using log4net;
  2 using System;
  3 using System.Collections.Generic;
  4 using System.Diagnostics;
  5 using System.Linq;
  6 using System.Reflection;
  7 using System.Text;
  8 using System.Threading.Tasks;
  9 
 10 namespace Ypf.Utils.Log
 11 {
 12     public class LogUtils
 13     {
 14         //聲明文件夾名稱(這里分兩個文件夾)
 15         static string log1Name = "OneLog";
 16         static string log2Name = "TwoLog";
 17 
 18         //可以聲明多個日志對象
 19         //模式一:不分文件夾(所有的log對存放在這一個文件夾下)
 20         public static ILog log = LogManager.GetLogger(typeof(LogUtils));
 21 
 22         //模式二:分文件夾
 23         //如果是要分文件夾存儲,這里的名稱需要和配置文件中loggerToMatch節點中的value相配合
 24         //1. OneLog文件夾
 25         public static ILog log1 = LogManager.GetLogger(log1Name);
 26         //2. TwoLog文件夾
 27         public static ILog log2 = LogManager.GetLogger(log2Name);
 28 
 29         #region 01-初始化Log4net的配置
 30         /// <summary>
 31         /// 初始化Log4net的配置
 32         /// xml文件一定要改為嵌入的資源
 33         /// </summary>
 34         public static void InitLog4Net()
 35         {
 36             Assembly assembly = Assembly.GetExecutingAssembly();
 37             var xml = assembly.GetManifestResourceStream("Ypf.Utils.Log.log4net.xml");
 38             log4net.Config.XmlConfigurator.Configure(xml);
 39         }
 40         #endregion
 41 
 42         /************************* 五種不同日志級別 *******************************/
 43         //FATAL(致命錯誤) > ERROR(一般錯誤) > WARN(警告) > INFO(一般信息) > DEBUG(調試信息)
 44 
 45         #region 00-將調試的信息輸出,可以定位到具體的位置(解決高層封裝帶來的問題)
 46         /// <summary>
 47         /// 將調試的信息輸出,可以定位到具體的位置(解決高層封裝帶來的問題)
 48         /// </summary>
 49         /// <returns></returns>
 50         private static string getDebugInfo()
 51         {
 52             StackTrace trace = new StackTrace(true);
 53             return trace.ToString();
 54         }
 55         #endregion
 56 
 57         #region 01-DEBUG(調試信息)
 58         /// <summary>
 59         /// DEBUG(調試信息)
 60         /// </summary>
 61         /// <param name="msg">日志信息</param>
 62         ///  <param name="logName">文件夾名稱</param>
 63         public static void Debug(string msg, string logName = "")
 64         {
 65             if (logName == "")
 66             {
 67                 log.Debug(getDebugInfo() + msg);
 68             }
 69             else if (logName == log1Name)
 70             {
 71                 log1.Debug(msg);
 72             }
 73             else if (logName == log2Name)
 74             {
 75                 log2.Debug(msg);
 76             }
 77         }
 78         /// <summary>
 79         /// Debug
 80         /// </summary>
 81         /// <param name="msg">日志信息</param>
 82         /// <param name="exception">錯誤信息</param>
 83         public static void Debug(string msg, Exception exception)
 84         {
 85             log.Debug(getDebugInfo() + msg, exception);
 86         }
 87 
 88         #endregion
 89 
 90         #region 02-INFO(一般信息)
 91         /// <summary>
 92         /// INFO(一般信息)
 93         /// </summary>
 94         /// <param name="msg">日志信息</param>
 95         /// <param name="logName">文件夾名稱</param>
 96         public static void Info(string msg, string logName = "")
 97         {
 98             if (logName == "")
 99             {
100                 log.Info(getDebugInfo() + msg);
101             }
102             else if (logName == log1Name)
103             {
104                 log1.Info(msg);
105             }
106             else if (logName == log2Name)
107             {
108                 log2.Info(msg);
109             }
110         }
111         /// <summary>
112         /// Info
113         /// </summary>
114         /// <param name="msg">日志信息</param>
115         /// <param name="exception">錯誤信息</param>
116         public static void Info(string msg, Exception exception)
117         {
118             log.Info(getDebugInfo() + msg, exception);
119         }
120         #endregion
121 
122         #region 03-WARN(警告)
123         /// <summary>
124         ///WARN(警告)
125         /// </summary>
126         /// <param name="msg">日志信息</param>
127         /// <param name="logName">文件夾名稱</param>
128         public static void Warn(string msg, string logName = "")
129         {
130             if (logName == "")
131             {
132                 log.Warn(getDebugInfo() + msg);
133             }
134             else if (logName == log1Name)
135             {
136                 log1.Warn(msg);
137             }
138             else if (logName == log2Name)
139             {
140                 log2.Warn(msg);
141             }
142         }
143         /// <summary>
144         /// Warn
145         /// </summary>
146         /// <param name="msg">日志信息</param>
147         /// <param name="exception">錯誤信息</param>
148         public static void Warn(string msg, Exception exception)
149         {
150             log.Warn(getDebugInfo() + msg, exception);
151         }
152         #endregion
153 
154         #region 04-ERROR(一般錯誤)
155         /// <summary>
156         /// ERROR(一般錯誤)
157         /// </summary>
158         /// <param name="msg">日志信息</param>
159         /// <param name="logName">文件夾名稱</param>
160         public static void Error(string msg, string logName = "")
161         {
162             if (logName == "")
163             {
164                 log.Error(getDebugInfo() + msg);
165             }
166             else if (logName == log1Name)
167             {
168                 log1.Error(msg);
169             }
170             else if (logName == log2Name)
171             {
172                 log2.Error(msg);
173             }
174         }
175         /// <summary>
176         /// Error
177         /// </summary>
178         /// <param name="msg">日志信息</param>
179         /// <param name="exception">錯誤信息</param>
180         public static void Error(string msg, Exception exception)
181         {
182             log.Error(getDebugInfo() + msg, exception);
183         }
184         #endregion
185 
186         #region 05-FATAL(致命錯誤)
187         /// <summary>
188         /// FATAL(致命錯誤)
189         /// </summary>
190         /// <param name="msg">日志信息</param>
191         /// <param name="logName">文件夾名稱</param>
192         public static void Fatal(string msg, string logName = "")
193         {
194             if (logName == "")
195             {
196                 log.Fatal(getDebugInfo() + msg);
197             }
198             else if (logName == log1Name)
199             {
200                 log1.Fatal(msg);
201             }
202             else if (logName == log2Name)
203             {
204                 log2.Fatal(msg);
205             }
206         }
207         /// <summary>
208         /// Fatal
209         /// </summary>
210         /// <param name="msg">日志信息</param>
211         /// <param name="exception">錯誤信息</param>
212         public static void Fatal(string msg, Exception exception)
213         {
214             log.Fatal(getDebugInfo() + msg, exception);
215         }
216 
217         #endregion
218 
219 
220 
221     }
222 }
View Code

代碼測試:

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 本人才疏學淺,用郭德綱的話說“我是一個小學生”,如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM