【问题标题】:Handle null values of nested objects in C# expression tree处理 C# 表达式树中嵌套对象的空值
【发布时间】:2015-08-21 09:42:06
【问题描述】:

我已搜索并找到与我的问题相关的类似帖子,但似乎没有任何问题可以解决我的问题。

我是 C# 的新手,这是我第一次尝试构建表达式树。 (请放轻松;-)

我正在尝试创建一个表达式树,一旦编译,它将过滤一组数据的值。

这是我的表达方式:

private static Expression<Func<TItem, bool>> CreateFilterExpression<TItem>(string propertyName, string expressionType, dynamic filterValue)
{
    if (param == null)
    {
        param = Expression.Parameter(typeof(TItem), "item");
    }
    MemberExpression member = GetMemberExpression<TItem>(propertyName);

    //When we call our method, we need to evaluate on the same type
    //we convert the filter value to the type of the property we are evaluating on
    dynamic convertedValue = Convert.ChangeType(filterValue, member.Type);
    MethodInfo method = member.Type.GetMethod(expressionType, new[] { member.Type });
    ConstantExpression constantValue = Expression.Constant(convertedValue, member.Type);
    Expression containsMethodExp;

    if (expressionType == "NotEqual")
    {
        method = member.Type.GetMethod("Equals", new[] { member.Type });
    }
    if (member.Type.ToString().ToLower() == "system.string")
    {
        //We need to compare the lower case of property and value
        MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
        MethodCallExpression filterValueToLowerCase = Expression.Call(constantValue, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
        containsMethodExp = Expression.Call(propertyValueToLowerCase, method, filterValueToLowerCase);
    }
    else if (member.Type.ToString().ToLower() == "system.datetime")
    {
        //we need to compare only the dates
        MemberExpression dateOnlyProperty = Expression.Property(member, "Date");
        containsMethodExp = Expression.Call(dateOnlyProperty, method, constantValue);
    }
    else
    {
        containsMethodExp = Expression.Call(member, method, constantValue);
    }

    if (expressionType == "NotEqual")
    {
        containsMethodExp = Expression.Not(containsMethodExp);
    }

    return Expression.Lambda<Func<TItem, bool>>(containsMethodExp, param);
}

private static MemberExpression GetMemberExpression<TItem>(string propertyName)
{
    if (param == null)
    {
        param = Expression.Parameter(typeof(TItem), "item");
    }
    MemberExpression member = null;

    //Check if we have a nested property
    if (propertyName.Contains('.'))
    {
        Expression nestedProperty = param;
        string[] properies = propertyName.Split('.');
        int zeroIndex = properies.Count() - 1;
        for (int i = 0; i <= zeroIndex; i++)
        {
            if (i < zeroIndex)
            {
                nestedProperty = Expression.PropertyOrField(nestedProperty, properies[i]);
            }
            else
            {
                member = Expression.Property(nestedProperty, properies[i]);
            }
        }
    }
    else
    {
        member = Expression.Property(param, propertyName);
    }
    return member;
}

示例用法如下:

var lambda = CreateFilterExpression<T>("Some.Nested.Object", "Equals", "Some value");
var compiled = lambda.Compile();
gridData = gridData.Where(compiled);

我尝试最终绑定到网格的数据示例如下所示:

public class Some : BaseClass
{
    public decimal NumberAvailable { get; set; }
    public DateTime EffectiveDate { get; set; }
    public Batch Batch { get; set; }
    public decimal Price { get; set; }
    public decimal Limit { get; set; }
    public NestedClass Nested { get; set; }
    public int? CompanyId { get; set; }
    public decimal Amount { get; set; }
}

public class NestedClass : BaseClass
{
    public int RequestId { get; set; }
    public string Code { get; set; }
    public string Company { get; set; }
    public string Reference { get; set; }
}

当我们在一个对象上有空值时出现问题,例如“Some.Nested = null”,然后尝试将“Reference”转换为小写。这里:

MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));

这是调试器中的结果:

如何检查嵌套对象上的空值,如果为空则返回空字符串?

我希望我能很好地解释我的问题。提前谢谢!

【问题讨论】:

  • 您可以在表达式中添加 if 语句,并在尝试获取属性之前检查当前属性不为空
  • 谢谢 Grundy,也许我理解错了,但是对于任何数量的嵌套对象来说,这需要是动态的?
  • 你能提供GetMemberExpression吗?
  • 当然,已经编辑了我的问题并添加了 GetMemberExpression 方法
  • 有许多对 param 的引用 - 没有在任何地方声明。只是一个静态私有成员?

标签: c# lambda expression


【解决方案1】:

你要做的是生成这样的表达式:

Some == null ? null : Some.Nested == null ? null : Some.Nested.Object

不幸的是,这不再是一个成员表达式,所以GetMemberExpression 不能用于这个。相反,您需要一个条件表达式链,一次访问一个级别。

一旦你有了它,你就可以通过&lt;memberExpression&gt; ?? string.Empty 得到一个你可以安全操作的字符串。

要生成后一个表达式,可以使用Expression.Coalesce

Expression.Coalesce(memberExpression, Expression.Constant(string.Empty))

对于成员表达式本身,你可以这样写:

Expression AccessMember(Expression obj, string propertyName)
{
    string[] parts = propertyName.Split(new char[] { '.' }, 2);
    Expression member = Expression.PropertyOrField(obj, parts[0]);

    if (parts.Length > 1)
        member = AccessMember(member, parts[1]);

    return Expression.Condition(Expression.Equal(obj, Expression.Constant(null)),
        Expression.Constant(null, member.Type), member);
}

可以这样使用:

string path = "Some.Nested.Object";
string[] parts = path.Split(new char[] { '.' }, 2);
ParameterExpression param = Expression.Parameter(typeof(T), parts[0]);
Expression memberAccess = AccessMember(param, parts[1]);

memberAccess 就是上面的链式条件表达式。

结合到您的函数中(目前仅针对字符串进行了简化),它可能如下所示:

Expression<Func<TObj, bool>> BuildFilterExpression<TObj, TMember>(string propertyPath, TMember comparisonValue, TMember defaultValue)
{
    string[] parts = propertyPath.Split(new char[] { '.' }, 2);
    ParameterExpression param = Expression.Parameter(typeof(TObj), parts[0]);

    // get member access expression
    Expression memberExpression = AccessMember(param, parts[1]);

    // coalesce the member with the default value
    memberExpression = Expression.Coalesce(memberExpression, Expression.Constant(defaultValue));

    // get the comparison value as expression
    Expression comparisonExpression = Expression.Constant(comparisonValue);

    // type specific logic
    if (memberExpression.Type == typeof(string))
    {
        MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
        memberExpression = Expression.Call(memberExpression, toLowerMethod);
        comparisonExpression = Expression.Call(comparisonExpression, toLowerMethod);
    }

    // create the comparison expression
    Expression filterExpression = Expression.Equal(memberExpression, comparisonExpression);

    return Expression.Lambda<Func<TObj, bool>>(filterExpression, param);
}

这样使用:

BuildFilterExpression<SomeType, string>("Some.Nested.Object", "foo bar", string.Empty)

...它本质上创建了以下 lambda 表达式:

(Some) => ((Some == null ? null : Some.Nested == null ? null : Some.Nested.Object) ?? string.Empty).ToLower() == "foo bar"

上面的代码假定对于属性表达式Some.Nested.ObjectSome 是传递给 lambda 的对象,因此要访问的第一个属性是 Nested。原因是我根本不知道你的示例对象结构,所以我必须想出一些办法。

如果您希望 Some 成为传递对象访问的第一个属性,您可以轻松更改它。为此,请修改BuildFilterExpression 的开头,以便propertyPath 不会被拆分。将一些随机名称(甚至没有名称)传递给Expression.Parameter,并将完整的propertyPath 传递给AccessMember

// don’t split up the propertyPath

// let’s call the parameter `obj`
ParameterExpression param = Expression.Parameter(typeof(TObj), "obj");

// get member access expression—for the full property path
Expression memberExpression = AccessMember(param, propertyPath);

【讨论】:

  • 非常感谢戳!我正忙于测试您的解决方案,但是在 AccessMember 方法中收到错误消息(Expression member = Expression.PropertyOrField(obj, parts[0]);)。 "'Nested' 不是 'MyClassName' 类型的成员"NestedSome 类实例的 null 属性。跨度>
  • 是的,我不完全知道您的对象结构是如何设置的,所以在我的示例中,我使用 Some 作为传递给 lambda 的对象,而不是作为第一个属性访问。所以一个快速的解决方法是在属性路径前面添加一些前缀,例如obj.Some.Nested.Object。但我会调整我的答案,包括如何改变这种行为。
  • 当然,你无法知道。我已经实现了更改,但是我仍然收到 “对象引用未设置为对象的实例。” 我构造的表达式(感谢您的帮助)现在看起来像这样:obj => ((IIF((obj == null), null, IIF((obj.Request == null), null, obj.Request.Reference)) ?? default(String)).ToLower() == "filtervalue"。 ToLower()) 就我所见,哪个看起来正确?
  • 您能否在问题中添加一些数据的小示例,以便我可以正确测试?
  • 没问题,我添加了我要绑定的类的简化版本。因此,当我的对象上的嵌套类 "NestedClass" 为 null 时,就会发生错误。如果您还需要什么,请告诉我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
相关资源
最近更新 更多