做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 = ,
GreaterThan = ,
GreaterThanOrEqual = ,
LessThan = ,
LessThanOrEqual = ,
Include = ,
}
public enum CompareDataType
{
Int = ,
String = ,
Double = ,
Decimal = ,
Float = ,
DateTime =
}
查询条件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 - ) * 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;
}
}