Expression<Func<T, bool>>转字符串时闭包变量未解析问题求助
问题原因
你当前的实现存在两个核心问题:
- 直接调用
Expression.Body.ToString()只会输出表达式语法树的原生文本,不会计算外部引用变量(也就是你示例里的articolo)的实际值。这类外部变量会被编译器封装到自动生成的闭包类中,所以ToString后会出现value(Vendo.ViewModels.DettaglioArticoliViewModel+<>c__DisplayClass184_0)这类闭包类引用字符串,无法直接用于SQL语句。 - 简单的字符串替换只能覆盖极少数基础场景,无法兼容
>、<、Contains、??、嵌套属性判断等更复杂的表达式,非常容易生成非法SQL。
解决方法
你需要递归遍历整个表达式树,针对不同类型的表达式节点做针对性解析:
- 参数节点解析:识别表达式的输入参数(也就是你示例里的
i),将其属性访问路径替换为对应的表字段名 - 外部值解析:对于闭包类的成员访问、常量节点,通过反射获取其实际运行时值,要么转为SQL合法的常量字符串,要么绑定为SQL命令参数(更安全,避免SQL注入)
- 运算符转换:将C#表达式运算符转为SQL标准运算符,比如
AndAlso转AND、OrElse转OR、==转=等
下面是简化的实现示例:
public static string ParseExpressionToSqlCondition<T>(Expression<Func<T, bool>> predicate, out List<SQLiteParameter> parameters) { parameters = new List<SQLiteParameter>(); var visitor = new SqlExpressionVisitor(parameters); visitor.Visit(predicate.Body); return visitor.GetSql(); } // 自定义表达式访问器 public class SqlExpressionVisitor : ExpressionVisitor { private readonly StringBuilder _sqlBuilder = new StringBuilder(); private readonly List<SQLiteParameter> _parameters; private int _paramIndex = 0; public SqlExpressionVisitor(List<SQLiteParameter> parameters) { _parameters = parameters; } public string GetSql() => _sqlBuilder.ToString(); // 处理二元运算(==、&&、>这类) protected override Expression VisitBinary(BinaryExpression node) { _sqlBuilder.Append("("); Visit(node.Left); _sqlBuilder.Append(GetSqlOperator(node.NodeType)); Visit(node.Right); _sqlBuilder.Append(")"); return node; } // 处理参数的属性访问(比如i.CodArt) protected override Expression VisitMember(MemberExpression node) { // 如果是访问表达式参数的属性,直接输出字段名 if (node.Expression is ParameterExpression paramExpr) { _sqlBuilder.Append($"[{node.Member.Name}]"); return node; } // 否则是外部变量,获取实际值转为参数 var value = GetMemberValue(node); var paramName = $"@Param{_paramIndex++}"; _parameters.Add(new SQLiteParameter(paramName, value)); _sqlBuilder.Append(paramName); return node; } // 处理常量 protected override Expression VisitConstant(ConstantExpression node) { var paramName = $"@Param{_paramIndex++}"; _parameters.Add(new SQLiteParameter(paramName, node.Value)); _sqlBuilder.Append(paramName); return node; } // 获取对应SQL运算符 private string GetSqlOperator(ExpressionType nodeType) { return nodeType switch { ExpressionType.Equal => " = ", ExpressionType.AndAlso => " AND ", ExpressionType.OrElse => " OR ", ExpressionType.GreaterThan => " > ", ExpressionType.LessThan => " < ", ExpressionType.NotEqual => " <> ", _ => throw new NotSupportedException($"不支持运算符{nodeType}") }; } // 反射获取成员的实际值 private object GetMemberValue(MemberExpression member) { var objectMember = Expression.Convert(member, typeof(object)); var getterLambda = Expression.Lambda<Func<object>>(objectMember); var getter = getterLambda.Compile(); return getter(); } }
改造后的调用方式:
public List<TEntity> ExecQuery<TEntity>(Expression<Func<TEntity, bool>> predicate) { var parameters = new List<SQLiteParameter>(); string condition = ParseExpressionToSqlCondition(predicate, out parameters); SQLiteCommand sQLiteCommand = new(App.CNManager.Connection); sQLiteCommand.CommandText = $"SELECT * FROM {typeof(TEntity).Name} WHERE {condition}"; sQLiteCommand.Parameters.AddRange(parameters.ToArray()); return sQLiteCommand.ExecuteQuery<TEntity>(); }
注意:上述实现是基础版本,你可以根据业务需求扩展支持
Contains、StartsWith、null判断等更多表达式场景。使用参数化查询可以完全避免SQL注入风险,比直接拼接值到SQL语句更安全。
内容的提问来源于stack exchange,提问作者Innova
相关产品推荐
相关产品推荐

