做CURD開發的過程中,通常都會需要GetList,然而查詢條件是一個可能變化的需求,如何從容對應需求變化呢?
首先,我們來設計一個套路,嘗試以最小的工作量完成一次查詢條件的需求變更
1.UI收集查詢數據
2.UI將查詢數據傳遞給Service
3.Service從查詢配置(數據庫、JSON、XML)中匹配出查詢條件,並賦予UI取得的值
4.Service根據查詢配置(已賦值)構建查詢表達式。
5.執行查詢返回數據。
大概流程如下圖所示:
下面上代碼,希望有人能看懂 ><
查詢保存設置
public interface IEntity { int Id { get; set; } } public class QueryCondition : IEntity { [Key] public int Id { get; set; } /// <summary> /// 條件分組:以此做為查詢條件 /// </summary> public string Group { get; set; } /// <summary> /// 字段名稱 /// </summary> public string FieldName { get; set; } public int CompareType { get; set; } public int CompareDataType { get; set; } public string Value { get; set; } }
查詢條件DTO模型
/// <summary> /// 查詢結構 /// </summary> public class QueryConditionModel { public string FieldName { get; set; } public CompareType Type { get; set; } public CompareDataType DataType { get; set; } public string Value { get; set; } } public enum CompareType { Equal = 1, GreaterThan = 2, GreaterThanOrEqual = 3, LessThan = 4, LessThanOrEqual = 5, Include = 6, } public enum CompareDataType { Int = 1, String = 2, Double = 3, Decimal = 4, Float = 5, DateTime = 6 }
查詢條件DTO轉換配置
public class QueryConditionProfile : Profile { [Obsolete("")] protected override void Configure() { CreateMap<QueryCondition, QueryConditionModel>() .ForMember(p => p.Type, opt => { opt.MapFrom(k => (CompareType)k.CompareType); }) .ForMember(p => p.DataType, opt => { opt.MapFrom(k => (CompareDataType)k.CompareDataType); }) ; } }
查詢條件構建
public class ServiceBase { protected XXXDbContext Ctx; /// <summary> /// 動態構建Lambda查詢表達式 /// </summary> /// <param name="searchItems"></param> /// <returns></returns> protected Expression<Func<T, bool>> BuildExpression<T>(IList<QueryConditionModel> searchItems) { var where = PredicateExtensionses.True<T>(); if (!searchItems.Any()) return @where; foreach (var subitem in searchItems) { try { var field = subitem.FieldName; var compare = subitem.Type; var type = subitem.DataType; var value = subitem.Value; if (string.IsNullOrEmpty(field)) continue; if (string.IsNullOrEmpty(value)) continue; //構建Lambda表達式 var parameter = Expression.Parameter(typeof(T), "p"); Expression constant; //表達式左側 like: p.Name var left = Expression.PropertyOrField(parameter, field); //表達式右側,比較值, like '張三' var right = Expression.Constant(value); //比較表達式 switch (compare) { case CompareType.GreaterThan: constant = Expression.GreaterThan(left, right); break; case CompareType.GreaterThanOrEqual: constant = Expression.GreaterThanOrEqual(left, right); break; case CompareType.LessThan: constant = Expression.LessThan(left, right); break; case CompareType.LessThanOrEqual: constant = Expression.LessThanOrEqual(left, right); break; case CompareType.Include: //like 查詢,需要調用外部int或string的Contains方法 var method = type == CompareDataType.Int ? typeof(int).GetMethod("Contains", new Type[] { typeof(int) }) : typeof(string).GetMethod("Contains", new Type[] { typeof(string) }); constant = Expression.Call(left, method, right); break; case CompareType.Equal: default: constant = Expression.Equal(left, right); break; } var lambda = Expression.Lambda<Func<T, Boolean>>(constant, parameter); @where = @where.And(lambda); } catch (Exception ex) { //OnMethodExecuted(JsonConvert.SerializeObject(searchItems), JsonConvert.SerializeObject(ex), "", LogType.Error); } } return @where; } protected Expression<Func<T, bool>> GenerateConditions<T>(Dictionary<string, string> conditions, string fieldGroup) { //read query condition define var fields = Ctx.QueryConditions.Where(p => p.Group == fieldGroup).ToList(); //read value from client conditions foreach (var condition in conditions) { SetValue(fields, condition.Key, condition.Value); } var businessCondigions = fields.Select(Mapper.Map<EntityFramework.QueryCondition, QueryConditionModel>).ToList(); return BuildExpression<T>(businessCondigions); } private void SetValue(IList<EntityFramework.QueryCondition> conditions, string name, string value) { var field = conditions.FirstOrDefault(p => p.FieldName == name); if (field == null) return; field.Value = value; } }
調用示例:
public IList<CustomerListModel> GetList(Dictionary<string,string> conditions, Pager pager) { try { var skip = (pager.PageIndex - 1) * pager.PageSize; var where = GenerateConditions<EntityFramework.Customer>(conditions, "CustomerQueryModel"); var query = Ctx.Customers.Include("MemberCard").WhereIf<EntityFramework.Customer>(where, pager); var list = query.Skip(skip).Take(pager.PageSize).ToList(); var ret = new List<CustomerListModel>(); foreach (var customer in list) { ret.Add(Mapper.Map<EntityFramework.Customer, CustomerListModel>(customer)); } //OnMethodExecuted("GetList", "", "", LogType.Operate); return ret; } catch (Exception ex) { //OnErrorThrow(JsonConvert.SerializeObject(conditions), JsonConvert.SerializeObject(ex), ex.Message); throw ex; } }