表达式组装器代码分享

起因

某些业务,组装的表达式比较长,且、或、括号混杂一块难以立即理解:

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();
}

条件拼接

如果表达式本身是有某些条件才拼接的,可以使用IfAndIfOr

// 表达式后跟两个条件表达式,若均为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 && exp2
        Assert.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 || exp2
        Assert.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 && exp2
        Assert.False(combined1.Compile()(0));

        var combined2 = exp1.IfAnd(false, exp2); // false, exp1
        Assert.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 || exp2
        Assert.True(combined1.Compile()(0));

        var combined2 = exp1.IfOr(false, exp2); // false, exp1
        Assert.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) || exp3

        var combined2 = exp1.And(exp2.Or(exp3));
        Assert.False(combined2.Compile()(0)); // exp1 && (exp2 || exp3)
    }
}
posted @ 2025-03-06 16:39  南山有榛  阅读(17)  评论(0)    收藏  举报