如何为Expression添加Linq方法(如Contains/StartsWith)?
实现泛型表达式扩展,动态添加字符串过滤方法
首先修正你代码里的类型问题:示例中exp = log => log.Message属于Expression<Func<LogRequest, string>>,而非Expression<Func<LogRequest, bool>>。下面通过泛型扩展实现通用逻辑,避免重复代码,同时生成符合预期的组合表达式。
核心扩展方法实现
1. 属性表达式转过滤表达式
实现泛型扩展,将指向字符串属性的表达式,转换为带StartsWith/Contains逻辑的布尔表达式:
public static class ExpressionExtensions { // 将字符串属性表达式转为过滤表达式 public static Expression<Func<T, bool>> ToStringFilter<T>(this Expression<Func<T, string>> propertyExpr, string filterType, string value) { var property = propertyExpr.Body; MethodInfo method = filterType.ToLower() switch { "startswith" => typeof(string).GetMethod(nameof(string.StartsWith), new[] { typeof(string) })!, "contains" => typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!, _ => throw new ArgumentException($"不支持的过滤类型: {filterType}") }; var constant = Expression.Constant(value, typeof(string)); var methodCall = Expression.Call(property, method, constant); return Expression.Lambda<Func<T, bool>>(methodCall, propertyExpr.Parameters); } // 组合多个布尔表达式(优化版,避免Invoke,直接拼接表达式树) public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) { var parameter = left.Parameters.Single(); var rightBody = new ParameterReplacer(right.Parameters.Single(), parameter).Visit(right.Body); return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body, rightBody), parameter); } // 替换表达式参数的辅助类 private class ParameterReplacer : ExpressionVisitor { private readonly ParameterExpression _oldParam; private readonly ParameterExpression _newParam; public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam) { _oldParam = oldParam; _newParam = newParam; } protected override Expression VisitParameter(ParameterExpression node) { return node == _oldParam ? _newParam : base.VisitParameter(node); } } }
2. 简化调用逻辑
现在可以轻松构建多属性的组合过滤表达式,无需为每个属性重复写判断:
// 示例使用 string filterType = "startswith"; var messageFilter = ((Expression<Func<LogRequest, string>>)(log => log.Message)) .ToStringFilter(filterType, "My Message"); var ipFilter = ((Expression<Func<LogRequest, string>>)(log => log.ClientIP)) .ToStringFilter(filterType, "192.168"); // 组合所有过滤条件 var combinedFilter = messageFilter.AndAlso(ipFilter); // 最终生成的表达式等价于:log => log.Message.StartsWith("My Message") && log.ClientIP.StartsWith("192.168")
3. 批量处理封装(可选)
如果需要批量处理多个属性,可再封装一个方法:
public static Expression<Func<T, bool>> CombineStringFilters<T>(string filterType, params (Expression<Func<T, string>> PropertyExpr, string Value)[] filters) { if (filters.Length == 0) throw new ArgumentException("至少需要一个过滤条件"); var combined = filters[0].PropertyExpr.ToStringFilter(filterType, filters[0].Value); for (int i = 1; i < filters.Length; i++) { var currentFilter = filters[i].PropertyExpr.ToStringFilter(filterType, filters[i].Value); combined = combined.AndAlso(currentFilter); } return combined; } // 调用示例 var batchFilter = CombineStringFilters<LogRequest>(filterType, (log => log.Message, "My Message"), (log => log.ClientIP, "192.168"), (log => log.UserAgent, "Chrome") // 轻松添加更多属性 );
关键说明
- 优化了原
AndAlso方法:使用ExpressionVisitor替换参数,避免Expression.Invoke,生成的表达式树更简洁,ORM(如EF Core)能更好地解析为SQL。 - 泛型设计支持任意实体类型,不止
LogRequest,复用性更强。 - 通过
ToStringFilter统一处理字符串过滤逻辑,新增过滤类型(如EndsWith)只需在switch中添加对应方法,完全符合DRY原则。
内容的提问来源于stack exchange,提问作者Jawoy
相关产品推荐
相关产品推荐

