如何在不使用.Compile()的情况下从MemberExpression获取属性值?

7

我在尝试获取表达式树中对象的值,但不想使用 .Compile() 方法。

这个对象非常简单。

var userModel = new UserModel { Email = "John@Doe.com"};

让我遇到问题的方法如下。

private void VisitMemberAccess(MemberExpression expression, MemberExpression left)
{
    var key = left != null ? left.Member.Name : expression.Member.Name;
    if (expression.Expression.NodeType.ToString() == "Parameter")
    {
        // add the string key
        _strings.Add(string.Format("[{0}]", key));
    }
    else
    {
        // add the string parameter
        _strings.Add(string.Format("@{0}", key));

        // Potential NullReferenceException
        var val = (expression.Member as FieldInfo).GetValue((expression.Expression as ConstantExpression).Value);

        // add parameter value
        Parameters.Add("@" + key, val);
    }
}

我正在运行的测试非常简单。
[Test]  // PASS
public void ShouldVisitExpressionByGuidObject ()
{
    // Setup
    var id = new Guid( "CCAF57D9-88A4-4DCD-87C7-DB875E0D4E66" );
    const string expectedString = "[Id] = @Id";
    var expectedParameters = new Dictionary<string, object> { { "@Id", id } };

    // Execute
    var actualExpression = TestExpression<UserModel>( u => u.Id == id );
    var actualParameters = actualExpression.Parameters;
    var actualString = actualExpression.WhereExpression;

    // Test
    Assert.AreEqual( expectedString, actualString );
    CollectionAssert.AreEquivalent( expectedParameters, actualParameters );
}

[Test]  // FAIL [System.NullReferenceException : Object reference not set to an instance of an object.]
public void ShouldVisitExpressionByStringObject ()
{
    // Setup
    var expectedUser = new UserModel {Email = "john@doe.com"};

    const string expectedString = "[Email] = @Email";
    var expectedParameters = new Dictionary<string, object> { { "@Email", expectedUser.Email } };

    // Execute
    var actualExpression = TestExpression<UserModel>( u => u.Email == expectedUser.Email );
    var actualParameters = actualExpression.Parameters;
    var actualString = actualExpression.WhereExpression;

    // Assert
    Assert.AreEqual( expectedString, actualString );
    CollectionAssert.AreEquivalent( expectedParameters, actualParameters );
}

我应该提醒您,更改
var val = (expression.Member as FieldInfo).GetValue((expression.Expression as ConstantExpression).Value);

to

var val = Expression.Lambda( expression ).Compile().DynamicInvoke().ToString();

这段代码可以通过测试,但是它需要在iOS上运行,因此不能使用.Compile()方法。


1
UserModel.Email 真的是一个字段吗?还是一个属性? - svick
如果它是一个属性,那么 expression 就是 PropertyExpression -> FieldExpression -> ConstantExpression,而代码假设只有 FieldExpression -> ConstantExpression,因此出现了问题。 - Jon
啊,这有道理...让我看看我能想出什么。 - Chase Florell
谢谢@svick,你是对的。它在处理属性时出错了,实际上它是一个属性。 - Chase Florell
@Jon,要从属性中获取值需要有耐心,这真是一次冒险。它远不如字段直观。 - Chase Florell
1个回答

11

简述;
反射是可以使用的,只要不使用EmitCompile。在问题中,值正在被提取给FieldInfo,但它没有被提取给PropertyInfo。确保你可以获取两者的值。

if ((expression.Member as PropertyInfo) != null)
{
    // get the value from the PROPERTY

}
else if ((expression.Member as FieldInfo) != null)
{
    // get the value from the FIELD
}
else
{
    throw new InvalidMemberException();
}

简洁版

评论指引了我方向。获取 PropertyInfo 有些麻烦,但最终我得到了以下代码。

private void VisitMemberAccess(MemberExpression expression, MemberExpression left)
{
    // To preserve Case between key/value pairs, we always want to use the LEFT side of the expression.
    // therefore, if left is null, then expression is actually left. 
    // Doing this ensures that our `key` matches between parameter names and database fields
    var key = left != null ? left.Member.Name : expression.Member.Name;

    // If the NodeType is a `Parameter`, we want to add the key as a DB Field name to our string collection
    // Otherwise, we want to add the key as a DB Parameter to our string collection
    if (expression.Expression.NodeType.ToString() == "Parameter")
    {
        _strings.Add(string.Format("[{0}]", key));
    }
    else
    {
        _strings.Add(string.Format("@{0}", key));

        // If the key is being added as a DB Parameter, then we have to also add the Parameter key/value pair to the collection
        // Because we're working off of Model Objects that should only contain Properties or Fields,
        // there should only be two options. PropertyInfo or FieldInfo... let's extract the VALUE accordingly
        var value = new object();
        if ((expression.Member as PropertyInfo) != null)
        {
            var exp = (MemberExpression) expression.Expression;
            var constant = (ConstantExpression) exp.Expression;
            var fieldInfoValue = ((FieldInfo) exp.Member).GetValue(constant.Value);
            value = ((PropertyInfo) expression.Member).GetValue(fieldInfoValue, null);

        }
        else if ((expression.Member as FieldInfo) != null)
        {
            var fieldInfo = expression.Member as FieldInfo;
            var constantExpression = expression.Expression as ConstantExpression;
            if (fieldInfo != null & constantExpression != null)
            {
                value = fieldInfo.GetValue(constantExpression.Value);
            }
        }
        else
        {
            throw new InvalidMemberException();
        }

        // Add the Parameter Key/Value pair.
        Parameters.Add("@" + key, value);
    }
}

基本上,如果Member.NodeType是一个Parameter,那么我将把它用作SQL字段。[FieldName]

否则,我将其用作SQL参数@FieldName... 虽然有些反向。

如果Member.NodeType不是参数,则我检查它是否为模型Field或模型Property。从那里,我获取相应的值,并将键/值对添加到字典中以用作SQL参数。

最终结果就是我构建了一个类似以下字符串的字符串:

SELECT * FROM TableName WHERE
[FieldName] = @FieldName
然后将参数传递。
var parameters = new Dictionary<string, object> Parameters;
parameters.Add("@FieldName", "The value of the field");

太棒了。 - jekcom

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