一. 前言

  本节继续探讨一种新的框架搭建模式,框架的结构划分和上一节是相同的,本节IOC框架换成了Unity,并且采用构造函数注入的方式,另外服务层的封装模式也发生了变化,下面将详细的进行探讨。
(一). 技术选型

  1. DotNet框架:4.6

  2. 数据库访问:EF 6.2 (CodeFrist模式)

  3. IOC框架:Unity 5.8.13

  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的程序集,利用【来自数据库的code first】的模式先进行映射,后续通过DataAnnotations 和 FluentAPI混合使用。该层结构如下:

PS:EF的关闭默认策略、EF的DataAnnotations、EF的FluentAPI模式, 在关闭数据库策略的情况下,无论哪种模式都需要显式的 ToTable来映射表名,否则会提示该类找不到。

EF配置详情参考:

         第十五节: EF的CodeFirst模式通过DataAnnotations修改默认协定

         第十六节: EF的CodeFirst模式通过Fluent API修改默认协定

给【Ypf.AdminWeb】层,通过Nuget引入EF的程序集,并配置数据库连接字符串,直接在该层测试数据库访问。【测试通过】

3. Service层和IService层简单的封装一下

  ①.【Ypf.Service】层只有一个BaseService普通类(非泛型)封装,【Ypf.IService】层有设置一个IBaseService接口,BaseService类实现IBaseService接口,里面的方法全部封装为泛型方法

  ②.【Ypf.Service】层中有很多自定义的 xxxService,每个xxxService都要实现【Ypf.IService】层的IxxxService层接口,同时继承BaseService类,这里的xxxService层划分并不依赖表名划分,自定义根据业务合理起名即可。

  ③. xxxService类中,在构造函数中传入DbContext db,但此处并不实例化,而是利用Unity进行构造函数的注入,所有的子类xxxService类中,都注入相应的EF上下文,这样就不需要手动再传入了(这里需要特别注意:Unity默认支持构造函数注入,只要xxxService被配置,那么该类中的(参数最多)的构造函数中的参数类即可以进行注入,只要在配置文件中配置上即可实现注入)。

  ④.在Unity的配置文件中进行配置IOC,在控制器中进行构造函数注入。

  ⑤ . 控制器中的Action仅仅负责传值和简单的一些判断,核心业务全部都写在Service层中。

  ⑥.  子类xxxService中的方法中,可以直接通过 this.XXX<T>的方式调用父类BaseService中的泛型方法,db是通过子类构造函数传到父类BaseService构造函数中。

分享BaseService类和IBaseService接口:

 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.IService; namespace Ypf.Service
{
public class BaseService: IBaseService
{
/// <summary>
/// 一个属性,在该类中使用
/// </summary>
public DbContext db { get; private set; } /// <summary>
/// 通过构造函数传入EF的上下文
/// 该上下文可能是同种类型的不同数据库、也可能是相同结构的不同类型的数据库
/// 为后面的Untiy的构造函数注入埋下伏笔
/// </summary>
/// <param name="db"></param>
public BaseService(DbContext db)
{
this.db = db;
} //1. 直接提交数据库 #region 01-数据源
public IQueryable<T> Entities<T>() where T : class
{
return db.Set<T>();
} #endregion #region 02-新增
public int Add<T>(T model) where T : class
{
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>(T model) where T : class
{
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<T>(Expression<Func<T, bool>> delWhere) where T : class
{
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>(T model) where T : class
{
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>(T model, Expression<Func<T, bool>> whereLambda, params string[] proNames) where T : class
{
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<T>(Expression<Func<T, bool>> whereLambda) where T : class
{
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<T,Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) where T : class
{
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<T,Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) where T : class
{ 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<T,Tkey>(int pageIndex, int pageSize, ref int rowCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) where T : class
{
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>(T model) where T : class
{
db.Set<T>().Add(model);
}
#endregion #region 03-删除
/// <summary>
/// 删除
/// </summary>
/// <param name="model">需要删除的实体</param>
public void DelNo<T>(T model) where T : class
{
db.Entry(model).State = EntityState.Deleted;
}
#endregion #region 04-根据条件删除
/// <summary>
/// 条件删除
/// </summary>
/// <param name="delWhere">需要删除的条件</param>
public void DelByNo<T>(Expression<Func<T, bool>> delWhere) where T : class
{
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>(T model) where T : class
{
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) where T : class
{
return db.Database.SqlQuery<T>(sql, pars).ToList();
}
#endregion }
}

BaseService类

 using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks; namespace Ypf.IService
{
public interface IBaseService
{
//1. 直接提交数据库 #region 01-数据源
IQueryable<T> Entities<T>() where T : class; #endregion #region 02-新增
int Add<T>(T model) where T : class; #endregion #region 03-删除(适用于先查询后删除 单个)
/// <summary>
/// 删除(适用于先查询后删除的单个实体)
/// </summary>
/// <param name="model">需要删除的实体</param>
/// <returns></returns>
int Del<T>(T model) where T : class; #endregion #region 04-根据条件删除(支持批量删除)
/// <summary>
/// 根据条件删除(支持批量删除)
/// </summary>
/// <param name="delWhere">传入Lambda表达式(生成表达式目录树)</param>
/// <returns></returns>
int DelBy<T>(Expression<Func<T, bool>> delWhere) where T : class; #endregion #region 05-单实体修改
/// <summary>
/// 修改
/// </summary>
/// <param name="model">修改后的实体</param>
/// <returns></returns>
int Modify<T>(T model) where T : class; #endregion #region 06-批量修改(非lambda)
/// <summary>
/// 批量修改(非lambda)
/// </summary>
/// <param name="model">要修改实体中 修改后的属性 </param>
/// <param name="whereLambda">查询实体的条件</param>
/// <param name="proNames">lambda的形式表示要修改的实体属性名</param>
/// <returns></returns>
int ModifyBy<T>(T model, Expression<Func<T, bool>> whereLambda, params string[] proNames) where T : class; #endregion #region 07-根据条件查询
/// <summary>
/// 根据条件查询
/// </summary>
/// <param name="whereLambda">查询条件(lambda表达式的形式生成表达式目录树)</param>
/// <returns></returns>
List<T> GetListBy<T>(Expression<Func<T, bool>> whereLambda) where T : class; #endregion #region 08-根据条件排序和查询
/// <summary>
/// 根据条件排序和查询
/// </summary>
/// <typeparam name="Tkey">排序字段类型</typeparam>
/// <param name="whereLambda">查询条件</param>
/// <param name="orderLambda">排序条件</param>
/// <param name="isAsc">升序or降序</param>
/// <returns></returns>
List<T> GetListBy<T, Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) where T : class; #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>
List<T> GetPageList<T, Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) where T : class; #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>
List<T> GetPageList<T, Tkey>(int pageIndex, int pageSize, ref int rowCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) where T : class; #endregion //2. SaveChange剥离出来,处理事务 #region 01-批量处理SaveChange()
/// <summary>
/// 事务批量处理
/// </summary>
/// <returns></returns>
int SaveChange(); #endregion #region 02-新增
/// <summary>
/// 新增
/// </summary>
/// <param name="model">需要新增的实体</param>
void AddNo<T>(T model) where T : class; #endregion #region 03-删除
/// <summary>
/// 删除
/// </summary>
/// <param name="model">需要删除的实体</param>
void DelNo<T>(T model) where T : class; #endregion #region 04-根据条件删除
/// <summary>
/// 条件删除
/// </summary>
/// <param name="delWhere">需要删除的条件</param>
void DelByNo<T>(Expression<Func<T, bool>> delWhere) where T : class; #endregion #region 05-修改
/// <summary>
/// 修改
/// </summary>
/// <param name="model">修改后的实体</param>
void ModifyNo<T>(T model) where T : class; #endregion //3. EF调用sql语句 #region 01-执行增加,删除,修改操作(或调用存储过程)
/// <summary>
/// 执行增加,删除,修改操作(或调用存储过程)
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
/// <returns></returns>
int ExecuteSql(string sql, params SqlParameter[] pars); #endregion #region 02-执行查询操作
/// <summary>
/// 执行查询操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="pars"></param>
/// <returns></returns>
List<T> ExecuteQuery<T>(string sql, params SqlParameter[] pars) where T : class; #endregion }
}

IBaseService接口

4. 利用Unity进行整合

  (1). 通过Nuget给【Ypf.Utils】层引入“Unity”的程序集和“Microsoft.AspNet.Mvc”程序集。

  (2). 新建类:DIFactory 用于读取Unity配置文件创建Unity容器。需要引入程序集“System.Configuration”

        新建类:UnityControllerFactory 用于自定义控制器实例化工厂。需要引入程序集“System.Web”。

分享代码:

 using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity; namespace Ypf.Utils
{
/// <summary>
/// 依赖注入工厂(单例的 采用双if+lock锁)
/// 读取Unity的配置文件,并创建Unity容器
/// </summary>
public class DIFactory
{
//静态的私有变量充当Lock锁
private static object _lock = new object();
private static Dictionary<string, IUnityContainer> _UnityDictory = new Dictionary<string, IUnityContainer>(); /// <summary>
/// 获取Unity容器
/// </summary>
/// <param name="containerName">对应配置文件中节点的名称,同时也当做字典中的key值</param>
/// <returns></returns>
public static IUnityContainer GetContainer(string containerName = "EFContainer")
{
if (!_UnityDictory.ContainsKey(containerName))
{
lock (_lock)
{
if (!_UnityDictory.ContainsKey(containerName))
{
//1. 固定的4行代码读取配置文件
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\UnityConfig.xml");//找配置文件的路径
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
//2. Unity层次的步骤
IUnityContainer container = new UnityContainer();
section.Configure(container, containerName);
//3.将创建好的容器放到字典里
_UnityDictory.Add(containerName, container);
}
}
}
return _UnityDictory[containerName];
}
}
}

DIFactory

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using System.Web.Routing;
using Unity; namespace Ypf.Utils
{
/// <summary>
/// 自定义控制器实例化工厂
/// </summary>
public class UnityControllerFactory : DefaultControllerFactory
{
private IUnityContainer UnityContainer
{
get
{
return DIFactory.GetContainer();
}
} /// <summary>
/// 创建控制器对象
/// </summary>
/// <param name="requestContext"></param>
/// <param name="controllerType"></param>
/// <returns></returns>
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (null == controllerType)
{
return null;
}
IController controller = (IController)this.UnityContainer.Resolve(controllerType);
return controller;
} /// <summary>
/// 释放控制器
/// </summary>
/// <param name="controller"></param>
public override void ReleaseController(IController controller)
{
//this.UnityContainer.Teardown(controller);//释放对象(老版本) base.ReleaseController(controller);
}
}
}

UnityControllerFactory

  (3). 通过Nuget给【Ypf.AdminWeb】层引入“Unity”的程序集,并新建CfgFiles文件夹和UnityConfig.xml文件,该xml文件需要改属性为“始终复制”。

分享代码:

 <configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
</configSections>
<unity>
<!-- unity容器配置注册节点-->
<containers>
<!--容器配置方式一:类型名称和程序集名称全部写在容器中-->
<container name="EFContainer">
<!-- type中的两个参数分别是:类型名称和DLL程序集的名称 -->
<!-- mapTo中的两个参数分别是:类型名称和DLL程序集的名称 -->
<!--
分析:这里我们需要使用的是TestService,但不直接使用它,而是使用它的接口,即将【mapTo】里的类型注册给【type】里的类型
-->
<register type="Ypf.IService.ITestService,Ypf.IService" mapTo="Ypf.Service.TestService,Ypf.Service"/>
<register type="Ypf.IService.ITestService2,Ypf.IService" mapTo="Ypf.Service.TestService2,Ypf.Service"/>
<!--调用构造函数注入-->
<!--1.TestService需要依赖BaseService的构造函数,所以要对其进行注入-->
<register type="Ypf.IService.IBaseService,Ypf.IService" mapTo="Ypf.Service.BaseService, Ypf.Service"/>
<!--2.TestService需要传入EF的上下文,所以要对其进行注入-->
<register type="System.Data.Entity.DbContext, EntityFramework" mapTo="Ypf.Data.MyDBContext1, Ypf.Data" name="db"/>
<register type="System.Data.Entity.DbContext, EntityFramework" mapTo="Ypf.Data.MyDBContext2, Ypf.Data" name="db2"/> </container>
</containers>
</unity>
</configuration>

UnityConfig.xml

  (4). 将【Ypf.Service】层的程序集生成路径改为:..\Ypf.AdminWeb\bin\

  (5). 在【Ypf.AdminWeb】层中的Global文件中进行注册 ,用Unity代替原有的控制器创建流程.

    //注册自定义实例化控制器的容器(利用Unity代替原有的控制器创建流程)

    ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory());

 PS:横向对比,AutoFac中也有一句类似的话:

  在【Ypf.AdminWeb】层测试Untiy的IOC和DI【测试通过】

5. 将Log4net整合到Ypf.Utils层中。

  (1). 通过Nuget给【Ypf.Utils】层添加“Log4net”程序集。

  (2). 新建Log文件,拷贝“log4net.xml”和“LogUtils.cs”两个类文件,“log4net.xml”要改为嵌入的资源。

  (3). 在【Ypf.Admin】层的Global文件中进行注册。LogUtils.InitLog4Net();

  (4). 解析:主要配置了两种模式,输出到“txt文本文档”和“SQLServer数据库中”。其中“文本文档”又分了两种模式,全部输入到一个文档中 和 不同类型的日志输入到不同文档下,在调用的时候通过传入参数来区分存放在哪个文件夹下。

代码详见下面的实战测试。

6. 完善【Ypf.Service】层中BaseService的封装,封装EF常用的增删改查的方法,这里暂时先不扩展EF插件的方法。

代码见上

7.  如何控制EF上下文中的生命周期呢?

  在配置文件中可以通过lifetime这个节点进行配置,而上一套框架的模式是直接通过using的模式进行配置,这里可以使用默认的方式:每次使用时候都创建。

详见Unity专题:

  Unity深入浅出(一)

  Unity深入浅出(二)

三. 剖析核心

1. 相同数据库结构,不同类型的数据库如何快速切换。

解析:首先需要明白的是不同的数据库切换,实质上切换的就是 EF的上下文,该框架的模式EF的是使用Unity通过xxxService中子类的构造函数注入,需要在配置文件中配置构造函数注入EF上下文。

    <register type="System.Data.Entity.DbContext, EntityFramework" mapTo="Ypf.Data.MyDBContext1, Ypf.Data" />

 所以这里切换数据库(eg:SQLServer→MySQL)只需要通过Nuget引出EF对应数据库的程序集,编写好配置文件,将SQLServer的EF上下文(MyDbContext1)切换成MySQL的上下文即可。

【需要测试】

2. 在一个方法中如何同时访问多个数据库,并对其进行事务一体的增删改操作。

 解析:首先需要在BaseService的构造函数参数拼写多个DbContext参数,

 其次子类xxxService中同样也需要多个DbContext参数,当多个参数时候,需要通过命名的方式指定注入,否则相互覆盖,不能分别注入。

 最后,Unity的配置文件也需要通过命名的方式进行注入。

思考,Dependency特性写在父类BaseService中是否可以?

答案:经测试,不可以,EF的命名方式的构造函数注入要写在子类xxxService中。

同时会带来一个弊端?

由于BaseSevice类中泛型方法中的db,直接使用默认一个数据库的时候的db属性,所有导致当一个方法中如果涉及到多个上下文,没法直接使用BaseService中的封装方法,需要写原生代码,有点麻烦。

如下图:

那么如何解决这个问题?

3. 连接多个数据库框架的局限性,如何改进。

 将BaseSevice中的泛型方法使用的db通过参数的形式进行传入,而且默认为一个数据库时候对应的DbContext属性,这样当只有一个数据库的时候,不用管它,因为他有默认值;当需要同时操控数据库的时候,在子类XXXService中,根据需要传入相应的db接口。

【经测试,不可以,提示 默认参数必须是编译时候的常量】

 后续将采用别的方案进行处理,请期待。

四. 实战测试

这里准备两个数据库,分别是:YpfFrame_DB 和 YpfFrameTest_DB

①:YpfFrame_DB中,用到了表:T_SysUser 和 T_SysLoginLog,表结构如下

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

开始测试

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

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

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

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

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

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

在ITestService接口中定义ManyDBTransaction方法,并在TestService中实现该方法,同样需要在构造函数中注入多个EF上下文,代码如下:

      [InjectionConstructor]
public TestService([Dependency("db")]DbContext db, [Dependency("db2")]DbContext db2) : base(db, db2)
{ }
    /// <summary>
/// 3. 同时对多个数据库进行事务一体的CRUD操作
/// 注:需要手动开启msdtc服务(net start msdtc)
/// </summary>
public void ManyDBTransaction()
{
using (TransactionScope trans = new TransactionScope())
{
try
{
var data1 = db.Set<T_SysUser>().Where(u => u.id == "").FirstOrDefault();
if (data1 != null)
{
db.Set<T_SysUser>().Attach(data1);
db.Set<T_SysUser>().Remove(data1);
db.SaveChanges();
}
var data2 = db2.Set<T_SchoolInfor>().Where(u => u.id == "").FirstOrDefault();
if (data2 != null)
{
db2.Set<T_SchoolInfor>().Attach(data2);
db2.Set<T_SchoolInfor>().Remove(data2);
db2.SaveChanges();
} //最终提交事务
trans.Complete();
}
catch (Exception ex)
{
var msg = ex.Message;
//事务回滚
Transaction.Current.Rollback();
throw;
}
}
}

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

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

PS:为了与前一节中AutoFac相呼应

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

  public class TestService2 : BaseService, ITestService2
{
/// <summary>
/// 调用父类的构造函数,这里的db通过Unity的配置文件实现构造函数注入
/// </summary>
/// <param name="db"></param>
[InjectionConstructor]
public TestService2([Dependency("db")]DbContext db, [Dependency("db2")]DbContext db2) : base(db, db2)
{ } public List<T_SysUser> GetUserInfor()
{
return this.GetListBy<T_SysUser>(u => true);
}
}

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

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

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 }
}

代码测试

五. 后续

  后续将对比 Unity和AutoFac,对比这两套框架的搭建模式。

!

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

第十节:基于MVC5+Unity+EF+Log4Net的基础结构搭建的更多相关文章

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

    一. 前言 从本节开始,将陆续的介绍几种框架搭建组合形式,分析每种搭建形式的优势和弊端,剖析搭建过程中涉及到的一些思想和技巧. (一). 技术选型 1. DotNet框架:4.6 2. 数据库访问:E ...

  2. ASP.NET MVC深入浅出(被替换) 第一节: 结合EF的本地缓存属性来介绍【EF增删改操作】的几种形式 第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery ) 第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法 第六节: EF高级属性(二) 之延迟加载、立即加载、显示加载(含导航属性) 第十节: EF的三种追踪

    ASP.NET MVC深入浅出(被替换)   一. 谈情怀-ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态 ...

  3. centos LAMP第二部分apache配置 下载discuz!配置第一个虚拟主机 安装Discuz! 用户认证 配置域名跳转 配置apache的访问日志 配置静态文件缓存 配置防盗链 访问控制 apache rewrite 配置开机启动apache tcpdump 第二十节课

    centos    LAMP第二部分apache配置  下载discuz!配置第一个虚拟主机 安装Discuz! 用户认证 配置域名跳转  配置apache的访问日志  配置静态文件缓存  配置防盗链 ...

  4. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第十节

    原文链接 第十节:CUDPP, 强大的数据平行CUDA库Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多 ...

  5. centos shell编程6一些工作中实践脚本 nagios监控脚本 自定义zabbix脚本 mysql备份脚本 zabbix错误日志 直接送给bc做计算 gzip innobackupex/Xtrabackup 第四十节课

    centos   shell编程6一些工作中实践脚本   nagios监控脚本 自定义zabbix脚本 mysql备份脚本 zabbix错误日志  直接送给bc做计算  gzip  innobacku ...

  6. J2EE进阶(十八)基于留言板分析SSH工作流程

    J2EE进阶(十八)基于留言板分析SSH工作流程   留言板采用SSH(Struts1.2 + Spring3.0 + Hibernate3.0)架构.   工作流程(以用户登录为例):   首先是用 ...

  7. 第十节:详细讲解一下Java多线程,随机文件

    前言 大家好,给大家带来第十节:详细讲解一下Java多线程,随机文件的概述,希望你们喜欢 多线程的概念 线程的生命周期 多线程程序的设计 多线程的概念 多线程的概念:程序是静态的,进程是动态的.多进程 ...

  8. 第三百八十节,Django+Xadmin打造上线标准的在线教育平台—将所有app下的models数据库表注册到xadmin后台管理

    第三百八十节,Django+Xadmin打造上线标准的在线教育平台—将所有app下的models数据库表注册到xadmin后台管理 将一个app下的models数据库表注册到xadmin后台管理 重点 ...

  9. 第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页

    第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页 逻辑处理函数 计算搜索耗时 在开始搜索前:start_time ...

随机推荐

  1. Ubuntu下crontab启动、重启、关闭命令

    在Ubuntu14.04环境下,利用crontab编写shell脚本程序,定时执行php相关程序.在这个过程中,经常使用到的crontab命令如下: (root权限下) crontab启动:/etc/ ...

  2. 什么是tomcat集群?

    什么是tomcat集群? 利用nginx对请求进行分流,将请求分配给不同的tomcat去处理,减少每个tomcat的负载量,提高服务器的响应速度. 目标 实现高性能负载均衡的tomcat集群. 工具 ...

  3. SAP CRM 集类型(Set Type)与产品层次(Product Hierarchy)

    本文是产品与对象相关的部分SAP文档的翻译,不包含配置部分. 本文链接:https://www.cnblogs.com/hhelibeb/p/10112723.html 1,对象(Objects) 对 ...

  4. China Tightens Recycling Import Rules

    China Tightens Recycling Import Rules We have all seen the pictures of cities in China with air poll ...

  5. LinuxMint上安装redis和python遇到的一些问题

    今天在安装Redis和Python上遇到了些问题,解决后记录下来. 环境:LinuxMint 18.3 安装redis sudo wget http://download.redis.io/relea ...

  6. 最小化spring XML配置,Spring提供了4种自动装配策略。

    1.ByName自动装配:匹配属性的名字 在配置文件中的写法: <bean name="course" class="course类的全包名">&l ...

  7. LeetCode练习4 找出这两个有序数组的中位数

    给定两个大小为 m 和 n 的有序数组 nums1 和 nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)). 你可以假设 nums1 和 nums2  ...

  8. day 14 递归、匿名函数、内置函数

    三目运算符 # 三目(元)运算符:就是 if...else...语法糖# 前提:简化if...else...结构,且两个分支有且只有一条语句# 注:三元运算符的结果不一定要与条件直接性关系​cmd = ...

  9. zookeeper的分布式锁

    实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,其中前两种方案网络上有很多资料可以参考,本文不做展开.我们来看下使用Zookeeper如何实现分布式锁. 什么是 ...

  10. Golang 入门系列(六)理解Go中的协程(Goroutine)

    前面讲的都是一些Go 语言的基础知识,感兴趣的朋友可以先看看之前的文章.https://www.cnblogs.com/zhangweizhong/category/1275863.html. 今天就 ...