如何应用Expression <func <string,bool>&gt;到Linq2Sql .Where()子句中的字符串属性?

时间:2017-10-09 01:09:23

标签: c# .net linq linq-to-sql

背景

我有一个返回带有以下签名的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等)子句中的内联字符串函数对着实时数据上下文而不是本地集合?

后续/解决方法

标记为重复,但我不认为链接的问题回答了这个问题,即如何使用现有的字符串,bool Expression对任意属性(与创建自定义属性感知的对比)表达树在另一个问题的接受答案中)。这是我提出的解决方法:

添加另一个这样的辅助方法:

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));

我收集的内容尽可能接近我原来想要的语法。

1 个答案:

答案 0 :(得分:1)

正如在问题中添加的解决方法中所述,无法实现所请求的确切语法。但是,可以通过以下方式实现目标:

添加一个辅助方法,将类型为T2的表达式bool应用于原始类型T的T2属性:

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);

最后,使用表达式链接语法结合内联属性过滤器,如下所示:

var result = db.DbItems.Where(propfilter.And(d => d.Active));