注:此博客仅适合于刚入门的Asp.net Core初学者,仅作参考。git
学了3个月的Asp.net Core,将以前一个系统(http://caijt.com/it)的PHP后端用Asp.net Core重写了一遍,http://it.caijt.com:1001 (注:是日本服务器,比较慢),刚入门时,我是想用DDD或ABP这种高大上的框架来重写我以前的系统,后面我发现这些概念对我这个刚入门的初学者来讲,理解起来仍是有点困难,也可能我经历系统仍是比较简单,用这些框架反而会比较麻烦。github
代码:https://github.com/Caijt/ItSysAspNetCore数据库
如下是个人分层图,很是简单的分层,连标准三层都不是,用了EF我以为Respository仓储层不必,若是是用Sql或Dapper的话,就会加个Respository层后端
用框架的目标都是一致的,不写重复的代码!对于框架,个人理解就是把通用的重复的代码提取出来,写成一个基类,而后在那么须要个性化的地方挖坑,派生类中再对这些坑进行补充,这样就实现了每一个派生类有基类的通用代码,也能有派生类独特的代码。每一个派生类只写跟别人不同的代码,不写重复性代码。服务器
可能说得还不太能准确表达我想说的意思,下面以代码展现。app
例如查询列表GetList功能,我用EF的话,那我IT资产及IT合同的Service类,须要这样写框架
//IT资产查询列表方法 public List<ItAssetDto> GetList(ItAssetQueryDto queryParams) { var query = dbContext.Set<ItAsset>().AsQueryable(); query = query.Include(e => e.CreateUser); #region 资产编号 if (!string.IsNullOrWhiteSpace(queryParams.no)) { query = query.Where(e => e.no.Contains(queryParams.no)); } #endregion #region 资产型号 if (!string.IsNullOrWhiteSpace(queryParams.model)) { query = query.Where(e => e.no.Contains(queryParams.model)); } #endregion #region 标识号 if (!string.IsNullOrWhiteSpace(queryParams.diy_no)) { query = query.Where(e => e.diy_no.Contains(queryParams.diy_no)); } #endregion if (queryParams.sortOrder == "no") { query = query.OrderBy(e => e.no); } if (queryParams.sortOrder == "inbound_date") { query = query.OrderBy(e => e.inbound_date); } return query.Select(e => new ItAssetDto { no = e.no, inbound_date = e.inbound_date, id = e.Id }) .ToList(); }
//IT合同查询列表方法 public List<ItContractDto> GetList(ItContractQueryDto queryParams) { var query = dbContext.Set<ItContract>().AsQueryable(); query = query.Include(e => e.CreateUser).Include(e=>e.Supplier); #region 合同编号 if (!string.IsNullOrWhiteSpace(queryParams.no)) { query = query.Where(e => e.no.Contains(queryParams.no)); } #endregion #region 合同名称 if (!string.IsNullOrWhiteSpace(queryParams.name)) { query = query.Where(e => e.name.Contains(queryParams.name)); } #endregion return query.Select(e => new ItContractDto { no = e.no, name = e.name, id = e.Id }) .ToList(); }
有没有从其中发现一些重复,但又不重复的地方,重复的是 dbContext.Set<实体>().Include().Where().OrderBy().Select().ToList();不一样的是每一个实体表它的Where、Include、OrderBy、Select都不太同样。那我就在这些地方挖坑。this
以下代码,我定义了一个基类,里面有selectExpression、 onInclude、onWhere、orderProp属性,这些都是我挖的坑,哈哈。而后定义了一个通用的GetList方法,那么派生类继承于这个基类后,不用写任何方法,就有了通用的GetList方法,若是须要具备字段查询功能或字段排序功能的话,就在派生类的构造方法里对这些坑进行赋值。spa
using AutoMapper; using ItSys.Dto; using ItSys.EntityFramework; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; namespace ItSys.Service.Base { public class BaseService<TEntity, TDto, TQueryDto> where TEntity : class where TQueryDto : IQueryDto { protected ItSysDbContext dbContext; protected IMapper mapper; /// <summary> /// 实体转化为Dto对象的表达式 /// </summary> protected Expression<Func<TEntity, TDto>> selectExpression { get; set; } /// <summary> /// 构建Include关联属性数据 /// </summary> /// <param name="query"></param> /// <returns></returns> protected Func<IQueryable<TEntity>, IQueryable<TEntity>> onInclude { get; set; } /// <summary> /// 构建Where查询 /// </summary> /// <param name="query"></param> /// <returns></returns> protected Func<IQueryable<TEntity>, TQueryDto, IQueryable<TEntity>> onWhere { get; set; } /// <summary> /// 根据排序字段的字符串返回一个排序表达式 /// </summary> protected Func<string, Expression<Func<TEntity, dynamic>>> orderProp { get; set; } public BaseService(ItSysDbContext dbContext, IMapper mapper) { this.dbContext = dbContext; this.mapper = mapper; selectExpression = e => mapper.Map<TDto>(e); } protected List<TDto> GetList(TQueryDto queryParams) { var query = dbContext.Set<TEntity>().AsNoTracking(); #region 加载导航属性 if (onInclude != null) { query = onInclude(query); } #endregion #region 查询条件 if (onWhere != null) { query = onWhere(query, queryParams); } #endregion #region 排序 var exp = orderProp != null ? orderProp(queryParams.orderProp) : null; if (exp != null) { query = queryParams.orderDesc.GetValueOrDefault(true) ? query.OrderByDescending(exp) : query.OrderBy(exp); } #endregion return query.Select(selectExpression).ToList(); } } }
下面是上面代码IQueryDto对象接口的定义代码.net
namespace ItSys.Dto { public interface IQueryDto { /// <summary> /// 每页数量 /// </summary> int pageSize { get; set; } /// <summary> /// 当前页 /// </summary> int currentPage { get; set; }/// <summary> /// 排序字段 /// </summary> string orderProp { get; set; } /// <summary> /// 是否倒序排序 /// </summary> bool? orderDesc { get; set; } } }
如今的IT资产的Service类就能够很简单了,继承BaseService,泛型类型第一个是实体类型ItAsset,第二个是对应的Dto对象ItAssetDto,第三个是实现了IQueryDto接口的查询参数对象ItAssetQueryDto,而后不用写一个方法,只要在构造方法里对onWhere、OrderProp及SelectExpression属性配置就行了。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using AutoMapper; using ItSys.Dto; using ItSys.Entity; using ItSys.EntityFramework; using ItSys.Service.Base; namespace ItSys.Service.It { public class ItAssetService : BaseService<ItAsset, ItAssetDto, ItAssetQueryDto> { public ItAssetService(ItSysDbContext dbContext, IMapper mapper) : base(dbContext, mapper) { //定义Where的坑 onWhere = (query, queryParams) => { #region 资产编号 if (!string.IsNullOrWhiteSpace(queryParams.no)) { query = query.Where(e => e.no.Contains(queryParams.no)); } #endregion #region 资产型号 if (!string.IsNullOrWhiteSpace(queryParams.model)) { query = query.Where(e => e.no.Contains(queryParams.model)); } #endregion return query; }; //定义Order的坑 orderProp = prop => { switch (prop) { case "create_time": return e => e.CreateTime; case "update_time": return e => e.UpdateTime; case "no": return e => e.no; } return null; }; //定义Select的坑 selectExpression = e => new ItAssetDto { id = e.Id, no = e.no, model = e.model }; } } }
按照这个思路,给Create,Update,Delete方法也挖坑,我是在三个方法的以前跟以后分别挖了两个坑,由于有些实体建立时我须要给某些字段定义初始值,例如create_time字段,我能够在onBeforeCreate给实体初始化create_time值,有些实体我须要在更新时定义字段值,例如update_time,我在onBeforeUpdate初始化update_time的值,有些实体的删除,我须要在删除以前查询此实体跟其它表还有没有关联,我能够在onBeforeDelete中查询。
下面以Create代码为例
protected Action<TEntity, TCreateDto> onBeforeCreate { get; set; } protected Action<TEntity, TCreateDto> onAfterCreate { get; set; } /// <summary> /// 建立实体 /// </summary> /// <returns></returns> public virtual TDto Create(TCreateDto createDto) { var entity = mapper.Map<TEntity>(createDto); if (onBeforeCreate != null) { onBeforeCreate(entity, createDto); } dbSet.Add(entity); dbContext.SaveChanges(); if (onAfterCreate != null) { onAfterCreate(entity, createDto); } return mapper.Map<TDto>(entity); }
若是从github下载了个人代码看后会发现里面的代码跟上面的代码还有很大差异,由于我把方法拆得更细,主要考虑一些特殊状况,方法拆细点,能够实现更多特殊操做,不过思路是同样的,都是按上面的方式,在适合的地点挖坑。
介绍我这几个主要的Service基类,我是实体的一些通用特色(例如说某些实体都有Id主键,某些实体都有create_time、update_time字段)进行定义的
写到后面,发现有点乱了,不知怎么表达我想表达的东西了,就这样吧,也不是多么有技术含量的设计,有兴趣地看我代码吧。