注:此博客僅適合於剛入門的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字段)進行定義的
寫到後面,發現有點亂了,不知怎麼表達我想表達的東西了,就這樣吧,也不是多麼有技術含量的設計,有興趣地看我代碼吧。