【发布时间】:2017-10-09 01:09:23
【问题描述】:
背景
我有一个方法可以返回具有以下签名的 Linq 表达式:
public static Expression<Func<string, bool>> GetStringFilterExpression(IEnumerable<string> fields)
然后我将其放入局部变量中:
var filterby = GetStringFilterExpression(new[] {"Copy*Z"});
给定一个 Linq2Sql 数据上下文 db 和一个表 DbItems,这允许我这样做:
var names = db.DbItems.Select(d=>d.Name).Where(filterby);
var result = db.DbItems.Where(d=>names.Contains(d.Name);
或者我可以使用这个辅助方法
public static IQueryable<T> MatchesFilter<T>(IQueryable<T> query, Expression<Func<T, string>> selector, Expression<Func<string, bool>> filter)
{
LambdaExpression lambda = (LambdaExpression)selector;
var predicate = Expression.Invoke(filter, selector.Body);
var lambdaPredicate = Expression.Lambda<Func<T, bool>>(predicate, lambda.Parameters);
return query.Where(lambdaPredicate);
}
并执行更简洁的查询:
var result = MatchesPattern<DbItems>(db.DbItems, d=>d.Name, filterby);
两种方法都有效,返回相同的结果,并将表达式作为正确过滤的 SQL 查询运行(无本地集合)。
目标
我想做的是使用普通 .Where() 子句中的函数,因此它可以与其他逻辑组合或在多个字段上调用,例如:
var result = db.DbItems.Where(d => filterby(d.Name) && d.Active);
但要做到这一点,我必须使用 Func 而不是 Expression。我尝试像这样展开表达式:
public static Func<string, bool> CreateStringFilter(Expression<Func<string, bool>> filterby)
{
return p => filterby.Invoke(p);
}
var inlinefilter = CreateStringFilter(filterby);
这确实允许这样做:
var result = db.DbItems.ToList().Where(d => inlinefilter(d.Name));
但是,这需要将整个表拉到本地集合中。如果我删除 .ToList(),我会收到“不支持 SQL 转换”错误。
方法 'System.Object DynamicInvoke(System.Object[])' 不支持 翻译成 SQL。
即使它与包含在表达式中时作为 SQL 运行的代码完全相同。
有没有办法让它在 .Where(或 .Count 等)子句中针对实时数据上下文而不是本地集合作为内联字符串函数工作?
跟进/解决方法
标记为重复,但我觉得链接的问题没有回答这个问题,即如何使用 existing 字符串,bool 表达式针对任意属性(与创建自定义属性感知另一个问题的已接受答案中的表达式树)。这是我想出的解决方法:
像这样添加另一个辅助方法:
public static Expression<Func<T,bool>> PropFilter<T,T2>(Expression<Func<T, T2>> selector, Expression<Func<T2, bool>> filter)
{
LambdaExpression lambda = (LambdaExpression)selector;
var predicate = Expression.Invoke(filter, selector.Body);
return Expression.Lambda<Func<T, bool>>(predicate, lambda.Parameters);
}
然后我可以基于原始字符串过滤器创建特定于属性的过滤器,并像这样链接它们:
var filtername = PropFilter<DbItems, string>(d => d.Name, filterby);
var filterdesc = PropFilter<DbItems, string>(d => d.Description, filterby);
var propfilter = filtername.Or(filterdesc);
最后,我可以在 Linq 中调用该过滤器并与内联属性过滤器结合使用,如下所示:
var result = db.DbItems.Where(propfilter.And(d => d.Active));
我认为这与我要达到的原始所需语法一样接近。
【问题讨论】:
-
我在这被标记为重复之后添加了后续和解决方法。我觉得链接的问题没有专门回答这个问题,即如何以属性盲的方式使用现有的字符串表达式。如果重复状态被删除,我将添加我的解决方法作为答案。谢谢。
-
@servy 我不相信这是this question 的副本,因为它专门用于组合表达式。这个问题是关于将不同类型的现有表达式应用于父类型的任意属性,如我提供的答案所示。
-
@servy 虽然this question 是有用的背景,但它没有回答当前关于如何在Linq 的.Where 子句中尽可能直接地利用现有基本类型表达式的问题。所以我认为这个问题和答案仍然增加了价值和可搜索性。
-
它解决了您所说的确切问题。您认为它在哪些方面未能回答您提出的问题?
-
我同意它在概念上是相关的,而另一个问题的答案对于想出这个问题的答案非常有用。但这不是同一个问题。在发布之前我没有找到 Q+A,但即使我找到了,我也不认为我会认识到它是同样的问题,也许是因为它缺少任何具体的使用示例。这个问题具体是关于如何在 .Where() 子句中针对父属性使用基类表达式,而另一个问题只是抽象地与此相关。
标签: c# .net linq linq-to-sql