起因
某些业务,组装的表达式比较长,且、或、括号混杂一块难以立即理解:
public async Task<List<PatientMedicineScheduleInfo>> GetPatientMedicineSchedule(string inpatientID, string hospitalID, DateTime startDate, TimeSpan startTime, DateTime endDate, TimeSpan endTime)
{return await _medicalDbContext.PatientMedicineScheduleInfos.Where(m =>((m.ScheduleDate == startDate.Date && m.ScheduleTime >= startTime) || m.ScheduleDate > startDate.Date)&& (m.ScheduleDate < endDate.Date || (m.ScheduleDate == endDate.Date && m.ScheduleTime <= endTime))&& m.InpatientID == inpatientID && m.HospitalID == hospitalID&& m.DeleteFlag != "*").OrderBy(m => m.ScheduleDate).ThenBy(m => m.ScheduleTime).ThenBy(m => m.GroupID).ThenBy(m => m.HISOrderSort).ToListAsync();
}
我们可以使用表达式树,来对表达式进行分割,提高可读性。通过封装表达式树,实现了表达式组装器ExpBuilder
,先看使用:
使用方法
And、Or函数都是静态函数,可以直接通过ExpBuilder.xx
来调用。
public async Task<List<PatientMedicineScheduleInfo>> GetPatientMedicineSchedule(string inpatientID, string hospitalID, DateTime startDate, TimeSpan startTime, DateTime endDate, TimeSpan endTime)
{// 将表达式拆分为四个孙表达式Expression<Func<PatientMedicineScheduleInfo, bool>> onDateAfterTime = (PatientMedicineScheduleInfo m) => m.ScheduleDate == startDate.Date && m.ScheduleTime >= startTime;Expression<Func<PatientMedicineScheduleInfo, bool>> afterDate = (PatientMedicineScheduleInfo m) => m.ScheduleDate > startDate.Date;Expression<Func<PatientMedicineScheduleInfo, bool>> onDateBeforeTime = (PatientMedicineScheduleInfo m) => m.ScheduleDate == endDate.Date && m.ScheduleTime <= endTime;Expression<Func<PatientMedicineScheduleInfo, bool>> beforeDate = (PatientMedicineScheduleInfo m) => m.ScheduleDate < endDate.Date;// 使用or函数,组装出两个子表达式var onDateAfter = ExpBuilder.Or(onDateAfterTime, afterDate);var onDateBefore = ExpBuilder.Or(onDateBeforeTime, beforeDate);// 最后使用And组装两个子表达式var dateBetween = ExpBuilder.And(onDateAfter, onDateBefore);return await _medicalDbContext.PatientMedicineScheduleInfos// 使用.Where(dateBetween).Where(m => m.InpatientID == inpatientID && m.HospitalID == hospitalID && m.DeleteFlag != "*").OrderBy(m => m.ScheduleDate).ThenBy(m => m.ScheduleTime).ThenBy(m => m.GroupID).ThenBy(m => m.HISOrderSort).ToListAsync();
}
条件拼接
如果表达式本身是有某些条件才拼接的,可以使用IfAnd
、IfOr
// 表达式后跟两个条件表达式,若均为false,则表达式恒返回true
var apInterventionPredicate = ExpBuilder.True<QuarterPlanWorkInfo>()// 条件一:若excludeApInterventionIDs不为空,则根据其排除部分数据.IfAnd(excludeApInterventionIDs.Length > 0, m => !excludeApInterventionIDs.Contains(m.APInterventionID))// 条件二:若includeApInterventionID有值,则根据其筛选对应数据.IfAnd(includeApInterventionID.HasValue, m => m.APInterventionID == includeApInterventionID.Value);
源代码
ExpBuilder.cs
public static class ExpBuilder
{public static Expression<Func<T, bool>> True<T>(){return f => true;}public static Expression<Func<T, bool>> False<T>(){return f => false;}/// <summary>/// 组合表达式/// </summary>/// <typeparam name="T"></typeparam>/// <param name="first">原表达式</param>/// <param name="second">拼接表达式</param>/// <param name="merge">合并方式函数</param>/// <returns></returns>private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second,Func<Expression, Expression, Expression> merge){// build parameter map (from parameters of second to parameters of first) var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);// replace parameters in the second lambda expression with parameters from the first var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);// apply composition of lambda expression bodies to parameters from the first expression return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);}/// <summary>/// 表达式且/// </summary>/// <typeparam name="T">类型</typeparam>/// <param name="first">原表达式</param>/// <param name="second">拼接表达式</param>/// <returns></returns>public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second){return first.Compose(second, Expression.AndAlso);}/// <summary>/// 条件表达式且/// </summary>/// <typeparam name="T">类型</typeparam>/// <param name="first">原表达式</param>/// <param name="condition">条件</param>/// <param name="second">拼接表达式</param>/// <returns></returns>public static Expression<Func<T, bool>> IfAnd<T>(this Expression<Func<T, bool>> first, bool condition, Expression<Func<T, bool>> second){return condition ? first.Compose(second, Expression.AndAlso) : first;}/// <summary>/// 表达式或/// </summary>/// <typeparam name="T">类型</typeparam>/// <param name="first">原表达式</param>/// <param name="second">拼接表达式</param>/// <returns></returns>public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second){return first.Compose(second, Expression.OrElse);}/// <summary>/// 条件表达式或/// </summary>/// <typeparam name="T">类型</typeparam>/// <param name="first">原表达式</param>/// <param name="condition">条件</param>/// <param name="second">拼接表达式</param>/// <returns></returns>public static Expression<Func<T, bool>> IfOr<T>(this Expression<Func<T, bool>> first, bool condition, Expression<Func<T, bool>> second){return condition ? first.Compose(second, Expression.OrElse) : first;}
}
ParameterRebinder.cs
public class ParameterRebinder : ExpressionVisitor
{private readonly Dictionary<ParameterExpression, ParameterExpression> map;public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map){this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();}internal static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp){return new ParameterRebinder(map).Visit(exp);}protected override Expression VisitParameter(ParameterExpression p){if (map.TryGetValue(p, out ParameterExpression replacement)) p = replacement;return base.VisitParameter(p);}
}
单元测试
public class ExBuilderTest
{/// <summary>/// 且表达式/// </summary>[Fact]public void AndTest(){Expression<Func<int, bool>> exp1 = (_) => true;Expression<Func<int, bool>> exp2 = (_) => false;var combined = exp1.And(exp2); // exp1 && exp2Assert.False(combined.Compile()(0));}/// <summary>/// 或表达式/// </summary>/// <example>/// exp1 || exp2/// </example>[Fact]public void OrTest(){Expression<Func<int, bool>> exp1 = (_) => true;Expression<Func<int, bool>> exp2 = (_) => false;var combined = exp1.Or(exp2); // exp1 || exp2Assert.True(combined.Compile()(0));}/// <summary>/// 条件且表达式/// </summary>/// <example>/// if (condition) exp1 && exp2/// </example>[Fact]public void IfAndTest(){Expression<Func<int, bool>> exp1 = (_) => true;Expression<Func<int, bool>> exp2 = (_) => false;var combined1 = exp1.IfAnd(true, exp2); // true, exp1 && exp2Assert.False(combined1.Compile()(0));var combined2 = exp1.IfAnd(false, exp2); // false, exp1Assert.True(combined2.Compile()(0));}/// <summary>/// 条件或表达式/// </summary>/// <example>/// if (condition) exp1 || exp2/// </example>[Fact]public void IfOrTest(){Expression<Func<int, bool>> exp1 = (_) => false;Expression<Func<int, bool>> exp2 = (_) => true;var combined1 = exp1.IfOr(true, exp2); // true, exp1 || exp2Assert.True(combined1.Compile()(0));var combined2 = exp1.IfOr(false, exp2); // false, exp1Assert.False(combined2.Compile()(0));}/// <summary>/// 且表达式与或表达式组合/// </summary>[Fact]public void AndCombineOr(){Expression<Func<int, bool>> exp1 = (_) => false;Expression<Func<int, bool>> exp2 = (_) => true;Expression<Func<int, bool>> exp3 = (_) => true;var combined1 = exp1.And(exp2).Or(exp3);Assert.True(combined1.Compile()(0)); // (exp1 && exp2) || exp3var combined2 = exp1.And(exp2.Or(exp3));Assert.False(combined2.Compile()(0)); // exp1 && (exp2 || exp3)}
}