zoukankan      html  css  js  c++  java
  • 第九节:基于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:MyLogOneLog" />
     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:MyLogTwoLog" />
     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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    Codewars Solution:Two to One
    Codewars Solution:Get the Middle Character
    golang jwt+token验证
    C#基础知识点梳理二
    C#基础知识点梳理一
    逻辑值
    step-1
    视频第5讲-ID转换
    通过向量访问矩阵
    跟着jmzeng学习GEO数据分析-GEO42872_1--题外
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/9967526.html
Copyright © 2011-2022 走看看