第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建一. 前言 从本节开始,将陆续的介绍几种框架搭建组合形式,分析每种搭建形式的优势和弊端,剖析搭建过程中涉及到的一些思想和技巧。

欢迎大家来到IT世界,在知识的湖畔探索吧!

一. 前言

从本节开始,将陆续的介绍几种框架搭建组合形式,分析每种搭建形式的优势和弊端,剖析搭建过程中涉及到的一些思想和技巧。

(一). 技术选型

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的模式先进行配置(实际项目多种模式配合使用),该层的结构如下:

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

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 }

欢迎大家来到IT世界,在知识的湖畔探索吧!

三. 剖析核心

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,表结构如下

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

②. YpfFrameTest_DB 表中用到了T_SchoolInfor,表结构如下

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

开始测试

1. 测试增删改查,包括基本的事务一体。

在【Ypf.IService】层中新建ITestService接口,在【Ypf.Service】层中新建TestService类,实现ITestService接口, 定义TestBasicCRUD方法,进行测试,代码如下。

欢迎大家来到IT世界,在知识的湖畔探索吧! 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中实现该方法,代码如下:

欢迎大家来到IT世界,在知识的湖畔探索吧! 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属性,如下:

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

在TestService中定义如下方法,内部用TestService2进行调用,可以调用成功,从而证明xxxSevice子类中也可以通过AutoFac进行IxxxService的模式进行属性的注入。

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

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 }

代码测试:

第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/34272.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信