可查询和自定义过滤-使用Expression>

本文关键字:Expression Func 使用 查询 自定义 过滤 | 更新日期: 2023-09-27 18:19:06

我面临一个新问题

我有以下实体(我使用流利的nhibernate,但这里无关紧要)

public class SomeEntity
{
     public virtual string Name { get; set; }
}
过滤器类:

public class FilterOptions
{
    public string logic { get; set; }             // "and", "or"
    public FilterItems[] filters { get; set; }
}
public class FilterItems
{
    public string @operator { get; set; }
    public string value { get; set; }           //string value provided by user
}

@operator属性可以有以下值

EndsWith
DoesNotContain
Contains
StartsWith
NotEqual
IsEqualTo

我要做的就是基于两个过滤器做一些过滤操作:

private IQueryable<SomeEntity> BuildQuery(FilterOptions opts)
{
    IQueryable<SomeEntity> query = Session.Query<SomeEntity>();
    var firstFilter = opts.filters[0];
    var secondFilter = opts.filters[1];
}

因为@operator属性可以有这么多选项,我想知道是否有可能使用swich操作符的外部方法,并在.Where方法内部使用该方法。

之类的
  var query = query.Where(firstSwitchFilterMethod && secondFilterMethod)

伪代码:

firstSwitchFilterMethod:
if (firstFilter.@operator == "Contains")
    return SomeEntity.Name.Contains(firstFilter.value);

等等…

有什么想法吗?我在考虑Expression<Func<>>——它的方向好吗?如果是的话,在我的情况下如何使用它?

或者,也许建立自己的SomeEntity的扩展方法,这将使用该过滤器类?

可查询和自定义过滤-使用Expression<Func<>>

您不能任意调用函数表达式并期望它可以转换为SQL。但是有一些函数,比如StartsWith,可以。下面是一个例子,教你如何构建自己的表达式:

protected IQueryable<T> GetFiltered<T>(IQueryable<T> query, string filterOnProperty, string startsWithString, string endsWithString)
{
    LambdaExpression startsWithLambda = (Expression<Func<string, string, bool>>)((x, s) => x.StartsWith(s));
    MethodInfo startsWithMI = (startsWithLambda.Body as MethodCallExpression).Method;
    LambdaExpression endsWithLambda = (Expression<Func<string, string, bool>>)((x, s) => x.EndsWith(s));
    MethodInfo endsWithMI = (endsWithLambda.Body as MethodCallExpression).Method;
    ParameterExpression param = Expression.Parameter(typeof(T));
    Expression nameProp = Expression.Property(param, filterOnProperty);
    Expression filteredOk = Expression.Constant(true);
    Expression startsWithStringExpr = Expression.Constant(startsWithString);
    Expression startsWithCondition = Expression.Call(nameProp, startsWithMI, startsWithStringExpr);
    filteredOk = Expression.AndAlso(filteredOk, startsWithCondition);
    Expression endsWithStringExpr = Expression.Constant(endsWithString);
    Expression endsWithCondition = Expression.Call(nameProp, endsWithMI, endsWithStringExpr);
    filteredOk = Expression.AndAlso(filteredOk, endsWithCondition);
    Expression<Func<T, bool>> where = Expression.Lambda<Func<T, bool>>(filteredOk, new ParameterExpression[] { param });
    return query.Where(where);
}

用法简单

DCDataContext dc = new DCDataContext();
var query = dc.testtables.AsQueryable();
query = GetFiltered(query, "name", "aaa", "2");

NHibernate或任何其他LINQ提供程序并不真正关心你如何构建表达式-唯一重要的是最终表达式只包含LINQ提供程序理解并知道如何处理的结构。

是的,你可以让一个方法返回一个Expression<Func<>>,然后使用Where()方法将该表达式添加到LINQ查询中。

然而,你不能要求NHibernate分析你编译的代码并将if语句转换为SQL。您的方法将需要分析选项并返回一个合适的表达式,该表达式将插入到完整的LINQ查询中。

你可以在一个方法中这样写:

IQueryable<SomeEntity> query = Session.Query<SomeEntity>();
if (isEqOp)
    query = query.Where(e => e.Name == options.Value)
if (isContainsOp)
    query = query.Where(e => e.Name.Contains(options.Value))
query.ToList();

或者如果你想在一个单独的方法中使用过滤器逻辑,你也可以像这样分割它:

public Expression<Func<Entity, bool>> GetExpression(options)
{
    if (isEqOp)
        return (Entity e) => e.Name == options.Value;
    if (isContainsOp)
        return (Entity e) => e.Name.Contains(options.Value);
}

{
     IQueryable<SomeEntity> query = Session.Query<SomeEntity>();
     query = query.Where(GetExpression(options))
     ...
}