处理 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",然后尝试将 "Reference" 转换为小写。这里:
MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
这是调试器中的结果:
如何在嵌套对象上检查空值,以及 return 空字符串(如果它为空)?
我希望我对我的问题的解释足够好。提前致谢!
您想要做的是生成如下表达式:
Some == null ? null : Some.Nested == null ? null : Some.Nested.Object
不幸的是,这不再是成员表达式,因此 GetMemberExpression
对此不起作用。相反,您需要一次访问一个以上级别的条件表达式链。
一旦你有了它,你就可以 <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.Object
,Some
是传递给 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);
我已经搜索并找到了与我的问题相关的类似帖子,但似乎没有任何方法可以解决我的问题。
我是 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));
这是调试器中的结果:
如何在嵌套对象上检查空值,以及 return 空字符串(如果它为空)?
我希望我对我的问题的解释足够好。提前致谢!
您想要做的是生成如下表达式:
Some == null ? null : Some.Nested == null ? null : Some.Nested.Object
不幸的是,这不再是成员表达式,因此 GetMemberExpression
对此不起作用。相反,您需要一次访问一个以上级别的条件表达式链。
一旦你有了它,你就可以 <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.Object
,Some
是传递给 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);