一. 前言html
二. 搭建思路mysql
1. 層次劃分sql
將框架分爲:Ypf.Data、Ypf.IService、Ypf.Service、Ypf.DTO、Ypf.Utils、Ypf.AdminWeb 六個基本層(後續還會補充 Ypf.Api層),每層的做用分別爲:數據庫
①. Ypf.Data:存放鏈接數據庫的相關類,包括EF上下文類、映射的實體類、實體類的FluentApi模式的配置類。編程
②. Ypf.IService:業務接口層,用來約束接口規範。架構
③. Ypf.Service:業務層,用來編寫整套項目的業務方法,但須要符合Ypf.IService層的接口約束。oracle
④. Ypf.DTO: 存放項目中用到的自定義的實體類。app
⑤. Ypf.Utils: 工具類框架
⑥. Ypf.AdminWeb: 表現層,系統的展現、接受用戶的交互,傳遞數據,業務對接。ide
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的配置詳情參考:
5. 將Log4net整合到Ypf.Utils層中
解析:主要配置了兩種模式,輸出到「txt文本文檔」和「SQLServer數據庫中」。其中「文本文檔」又分了兩種模式,所有輸入到一個文檔中 和 不一樣類型的日誌輸入到不一樣文檔下,在調用的時候經過傳入參數來區分存放在哪一個文件夾下。
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 }
三. 剖析核心
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 }
在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文件 注意"的位置,去空格 --> 29 <param name="DatePattern" value="yyyy-MM-dd".log"" /> 30 <!--1.7.2 在根目錄下按日期產生文件夾,文件名固定 test.log --> 31 <!--<param name="DatePattern" value="yyyy-MM-dd/"test.log"" />--> 32 <!--1.7.3 在根目錄下按日期產生文件夾,這是按日期產生文件夾,並在文件名前也加上日期 --> 33 <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd"-test.log"" />--> 34 <!--1.7.4 在根目錄下按日期產生文件夾,這再造成下一級固定的文件夾 --> 35 <!--<param name="DatePattern" value="yyyyMMdd/"OrderInfor/test.log"" />--> 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".log"" /> 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".log"" /> 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>
分享對應的封裝類:
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 }
代碼測試:
!