如何将表达式编译成实际结果?

5
我正在使用表达式构建一个围绕Web服务调用的API,以允许开发人员指定查询,并使ExpressionVisitor将表达式转换为查询字符串。请求是XML格式,其中特定元素包含查询字符串。
例如,我可以执行以下操作,这将检索所有银行名称为Bank 1或Bank 2的支票账户:
"bankname = 'Bank 1' or bankname = 'Bank 2'"
Web服务可以处理更复杂的查询,但现在我只需要用这个。所以我有一个CheckingAccount类:

[IntacctObject("checkingaccount")]
public class CheckingAccount : Entity
{
    [IntacctElement("bankaccountid")]
    public string Id { get; set; }

    [IntacctElement("bankname")]
    public string BankName { get; set; }
}

一个ExpressionVisitor的主要功能是将这样的表达式转换为查询:

Expression> expression = ca => ca.BankName == "Bank 1" || ca.BankName == "Bank 2"

“bankname ='Bank 1'或bankname ='Bank 2'”

这并不难。当我引入本地变量时,问题开始变得复杂起来:


var account = new CheckingAccount { BankName = "Bank 1" };
string bankName = "Bank 2";

Expression> expression = ca => ca.BankName == account.BankName || ca.BankName == bankName;

我知道如何处理简单的本地变量(即string bankName = "Bank 2"),但处理其他类型的变量(例如var account = new CheckingAccount { BankName = "Bank 1" })要复杂得多。
最终,这些是我现在需要解决的重大问题。我知道还有更复杂的情况,但我目前并不太关心它们。
以下是我的表达式访问器(请注意CreateFilter方法上的泛型约束):

internal class IntacctWebService30ExpressionVisitor : ExpressionVisitor
{
    private readonly List _Filters = new List();
    private IntacctWebServicev30SimpleQueryFilter _CurrentSimpleFilter;
    private IntacctWebServicev30ComplexQueryFilter _CurrentComplexFilter;
    private MemberExpression _CurrentMemberExpression;

    public string CreateFilter(Expression> expression) where TEntity : Entity
    {

        Visit(expression);

        string filter = string.Join(string.Empty, _Filters.Select(f => f.ToString()).ToArray());
        return filter;
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        switch (node.NodeType)
        {
            case ExpressionType.AndAlso:
            case ExpressionType.OrElse:
                _CurrentComplexFilter = new IntacctWebServicev30ComplexQueryFilter { ExpressionType = node.NodeType };
                break;
            case ExpressionType.Equal:
            case ExpressionType.GreaterThan:
            case ExpressionType.GreaterThanOrEqual:
            case ExpressionType.LessThan:
            case ExpressionType.LessThanOrEqual:
            case ExpressionType.NotEqual:
                _CurrentSimpleFilter = new IntacctWebServicev30SimpleQueryFilter { ExpressionType = node.NodeType };
                break;
        }

        return base.VisitBinary(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        var attr = node.Member.GetAttribute();
        if (attr != null)
            _CurrentSimpleFilter.FieldName = attr.FieldName;
        else
            _CurrentMemberExpression = node;

        return base.VisitMember(node);
    }

    protected override Expression VisitConstant(ConstantExpression node)
    {
        object value = Expression.Lambda>(node).Compile().Invoke();

        string fieldValue = extraxtFieldValue(value, node);

        _CurrentSimpleFilter.FieldValue = fieldValue;

        if (_CurrentComplexFilter != null)
        {
            if (_CurrentComplexFilter.Left == null)
            {
                _CurrentComplexFilter.Left = _CurrentSimpleFilter;
            }
            else if (_CurrentComplexFilter.Right == null)
            {
                _CurrentComplexFilter.Right = _CurrentSimpleFilter;
                _Filters.Add(_CurrentComplexFilter);
            }
            else
                throw new InvalidOperationException();
        }
        else
        {
            _Filters.Add(_CurrentSimpleFilter);
        }

        return base.VisitConstant(node);
    }

    private string extraxtFieldValue(object value)
    {
        string fieldValue;
        if (value is DateTime)
            fieldValue = ((DateTime)value).ToString("yyyy-MM-dd");
        else if (value is string)
            fieldValue = value.ToString();
        else if (value.GetType().IsEnum)
        {
            throw new NotImplementedException();
        }
        else
        {
            // Not sure if this is the best way to do this or not but can't figure out how
            // else to get a variable value.

            // If we are here then we are dealing with a property, field, or variable.
            // This means we must extract the value from the object.
            // In order to do this we will rely on _CurrentMemberExpression
            if (_CurrentMemberExpression.Member.MemberType == MemberTypes.Property)
            {
                fieldValue = value.GetType().GetProperty(_CurrentMemberExpression.Member.Name).GetValue(value, null).ToString();
            }
            else if (_CurrentMemberExpression.Member.MemberType == MemberTypes.Field)
            {
                fieldValue = value.GetType().GetFields().First().GetValue(value).ToString();
            }
            else
            {
                throw new InvalidOperationException();
            }
        }

        return fieldValue;
    }
}

如果您需要更多细节,请告诉我...

谢谢


你的过滤器左侧的二元操作符始终是实体.属性吗? - MerickOWA
它们将是属性或字段。 - devlife
我看到你在另一个问题中接受了我的答案,我的答案对你来说是否足够清晰,可以将你的QueryFilter类整合进去? - MerickOWA
直到我发布了这个问题之后,我才真正看到它。它似乎正在工作。但奇怪的是,最初我所做的事情(没有在问题中发布)并没有起作用。最初我尝试了非常类似的东西:Expression.Lambda<Func<object>>(constantExpressionNode, typeof(object)).Compile().Invoke()。 - devlife
1
无法正常工作的原因是constantExpressionNode并不总是返回类型为“object”的表达式。该类型将与字段成员相同,可以是字符串/整数/日期时间等。这就是为什么我使用了通用的“Expression.Lambda”和通用委托的原因。 - MerickOWA
@devlife 让我们在聊天室里继续这个讨论 - MerickOWA
2个回答

0

-1

如果您有兴趣使用开源第三方库来完成此操作,您可以查看表达式树序列化。我相信它可以满足您的需求。


考虑到序列化器将任何LINQ表达式转换为XElement,然后可以通过网络将其转换为XML字符串,这似乎正是op想要做的。如果您能够指出缺少什么,我将不胜感激。 - Mranz
以我理解这个问题,他想要将表达式转换为一个非常具体的形式(例如 "bankname ='银行1'或bankname ='银行2'"),而不是一些XML。 - svick
“该请求是一个包含查询字符串的特定元素的XML格式。” 在我看来,他似乎试图将查询转换为序列化的XML字符串,这正是我指向的库所做的。由于它可以进行序列化和反序列化,并且Expression具有.ToString()方法,使其更像纯文本,因此这是我能想到的最接近的方法。 - Mranz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接