You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expression<Func<T, bool>>转字符串时闭包变量未解析问题求助

问题原因

你当前的实现存在两个核心问题:

  • 直接调用Expression.Body.ToString()只会输出表达式语法树的原生文本,不会计算外部引用变量(也就是你示例里的articolo)的实际值。这类外部变量会被编译器封装到自动生成的闭包类中,所以ToString后会出现value(Vendo.ViewModels.DettaglioArticoliViewModel+<>c__DisplayClass184_0)这类闭包类引用字符串,无法直接用于SQL语句。
  • 简单的字符串替换只能覆盖极少数基础场景,无法兼容>、<、Contains、??、嵌套属性判断等更复杂的表达式,非常容易生成非法SQL。
解决方法

你需要递归遍历整个表达式树,针对不同类型的表达式节点做针对性解析:

  1. 参数节点解析:识别表达式的输入参数(也就是你示例里的i),将其属性访问路径替换为对应的表字段名
  2. 外部值解析:对于闭包类的成员访问、常量节点,通过反射获取其实际运行时值,要么转为SQL合法的常量字符串,要么绑定为SQL命令参数(更安全,避免SQL注入)
  3. 运算符转换:将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 20:36:01