在 C# 中使用动态或表达式
Using Dynamic Or Expressions in C#
我在我的程序中使用动态过滤器,就像下面的解决方案:
public static IQueryable<T> MyFilter<T>(this IQueryable<T> queryable) where T : class ,IModel
{
var someIds = new int[]{1,2,3,4,5};
var userId = 2;
Expression<Func<T, bool>> predicate1 = e => someIds.Contains(e.Id);
Expression<Func<T, bool>> predicate2 = e => e.UserId==userId;
Expression<Func<T, bool>> predicate3 = e => e.CreatedDate != null;
var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));
var pred2 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(pred1, predicate3));
var result = queryable
.Where(pred2);
return result;
}
IModel
是这样的:
Public interface IModel{
int Id { get; set; }
int UserId { get; set; }
DateTime? CreatedDate { get; set; }
}
但是,我得到这个错误:
The binary operator Or is not defined for the types 'System.Func2[AnyClass,System.Boolean]' and 'System.Func
2[AnyClass,System.Boolean]'.
在这一行:
var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));
我该如何解决这个问题?
预先感谢任何帮助
正如我在评论中所写,存在两个不同的问题:
您必须使用 Expression.OrElse
,因为 Expression.Or
是 |
运算符。
但真正的问题是您不能以这种方式连接 Lambda 表达式(predicate1
、2
、3
)。连接表达式很复杂,因为predicate1
、2
、3
的输入参数不同(好像是e1
、e2
、e3
而不是 e
),并且 pred1
和 pred2
将有一个 e4
和 e5
输入参数,因此您必须替换一些表达式。
解决方法:
// Note that we only need the Body!
Expression pred1 = Expression.OrElse(Expression.OrElse(predicate1.Body, predicate2.Body), predicate3.Body);
// We change all the predicate2.Parameters[0] to predicate1.Parameters[0] and
// predicate3.Parameters[0] to predicate1.Parameters[0]
var replacer = new SimpleExpressionReplacer(
/* from */ new[] { predicate2.Parameters[0], predicate3.Parameters[0] },
/* to */ new[] { predicate1.Parameters[0], predicate1.Parameters[0] });
pred1 = replacer.Visit(pred1);
// We use for the new predicate the predicate1.Parameters[0]
var pred2 = Expression.Lambda<Func<T, bool>>(pred1, predicate1.Parameters[0]);
var result = queryable.Where(pred2);
和 SimpleExpressionReplacer
:
// A simple expression visitor to replace some nodes of an expression
// with some other nodes
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
{
Replaces = replaces;
}
public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
{
Replaces = new Dictionary<Expression, Expression>();
using (var enu1 = from.GetEnumerator())
using (var enu2 = to.GetEnumerator())
{
while (true)
{
bool res1 = enu1.MoveNext();
bool res2 = enu2.MoveNext();
if (!res1 || !res2)
{
if (!res1 && !res2)
{
break;
}
if (!res1)
{
throw new ArgumentException("from shorter");
}
throw new ArgumentException("to shorter");
}
Replaces.Add(enu1.Current, enu2.Current);
}
}
}
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
你可以创建一个没有Expression.Or的谓词:
public static IQueryable<T> MyFilter<T>(this IQueryable<T> queryable) where T : class ,IModel
{
var someIds = new int[] { 1, 2, 3, 4, 5 };
var userId = 2;
var result = queryable
.Where(e => someIds.Contains(e.Id) || e.UserId == userId || e.CreatedDate != null);
return result;
}
使用PredicateBuilder
从here, or install the LINQKit NuGet package(依赖于Entity Framework)复制class的源代码,得到PredicateBuilder
class。将表达式动态链接在一起很简单:
var predicate = PredicateBuilder.False<int>();
predicate = predicate.Or(x => x > 5);
predicate = predicate.Or(x => x % 2 == 0);
predicate = predicate.Or(x => x == 42 );
var queryable = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsQueryable();
var result = queryable.Where(predicate);
我在我的程序中使用动态过滤器,就像下面的解决方案:
public static IQueryable<T> MyFilter<T>(this IQueryable<T> queryable) where T : class ,IModel
{
var someIds = new int[]{1,2,3,4,5};
var userId = 2;
Expression<Func<T, bool>> predicate1 = e => someIds.Contains(e.Id);
Expression<Func<T, bool>> predicate2 = e => e.UserId==userId;
Expression<Func<T, bool>> predicate3 = e => e.CreatedDate != null;
var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));
var pred2 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(pred1, predicate3));
var result = queryable
.Where(pred2);
return result;
}
IModel
是这样的:
Public interface IModel{
int Id { get; set; }
int UserId { get; set; }
DateTime? CreatedDate { get; set; }
}
但是,我得到这个错误:
The binary operator Or is not defined for the types 'System.Func
2[AnyClass,System.Boolean]' and 'System.Func
2[AnyClass,System.Boolean]'.
在这一行:
var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));
我该如何解决这个问题?
预先感谢任何帮助
正如我在评论中所写,存在两个不同的问题:
您必须使用
Expression.OrElse
,因为Expression.Or
是|
运算符。但真正的问题是您不能以这种方式连接 Lambda 表达式(
predicate1
、2
、3
)。连接表达式很复杂,因为predicate1
、2
、3
的输入参数不同(好像是e1
、e2
、e3
而不是e
),并且pred1
和pred2
将有一个e4
和e5
输入参数,因此您必须替换一些表达式。
解决方法:
// Note that we only need the Body!
Expression pred1 = Expression.OrElse(Expression.OrElse(predicate1.Body, predicate2.Body), predicate3.Body);
// We change all the predicate2.Parameters[0] to predicate1.Parameters[0] and
// predicate3.Parameters[0] to predicate1.Parameters[0]
var replacer = new SimpleExpressionReplacer(
/* from */ new[] { predicate2.Parameters[0], predicate3.Parameters[0] },
/* to */ new[] { predicate1.Parameters[0], predicate1.Parameters[0] });
pred1 = replacer.Visit(pred1);
// We use for the new predicate the predicate1.Parameters[0]
var pred2 = Expression.Lambda<Func<T, bool>>(pred1, predicate1.Parameters[0]);
var result = queryable.Where(pred2);
和 SimpleExpressionReplacer
:
// A simple expression visitor to replace some nodes of an expression
// with some other nodes
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
{
Replaces = replaces;
}
public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
{
Replaces = new Dictionary<Expression, Expression>();
using (var enu1 = from.GetEnumerator())
using (var enu2 = to.GetEnumerator())
{
while (true)
{
bool res1 = enu1.MoveNext();
bool res2 = enu2.MoveNext();
if (!res1 || !res2)
{
if (!res1 && !res2)
{
break;
}
if (!res1)
{
throw new ArgumentException("from shorter");
}
throw new ArgumentException("to shorter");
}
Replaces.Add(enu1.Current, enu2.Current);
}
}
}
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
你可以创建一个没有Expression.Or的谓词:
public static IQueryable<T> MyFilter<T>(this IQueryable<T> queryable) where T : class ,IModel
{
var someIds = new int[] { 1, 2, 3, 4, 5 };
var userId = 2;
var result = queryable
.Where(e => someIds.Contains(e.Id) || e.UserId == userId || e.CreatedDate != null);
return result;
}
使用PredicateBuilder
从here, or install the LINQKit NuGet package(依赖于Entity Framework)复制class的源代码,得到PredicateBuilder
class。将表达式动态链接在一起很简单:
var predicate = PredicateBuilder.False<int>();
predicate = predicate.Or(x => x > 5);
predicate = predicate.Or(x => x % 2 == 0);
predicate = predicate.Or(x => x == 42 );
var queryable = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsQueryable();
var result = queryable.Where(predicate);