簡體   English   中英

處理C#表達式樹中嵌套對象的空值

[英]Handle null values of nested objects in C# expression tree

我已經搜索過,並且發現了與我的問題有關的類似帖子,但是似乎沒有什么可以解決我的問題。

我對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”)上具有空值,然后嘗試將“引用”轉換為小寫時,就會出現問題。 這里:

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

這是調試器中的結果:

在此處輸入圖片說明

如何檢查嵌套對象上的null值,如果它為null,則返回空字符串?

我希望我能很好地解釋我的問題。 先感謝您!

您想要做的是生成一個這樣的表達式:

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

不幸的是,它不再是成員表達式,因此GetMemberExpression對此不起作用。 相反,您需要一連串條件表達式,一次可以訪問一個以上的級別。

一旦有了,就可以執行<memberExpression> ?? string.Empty <memberExpression> ?? 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);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM