是否可以创建一个"元谓词"通过编写一个函数,该函数需要2(或4个必要的)lambda表示左侧和右侧属性(操作数)并使其生成谓词。类似下面的代码示例:
public Expression<Func<Something,bool>> StringEquals<Something>(Expression<Something> leftOperand, Expression<Something> leftOperandSelector){
return (rightOperandSelector, leftOperandSelector) => (
(rightOperandSelector == leftOperandSelector)
|| (
string.IsNullOrEmpty(rightOperandSelector) &&
string.IsNullOrEmpty(leftOperandSelector)
)
);
}
OR:
public Expression<Func<Something,bool>> DatesActive<Something>(Expression<Something> startDateOperandSelector, Expression<Something> endDateOperandSelector){
return (startDateOperandSelector, endDateOperandSelector) => (
(startDatePropertySelector >= DateTime.Now)
&& (endDatePropertySelector <= DateTime.Now)
);
}
每一方的 SomeStringProperty 或 startDatePropertySelector 或 endDatePropertySelector 是由lambda定义的?我还没想出如何动态传递谓词表达式的操作数。
理想情况下,我希望能够像这样内联:
return new Expression<Func<Request,bool>>[]{
r => (r.Id != request.Id) && (!r.Reviewed),
StringEquals(r => r.VendorName, request=>request.VendorName),
NotExpired(r => r.ContractStart, request=>request.ContractEnd),
...
};
* 有人对最佳方法有所了解吗?我的兴趣在于创建&#34; meta&#34; -expressions以方便使用,我在多个属性上重复使用相同的表达式。具体示例仅供参考/解释。非常愿意知道这是否愚蠢,如果有更好的方法,乐于学习。 *
如果需要,请在下方提供更多背景信息。
背景:在这个表达式的一个更简单的形式到位之后,我被迫重构来处理这样一个事实,即EntityFramework并没有像你期望的那样对待平等而不是解释C#如下:
(rightSide.SomeStringProperty == leftSide.SomeStringProperty)
像这样的两部分SQL表达式
(
(rightSide.SomeStringProperty IS NULL AND leftSide.SomeStringProperty IS NULL)
OR (rightSide.SomeStringProperty = leftSide.SomeStringProperty)
)
它更真实地将其转换为:
(rightSide.SomeStringProperty = leftSide.SomeStringProperty)
当然不会返回双方都为空的值。显然这已在EF6中得到纠正(更正:@Slauma指出这可以通过UseCSharpNullComparisonBehavior在EF5中使用。我使用的是EF4,无法升级此版本。)
我想避免更多重复的代码,如下所示:
var where = new Expression<Func<Request,bool>>[]{
r => (r.Id != request.Id) && (!r.Reviewed)
&& (
(r.Address == request.Address)
|| (string.IsNullOrEmpty(r.Address) && string.IsNullOrEmpty(request.Address))
)
&& (
(r.City == request.City)
|| (string.IsNullOrEmpty(r.City) && string.IsNullOrEmpty(request.City))
)
&& (
(r.Province == request.Province)
|| (string.IsNullOrEmpty(r.Province) && string.IsNullOrEmpty(request.Province))
)
&& (
(r.PostalCode == request.PostalCode)
|| (string.IsNullOrEmpty(r.PostalCode) && string.IsNullOrEmpty(request.PostalCode))
)
&& (
(r.Website == request.Website)
|| (string.IsNullOrEmpty(r.Website) && string.IsNullOrEmpty(request.Website))
)
};
答案 0 :(得分:2)
您可以使用System.Linq.Expressions
namespace手动构建表达式。对于您发布的两个示例,以下内容应该有效:
public static Expression<Func<T, bool>> StringEquals<T>(Expression<Func<T, string>> leftOperand, Expression<Func<T, string>> rightOperand)
{
var p = leftOperand.Parameters[0];
var leftOperandBody = leftOperand.Body;
var rightOperandBody = ReplacementVisitor.Transform(rightOperand, rightOperand.Parameters[0], p);
var isNullOrEmptyMethod = typeof(string).GetMethod("IsNullOrEmpty");
var leftNullOrEmpty = Expression.Call(isNullOrEmptyMethod, leftOperandBody);
var rightNullOrEmpty = Expression.Call(isNullOrEmptyMethod, rightOperandBody);
var bothNullOrEmpty = Expression.AndAlso(leftNullOrEmpty, rightNullOrEmpty);
var areEqual = Expression.Equal(leftOperandBody, rightOperandBody);
var body = Expression.OrElse(bothNullOrEmpty, areEqual);
return Expression.Lambda<Func<T, bool>>(body, p);
}
public static Expression<Func<T, bool>> DatesActive<T>(Expression<Func<T, DateTime>> startDate, Expression<Func<T, DateTime>> endDate)
{
var p = startDate.Parameters[0];
var startDateBody = startDate.Body;
var endDateBody = ReplacementVisitor.Transform(endDate, endDate.Parameters[0], p);
var nowProperty = typeof(DateTime).GetProperty("Now");
var nowValue = Expression.Property(null, nowProperty);
var startValid = Expression.GreaterThanOrEqual(startDateBody, nowValue);
var endValid = Expression.LessThanOrEqual(endDateBody, nowValue);
var body = Expression.AndAlso(startValid, endValid);
return Expression.Lambda<Func<T, bool>>(body, p);
}
internal sealed class ReplacementVisitor : ExpressionVisitor
{
private IList<ParameterExpression> SourceParameters { get; set; }
private Expression Find { get; set; }
private Expression Replace { get; set; }
public static Expression Transform(LambdaExpression source, Expression find, Expression replace)
{
var visitor = new ReplacementVisitor
{
SourceParameters = source.Parameters,
Find = find,
Replace = replace,
};
return visitor.Visit(source.Body);
}
private Expression ReplaceNode(Expression node)
{
return (node == Find) ? Replace : node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
return ReplaceNode(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
答案 1 :(得分:0)
这可以通过动态编写表达式树来完成,正如Richard所展示的那样,尽管它不需要那么复杂。具体来说,访问者模式增加了不必要的开考虑这个简单的解决方案,你的第一个例子:
using E = System.Linq.Expressions.Expression;
/* ... */
private static readonly MethodInfo IsNullOrEmptyMethod = typeof(string).GetMethod("IsNullOrEmpty");
public static Expression<Func<bool>> StringEquals(Expression<Func<String>> leftAccessor, Expression<Func<String>> rightAccessor)
{
var left = E.Parameter(typeof(string), "left");
var right = E.Parameter(typeof(string), "left");
// () => {
// string left = leftAccessor();
// string right = rightAccessor();
//
// return left == right ||
// string.IsNullOrEmpty(left) && string.IsNullOrEmpty(right);
// }
return E.Lambda<Func<bool>>(
E.Block(
new[] { left, right },
E.Assign(left, E.Invoke(leftAccessor)),
E.Assign(right, E.Invoke(rightAccessor)),
E.OrElse(
E.Equal(left, right),
E.AndAlso(
E.Call(IsNullOrEmptyMethod, left),
E.Call(IsNullOrEmptyMethod, right)))));
}
您可以应用类似的技术来设计第二个示例的解决方案。请注意,缺少任何通用参数:不需要公开包含属性的实际项。您可以使用此方法比较同一对象上的两个属性;两个不同对象上的相同属性;或任何任意值。
请注意,lambda编译器将负责内联() => r.Address
之类的简单访问器。它可以很容易地做到这一点,因为访问器本身就是表达式。
编辑:再次阅读您的问题,我发现您正在使用Entity Framework。我不确定EF的查询提供程序是否足够复杂以内联访问者。如果不是,这可能不起作用,在这种情况下,可能需要像理查德在他的回答中那样做一些手动转换。我有兴趣听听这是否适用于您的情况。我将以任何一种方式留下这个答案,因为它可能对不使用EF的人有用。
另外,正如@svick在评论中指出的那样,EF几乎肯定不支持块表达式。您可能必须按如下方式构造lambda:
return E.Lambda<Func<bool>>(
E.OrElse(
E.Equal(E.Invoke(leftAccessor), E.Invoke(rightAccessor)),
E.AndAlso(
E.Call(IsNullOrEmptyMethod, E.Invoke(leftAccessor)),
E.Call(IsNullOrEmptyMethod, E.Invoke(rightAccessor)))));