一. 前言

  从本节开始,将陆续的介绍几种框架搭建组合形式,分析每种搭建形式的优势和弊端,剖析搭建过程中涉及到的一些思想和技巧。
(一). 技术选型
  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的基础结构搭建-LMLPHP

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插件的方法,分享一下代码。

 using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Ypf.Data; namespace Ypf.Service.BaseClass
{
public class BaseService<T> where T : class
//public class BaseService
{
private DbContext db; //子类通过构造函数来传入EF上下文
public BaseService(DbContext db)
{
this.db = db;
} /****************************************下面进行方法的封装***********************************************/
//1. 直接提交数据库 #region 01-数据源
public IQueryable<T> Entities
{
get
{
return db.Set<T>();
}
}
#endregion #region 02-新增
public int Add(T model)
{
DbSet<T> dst = db.Set<T>();
dst.Add(model);
return db.SaveChanges(); }
#endregion #region 03-删除(适用于先查询后删除 单个)
/// <summary>
/// 删除(适用于先查询后删除的单个实体)
/// </summary>
/// <param name="model">需要删除的实体</param>
/// <returns></returns>
public int Del(T model)
{
db.Set<T>().Attach(model);
db.Set<T>().Remove(model);
return db.SaveChanges();
}
#endregion #region 04-根据条件删除(支持批量删除)
/// <summary>
/// 根据条件删除(支持批量删除)
/// </summary>
/// <param name="delWhere">传入Lambda表达式(生成表达式目录树)</param>
/// <returns></returns>
public int DelBy(Expression<Func<T, bool>> delWhere)
{
List<T> listDels = db.Set<T>().Where(delWhere).ToList();
listDels.ForEach(d =>
{
db.Set<T>().Attach(d);
db.Set<T>().Remove(d);
});
return db.SaveChanges();
}
#endregion #region 05-单实体修改
/// <summary>
/// 修改
/// </summary>
/// <param name="model">修改后的实体</param>
/// <returns></returns>
public int Modify(T model)
{
db.Entry(model).State = EntityState.Modified;
return db.SaveChanges();
}
#endregion #region 06-批量修改(非lambda)
/// <summary>
/// 批量修改(非lambda)
/// </summary>
/// <param name="model">要修改实体中 修改后的属性 </param>
/// <param name="whereLambda">查询实体的条件</param>
/// <param name="proNames">lambda的形式表示要修改的实体属性名</param>
/// <returns></returns>
public int ModifyBy(T model, Expression<Func<T, bool>> whereLambda, params string[] proNames)
{
List<T> listModifes = db.Set<T>().Where(whereLambda).ToList();
Type t = typeof(T);
List<PropertyInfo> proInfos = t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();
Dictionary<string, PropertyInfo> dicPros = new Dictionary<string, PropertyInfo>();
proInfos.ForEach(p =>
{
if (proNames.Contains(p.Name))
{
dicPros.Add(p.Name, p);
}
});
foreach (string proName in proNames)
{
if (dicPros.ContainsKey(proName))
{
PropertyInfo proInfo = dicPros[proName];
object newValue = proInfo.GetValue(model, null);
foreach (T m in listModifes)
{
proInfo.SetValue(m, newValue, null);
}
}
}
return db.SaveChanges();
}
#endregion #region 07-根据条件查询
/// <summary>
/// 根据条件查询
/// </summary>
/// <param name="whereLambda">查询条件(lambda表达式的形式生成表达式目录树)</param>
/// <returns></returns>
public List<T> GetListBy(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where(whereLambda).ToList();
}
#endregion #region 08-根据条件排序和查询
/// <summary>
/// 根据条件排序和查询
/// </summary>
/// <typeparam name="Tkey">排序字段类型</typeparam>
/// <param name="whereLambda">查询条件</param>
/// <param name="orderLambda">排序条件</param>
/// <param name="isAsc">升序or降序</param>
/// <returns></returns>
public List<T> GetListBy<Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
{
List<T> list = null;
if (isAsc)
{
list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda).ToList();
}
else
{
list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda).ToList();
}
return list;
}
#endregion #region 09-分页查询
/// <summary>
/// 根据条件排序和查询
/// </summary>
/// <typeparam name="Tkey">排序字段类型</typeparam>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">页容量</param>
/// <param name="whereLambda">查询条件</param>
/// <param name="orderLambda">排序条件</param>
/// <param name="isAsc">升序or降序</param>
/// <returns></returns>
public List<T> GetPageList<Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
{ List<T> list = null;
if (isAsc)
{
list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize).ToList();
}
else
{
list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize).ToList();
}
return list;
}
#endregion #region 10-分页查询输出总行数
/// <summary>
/// 根据条件排序和查询
/// </summary>
/// <typeparam name="Tkey">排序字段类型</typeparam>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">页容量</param>
/// <param name="whereLambda">查询条件</param>
/// <param name="orderLambda">排序条件</param>
/// <param name="isAsc">升序or降序</param>
/// <returns></returns>
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)
{
int count = ;
List<T> list = null;
count = db.Set<T>().Where(whereLambda).Count();
if (isAsc)
{
var iQueryList = db.Set<T>().Where(whereLambda).OrderBy(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize); list = iQueryList.ToList();
}
else
{
var iQueryList = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize);
list = iQueryList.ToList();
}
rowCount = count;
return list;
}
#endregion //2. SaveChange剥离出来,处理事务 #region 01-批量处理SaveChange()
/// <summary>
/// 事务批量处理
/// </summary>
/// <returns></returns>
public int SaveChange()
{
return db.SaveChanges();
}
#endregion #region 02-新增
/// <summary>
/// 新增
/// </summary>
/// <param name="model">需要新增的实体</param>
public void AddNo(T model)
{
db.Set<T>().Add(model);
}
#endregion #region 03-删除
/// <summary>
/// 删除
/// </summary>
/// <param name="model">需要删除的实体</param>
public void DelNo(T model)
{
db.Entry(model).State = EntityState.Deleted;
}
#endregion #region 04-根据条件删除
/// <summary>
/// 条件删除
/// </summary>
/// <param name="delWhere">需要删除的条件</param>
public void DelByNo(Expression<Func<T, bool>> delWhere)
{
List<T> listDels = db.Set<T>().Where(delWhere).ToList();
listDels.ForEach(d =>
{
db.Set<T>().Attach(d);
db.Set<T>().Remove(d);
});
}
#endregion #region 05-修改
/// <summary>
/// 修改
/// </summary>
/// <param name="model">修改后的实体</param>
public void ModifyNo(T model)
{
db.Entry(model).State = EntityState.Modified;
}
#endregion //3. EF调用sql语句 #region 01-执行增加,删除,修改操作(或调用存储过程)
/// <summary>
/// 执行增加,删除,修改操作(或调用存储过程)
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
/// <returns></returns>
public int ExecuteSql(string sql, params SqlParameter[] pars)
{
return db.Database.ExecuteSqlCommand(sql, pars);
} #endregion #region 02-执行查询操作
/// <summary>
/// 执行查询操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="pars"></param>
/// <returns></returns>
public List<T> ExecuteQuery<T>(string sql, params SqlParameter[] pars)
{
return db.Database.SqlQuery<T>(sql, pars).ToList();
}
#endregion }
}

BaseService

三. 剖析核心

1. 如何实现同时操作多个相同类型的不同结构的数据库。

  首先【Ypf.Data】层中新建一个存放的实体的文件夹,如“EntityTest”,用来引入另外一个数据库的存放实体和DbContext上下文,然后在【Ypf.Service】层中,双Using,往BaseService类中传入不同db上下文即可实现访问不同的数据库,如果要对多个数据库开启事务,手动开启msdtc服务,然后使用Transactions包裹,进行事务一体操作。

  详细的使用步骤见:实战测试。

2. 体会【Ypf.IService】层 和 引入IOC框架的作用

【PS:依赖倒置原则的核心就是:面向接口编程】

(1). 接口层的作用:

  a. 便于开发人员分工开发,写业务的单独去写业务,对接的单独去对接,而且事先把接口协议定好,那么对接的人员就不需要等业务人员全部写完代码,就可以对接了,无非最后再测试而已。

  b. 降低修改代码造成的成本代价,使以接口为基础搭建起来的框架更加稳健。

 (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的基础结构搭建-LMLPHP第九节:基于MVC5+AutoFac+EF+Log4Net的基础结构搭建-LMLPHP

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

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

开始测试

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

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

         /// <summary>
/// 1.测试基本的增删改查,事务一体
/// </summary>
/// <returns></returns>
public int TestBasicCRUD()
{
using (DbContext db = new MyDBContext1())
{
BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db);
BaseService<T_SysLoginLog> T_SysLoginLogService = new BaseService<T_SysLoginLog>(db);
//1.增加操作
T_SysUser t_SysUser = new T_SysUser()
{
id = Guid.NewGuid().ToString("N"),
userAccount = "",
userPwd = "XXX",
userRealName = "XXX",
appLoginNum = ,
addTime = DateTime.Now
};
T_SysUserService.AddNo(t_SysUser); //2.修改操作
T_SysLoginLog t_SysLoginLog = T_SysLoginLogService.Entities.Where(u => u.id == "").FirstOrDefault();
if (t_SysLoginLog != null)
{
t_SysLoginLog.userId = "xxx";
t_SysLoginLog.userName = "xxx";
T_SysLoginLogService.ModifyNo(t_SysLoginLog);
}
//3.提交操作
return db.SaveChanges();
}
}

2. 测试一个方法中查询多个数据库。

在ITestService接口中定义ConnectManyDB方法,并在TestService中实现该方法,代码如下:

         /// <summary>
/// 2. 同时连接多个数据库进行
/// </summary>
/// <param name="userList"></param>
/// <param name="schoolList"></param>
public void ConnectManyDB(out List<T_SysUser> userList, out List<T_SchoolInfor> schoolList)
{
using (DbContext db = new MyDBContext1())
using (DbContext db2 = new MyDBContext2())
{
BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db);
BaseService<T_SchoolInfor> T_SchoolInforService = new BaseService<T_SchoolInfor>(db2); //执行数据库查询操作
userList = T_SysUserService.GetListBy(u => true);
schoolList = T_SchoolInforService.GetListBy(u => true);
}
}

  分析:想连接几个数据库,就需要先在【Ypf.Data】层中新建对应数据库的实体、实体配置文件、EF上下文,然后在【Ypf.Service】层对应的方法中实例化对应的 EF上下文,然后传入到BaseService类中即可。

3. 测试一个方法中事务一体处理多个数据库的crud操作。

在ITestService接口中定义ManyDBTransaction方法,并在TestService中实现该方法,代码如下:

         /// <summary>
/// 3. 同时对多个数据库进行事务一体的CRUD操作
/// 注:需要手动开启msdtc服务(net start msdtc)
/// </summary>
public void ManyDBTransaction()
{
using (TransactionScope trans = new TransactionScope())
{
try
{
DbContext db = new MyDBContext1();
DbContext db2 = new MyDBContext2(); BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db);
BaseService<T_SchoolInfor> T_SchoolInforService = new BaseService<T_SchoolInfor>(db2); //执行业务操作
T_SysUserService.DelBy(u => u.id == "");
T_SchoolInforService.DelBy(u => u.id == ""); //最终提交事务
trans.Complete();
}
catch (Exception ex)
{
var msg = ex.Message;
//事务回滚
Transaction.Current.Rollback();
throw;
}
}
}

  分析:同时连接多个数据库,并对多个数据库进行事务性的crud操作,这个时候必须用 【TransactionScope事务】,前提要手动 【net start msdtc 】开启对应服务,这样整个事务通过“Complete”方法进行提交,通过Transaction.Current.Rollback()方法进行事务回滚,各自db的SaveChange不起作用,但还是需要SaveChange的。

4. 测试xxxSevice子类中也可以通过AutoFac进行IxxxService的模式进行属性的注入。

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

  public class TestService2 : ITestService2
{
/// <summary>
/// 获取用户信息
/// </summary>
/// <returns></returns>
public List<T_SysUser> GetUserInfor()
{
using (DbContext db=new MyDBContext1())
{
BaseService<T_SysUser> T_SysUserService = new BaseService<T_SysUser>(db);
return T_SysUserService.GetListBy(u => true);
}
}
}

在TestService中定义ITestService2属性,如下:

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

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

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

5. 测试Log4net的分文件夹和不分文件的使用。

先分享配置文件:

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- 一. 添加log4net的自定义配置节点-->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<!--二. log4net的核心配置代码-->
<log4net>
<!--1. 输出途径(一) 将日志以回滚文件的形式写到文件中--> <!--模式一:全部存放到一个文件夹里-->
<appender name="log0" type="log4net.Appender.RollingFileAppender">
<!--1.1 文件夹的位置(也可以写相对路径)-->
<param name="File" value="D:\MyLog\" />
<!--相对路径-->
<!--<param name="File" value="Logs/" />-->
<!--1.2 是否追加到文件-->
<param name="AppendToFile" value="true" />
<!--1.3 使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件 -->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--1.4 配置Unicode编码-->
<Encoding value="UTF-8" />
<!--1.5 是否只写到一个文件里-->
<param name="StaticLogFileName" value="false" />
<!--1.6 配置按照何种方式产生多个日志文件 (Date:日期、Size:文件大小、Composite:日期和文件大小的混合方式)-->
<param name="RollingStyle" value="Composite" />
<!--1.7 介绍多种日志的的命名和存放在磁盘的形式-->
<!--1.7.1 在根目录下直接以日期命名txt文件 注意&quot;的位置,去空格 -->
<param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
<!--1.7.2 在根目录下按日期产生文件夹,文件名固定 test.log -->
<!--<param name="DatePattern" value="yyyy-MM-dd/&quot;test.log&quot;" />-->
<!--1.7.3 在根目录下按日期产生文件夹,这是按日期产生文件夹,并在文件名前也加上日期 -->
<!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd&quot;-test.log&quot;" />-->
<!--1.7.4 在根目录下按日期产生文件夹,这再形成下一级固定的文件夹 -->
<!--<param name="DatePattern" value="yyyyMMdd/&quot;OrderInfor/test.log&quot;" />-->
<!--1.8 配置每个日志的大小。【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】可用的单位:KB|MB|GB。不要使用小数,否则会一直写入当前日志,
超出大小后在所有文件名后自动增加正整数重新命名,数字最大的最早写入。-->
<param name="maximumFileSize" value="10MB" />
<!--1.9 最多产生的日志文件个数,超过则保留最新的n个 将value的值设置-1,则不限文件个数 【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】
与1.8中maximumFileSize文件大小是配合使用的-->
<param name="MaxSizeRollBackups" value="5" />
<!--1.10 配置文件文件的布局格式,使用PatternLayout,自定义布局-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="记录时间:%date %n线程ID:[%thread] %n日志级别:%-5level %n出错类:%logger property: [%property{NDC}] - %n错误描述:%message%newline %n%newline"/>
</layout>
</appender> <!--模式二:分文件夹存放-->
<!--文件夹1-->
<appender name="log1" type="log4net.Appender.RollingFileAppender">
<param name="File" value="D:\MyLog\OneLog\" />
<param name="AppendToFile" value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<Encoding value="UTF-8" />
<param name="StaticLogFileName" value="false" />
<param name="RollingStyle" value="Composite" />
<param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
<param name="maximumFileSize" value="10MB" />
<param name="MaxSizeRollBackups" value="5" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message%newline" />
</layout>
<!--下面是利用过滤器进行分文件夹存放,两种过滤器进行配合-->
<!--与Logger名称(OneLog)匹配,才记录,-->
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="OneLog" />
</filter>
<!--阻止所有的日志事件被记录-->
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
<!--文件夹2-->
<appender name="log2" type="log4net.Appender.RollingFileAppender">
<param name="File" value="D:\MyLog\TwoLog\" />
<param name="AppendToFile" value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<Encoding value="UTF-8" />
<param name="StaticLogFileName" value="false" />
<param name="RollingStyle" value="Composite" />
<param name="DatePattern" value="yyyy-MM-dd&quot;.log&quot;" />
<param name="maximumFileSize" value="10MB" />
<param name="MaxSizeRollBackups" value="5" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message%newline" />
</layout>
<!--下面是利用过滤器进行分文件夹存放,两种过滤器进行配合-->
<!--与Logger名称(TwoLog)匹配,才记录,-->
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="TwoLog" />
</filter>
<!--阻止所有的日志事件被记录-->
<filter type="log4net.Filter.DenyAllFilter" />
</appender> <!--2. 输出途径(二) 记录日志到数据库-->
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<!--2.1 设置缓冲区大小,只有日志记录超设定值才会一块写入到数据库-->
<param name="BufferSize" value="1" />
<!--2.2 引用-->
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<!--2.3 数据库连接字符串-->
<connectionString value="data source=localhost;initial catalog=LogDB;integrated security=false;persist security info=True;User ID=sa;Password=123456" />
<!--2.4 SQL语句插入到指定表-->
<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)" />
<!--2.5 数据库字段匹配-->
<!-- 线程号-->
<parameter>
<parameterName value="@threadId" />
<dbType value="String" />
<size value="100" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<!--日志级别-->
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="100" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<!--日志记录类名称-->
<parameter>
<parameterName value="@log_name" />
<dbType value="String" />
<size value="100" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<!--日志信息-->
<parameter>
<parameterName value="@log_msg" />
<dbType value="String" />
<size value="5000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<!--异常信息 指的是如Infor 方法的第二个参数的值-->
<parameter>
<parameterName value="@log_exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
<!-- 日志记录时间-->
<parameter>
<parameterName value="@log_time" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
</appender> <!--(二). 配置日志的的输出级别和加载日志的输出途径-->
<root>
<!--1. level中的value值表示该值及其以上的日志级别才会输出-->
<!--OFF > FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息) > ALL -->
<!--OFF表示所有信息都不写入,ALL表示所有信息都写入-->
<level value="ALL"></level>
<!--2. append-ref标签表示要加载前面的日志输出途径代码 通过ref和appender标签的中name属性相关联--> <appender-ref ref="log0"></appender-ref>
<appender-ref ref="log1"></appender-ref>
<appender-ref ref="log2"></appender-ref> <!--<appender-ref ref="AdoNetAppender"></appender-ref>-->
</root>
</log4net> </configuration>

分享对应的封装类:

 using log4net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace Ypf.Utils.Log
{
public class LogUtils
{
//声明文件夹名称(这里分两个文件夹)
static string log1Name = "OneLog";
static string log2Name = "TwoLog"; //可以声明多个日志对象
//模式一:不分文件夹(所有的log对存放在这一个文件夹下)
public static ILog log = LogManager.GetLogger(typeof(LogUtils)); //模式二:分文件夹
//如果是要分文件夹存储,这里的名称需要和配置文件中loggerToMatch节点中的value相配合
//1. OneLog文件夹
public static ILog log1 = LogManager.GetLogger(log1Name);
//2. TwoLog文件夹
public static ILog log2 = LogManager.GetLogger(log2Name); #region 01-初始化Log4net的配置
/// <summary>
/// 初始化Log4net的配置
/// xml文件一定要改为嵌入的资源
/// </summary>
public static void InitLog4Net()
{
Assembly assembly = Assembly.GetExecutingAssembly();
var xml = assembly.GetManifestResourceStream("Ypf.Utils.Log.log4net.xml");
log4net.Config.XmlConfigurator.Configure(xml);
}
#endregion /************************* 五种不同日志级别 *******************************/
//FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息) #region 00-将调试的信息输出,可以定位到具体的位置(解决高层封装带来的问题)
/// <summary>
/// 将调试的信息输出,可以定位到具体的位置(解决高层封装带来的问题)
/// </summary>
/// <returns></returns>
private static string getDebugInfo()
{
StackTrace trace = new StackTrace(true);
return trace.ToString();
}
#endregion #region 01-DEBUG(调试信息)
/// <summary>
/// DEBUG(调试信息)
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="logName">文件夹名称</param>
public static void Debug(string msg, string logName = "")
{
if (logName == "")
{
log.Debug(getDebugInfo() + msg);
}
else if (logName == log1Name)
{
log1.Debug(msg);
}
else if (logName == log2Name)
{
log2.Debug(msg);
}
}
/// <summary>
/// Debug
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="exception">错误信息</param>
public static void Debug(string msg, Exception exception)
{
log.Debug(getDebugInfo() + msg, exception);
} #endregion #region 02-INFO(一般信息)
/// <summary>
/// INFO(一般信息)
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="logName">文件夹名称</param>
public static void Info(string msg, string logName = "")
{
if (logName == "")
{
log.Info(getDebugInfo() + msg);
}
else if (logName == log1Name)
{
log1.Info(msg);
}
else if (logName == log2Name)
{
log2.Info(msg);
}
}
/// <summary>
/// Info
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="exception">错误信息</param>
public static void Info(string msg, Exception exception)
{
log.Info(getDebugInfo() + msg, exception);
}
#endregion #region 03-WARN(警告)
/// <summary>
///WARN(警告)
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="logName">文件夹名称</param>
public static void Warn(string msg, string logName = "")
{
if (logName == "")
{
log.Warn(getDebugInfo() + msg);
}
else if (logName == log1Name)
{
log1.Warn(msg);
}
else if (logName == log2Name)
{
log2.Warn(msg);
}
}
/// <summary>
/// Warn
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="exception">错误信息</param>
public static void Warn(string msg, Exception exception)
{
log.Warn(getDebugInfo() + msg, exception);
}
#endregion #region 04-ERROR(一般错误)
/// <summary>
/// ERROR(一般错误)
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="logName">文件夹名称</param>
public static void Error(string msg, string logName = "")
{
if (logName == "")
{
log.Error(getDebugInfo() + msg);
}
else if (logName == log1Name)
{
log1.Error(msg);
}
else if (logName == log2Name)
{
log2.Error(msg);
}
}
/// <summary>
/// Error
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="exception">错误信息</param>
public static void Error(string msg, Exception exception)
{
log.Error(getDebugInfo() + msg, exception);
}
#endregion #region 05-FATAL(致命错误)
/// <summary>
/// FATAL(致命错误)
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="logName">文件夹名称</param>
public static void Fatal(string msg, string logName = "")
{
if (logName == "")
{
log.Fatal(getDebugInfo() + msg);
}
else if (logName == log1Name)
{
log1.Fatal(msg);
}
else if (logName == log2Name)
{
log2.Fatal(msg);
}
}
/// <summary>
/// Fatal
/// </summary>
/// <param name="msg">日志信息</param>
/// <param name="exception">错误信息</param>
public static void Fatal(string msg, Exception exception)
{
log.Fatal(getDebugInfo() + msg, exception);
} #endregion }
}

代码测试:

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

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
05-11 10:57