处理 LINQ 表达式调用中的空引用异常
Handle null ref exceptions in LINQ Expression calls
我正在尝试创建一个通用的表达式生成器,只要 none 对象值为空,它基本上就可以正常工作。
我当前的代码如下所示(以 StartsWith 为例):
case FilterOperationTypes.StartsWith:
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
PropertyInfo propertyInfo = typeof(T).GetProperty(field);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(val, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
return Expression.Lambda<Func<T, bool>>(call, e);
}
让我们假设字段是 属性 CustomerName。我知道实际的最终表达式是这样的:
e.CustomerName.StartsWith(val)
如果 CustomerName 为 null,它当然会调用 StartsWith 方法失败,这一点非常清楚。
我试过这样做:
case FilterOperationTypes.StartsWith:
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
PropertyInfo propertyInfo = typeof(T).GetProperty(field);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(val, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.IfThenElse(
Expression.Equal(m, Expression.Constant(null)),
Expression.Constant(null),
Expression.Call(m, mi, c));
//Expression.Call(m, mi, c);
return Expression.Lambda<Func<T, bool>>(call, e);
}
但这会产生类型为 'System.Void' 的表达式不能用于 return 类型 'System.Boolean' 异常。
我现在有点迷茫。也许你们可以把我推向正确的方向。
非常感谢!
您正在查找 Expression.Condition
,而不是 IfThenElse
,它表示条件运算符,而不是 if/else 语句。条件运算符解析为一个值,因为它是一个表达式,而不是一个语句。
现在可以使用了。非常感谢 Servy 将我推向正确的方向。
Expression.Condition 正是我实现目标所需要的。由于实际对象和对象的 属性 在 运行 时可以为空,因此我不得不嵌套两个条件:
PropertyInfo propertyInfo = typeof(T).GetProperty(fieldName);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(comparisonValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Call(m, mi, c)));
return Expression.Lambda<Func<T, bool>>(call, e);
在空值的情况下我决定使用
Expression.NotEqual(e, Expression.Constant(null))
一定要收到"false"。我确定有更好的方法可以做到这一点,但我还想不出一个。
最终的表达式将如下所示
{e => IIF((e == null), (e != null), IIF((e.CustomerName== null), (e != null), e.CustomerName.StartsWith("547")))}
到目前为止,我对这个解决方案非常满意,但我会尝试对其进行优化。
我当前的代码实现和用法如下所示。也许它可以帮助你们中的一个人:
Expression<Func<T, bool>> GetDetailEx<T>(string fieldName, object comparisonValue, string filterType)
{
var e = Expression.Parameter(typeof(T),"e");
switch (GetFilterOperationType(filterType))
{
case FilterOperationTypes.Between:
//Between is automatically translated in >= AND <= within the ExecuteDeepFilter-Method
break;
case FilterOperationTypes.GreaterThanOrEquals:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.GreaterThanOrEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)),e);
}
case FilterOperationTypes.LessThanOrEquals:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.LessThanOrEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)),e);
}
case FilterOperationTypes.GreaterThan:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.GreaterThan(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.LessThan:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.LessThan(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.NotEqual:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.NotEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.Equal:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.Equal(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.EndsWith:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "EndsWith",e);
}
case FilterOperationTypes.StartsWith:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "StartsWith",e);
}
case FilterOperationTypes.NotContains:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "Contains",e,false); ;
}
case FilterOperationTypes.Contains:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "Contains",e);
}
default:
break;
}
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.Equal(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
private Expression<Func<T, bool>> GenerateConditionalExpression<T>(string fieldName, object comparisonValue, Expression actualExpression, ParameterExpression e)
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), actualExpression));
return Expression.Lambda<Func<T, bool>>(call, e);
}
private Expression<Func<T, bool>> GenerateGenericCallExpression<T>(string fieldName, object comparisonValue, string methodName, ParameterExpression e, bool equals = true)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(fieldName);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(comparisonValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
if (equals)
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Call(m, mi, c)));
return Expression.Lambda<Func<T, bool>>(call, e);
}
else
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Not(Expression.Call(m, mi, c))));
return Expression.Lambda<Func<T, bool>>(call, e);
}
}
我正在尝试创建一个通用的表达式生成器,只要 none 对象值为空,它基本上就可以正常工作。
我当前的代码如下所示(以 StartsWith 为例):
case FilterOperationTypes.StartsWith:
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
PropertyInfo propertyInfo = typeof(T).GetProperty(field);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(val, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
return Expression.Lambda<Func<T, bool>>(call, e);
}
让我们假设字段是 属性 CustomerName。我知道实际的最终表达式是这样的:
e.CustomerName.StartsWith(val)
如果 CustomerName 为 null,它当然会调用 StartsWith 方法失败,这一点非常清楚。
我试过这样做:
case FilterOperationTypes.StartsWith:
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
PropertyInfo propertyInfo = typeof(T).GetProperty(field);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(val, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.IfThenElse(
Expression.Equal(m, Expression.Constant(null)),
Expression.Constant(null),
Expression.Call(m, mi, c));
//Expression.Call(m, mi, c);
return Expression.Lambda<Func<T, bool>>(call, e);
}
但这会产生类型为 'System.Void' 的表达式不能用于 return 类型 'System.Boolean' 异常。
我现在有点迷茫。也许你们可以把我推向正确的方向。
非常感谢!
您正在查找 Expression.Condition
,而不是 IfThenElse
,它表示条件运算符,而不是 if/else 语句。条件运算符解析为一个值,因为它是一个表达式,而不是一个语句。
现在可以使用了。非常感谢 Servy 将我推向正确的方向。
Expression.Condition 正是我实现目标所需要的。由于实际对象和对象的 属性 在 运行 时可以为空,因此我不得不嵌套两个条件:
PropertyInfo propertyInfo = typeof(T).GetProperty(fieldName);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(comparisonValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Call(m, mi, c)));
return Expression.Lambda<Func<T, bool>>(call, e);
在空值的情况下我决定使用
Expression.NotEqual(e, Expression.Constant(null))
一定要收到"false"。我确定有更好的方法可以做到这一点,但我还想不出一个。
最终的表达式将如下所示
{e => IIF((e == null), (e != null), IIF((e.CustomerName== null), (e != null), e.CustomerName.StartsWith("547")))}
到目前为止,我对这个解决方案非常满意,但我会尝试对其进行优化。
我当前的代码实现和用法如下所示。也许它可以帮助你们中的一个人:
Expression<Func<T, bool>> GetDetailEx<T>(string fieldName, object comparisonValue, string filterType)
{
var e = Expression.Parameter(typeof(T),"e");
switch (GetFilterOperationType(filterType))
{
case FilterOperationTypes.Between:
//Between is automatically translated in >= AND <= within the ExecuteDeepFilter-Method
break;
case FilterOperationTypes.GreaterThanOrEquals:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.GreaterThanOrEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)),e);
}
case FilterOperationTypes.LessThanOrEquals:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.LessThanOrEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)),e);
}
case FilterOperationTypes.GreaterThan:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.GreaterThan(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.LessThan:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.LessThan(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.NotEqual:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.NotEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.Equal:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.Equal(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.EndsWith:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "EndsWith",e);
}
case FilterOperationTypes.StartsWith:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "StartsWith",e);
}
case FilterOperationTypes.NotContains:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "Contains",e,false); ;
}
case FilterOperationTypes.Contains:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "Contains",e);
}
default:
break;
}
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.Equal(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
private Expression<Func<T, bool>> GenerateConditionalExpression<T>(string fieldName, object comparisonValue, Expression actualExpression, ParameterExpression e)
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), actualExpression));
return Expression.Lambda<Func<T, bool>>(call, e);
}
private Expression<Func<T, bool>> GenerateGenericCallExpression<T>(string fieldName, object comparisonValue, string methodName, ParameterExpression e, bool equals = true)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(fieldName);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(comparisonValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
if (equals)
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Call(m, mi, c)));
return Expression.Lambda<Func<T, bool>>(call, e);
}
else
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Not(Expression.Call(m, mi, c))));
return Expression.Lambda<Func<T, bool>>(call, e);
}
}