创建动态EF过滤器,以针对任何字符串实体属性构建LINQ where equals / contains语句

时间:2018-12-04 22:15:31

标签: c# entity-framework linq expression iqueryable

简而言之,我打算使用实体框架6做what this guy did

实施建议的解决方案会导致错误“ LINQ to Entities不支持LINQ表达式节点类型'Invoke'。” 由于建议的解决方案使用Invoke,因此很明显一个问题。

我知道有一种方法可以利用a custom Compose method来重写表达式树而无需使用Invoke,但是我似乎无法将其束之高阁。

这就是我要写的。

我使用IQueryable<TEntity>对象动态构建QueryParameters,该对象只是用于WHERE子句的一袋属性。 TEntity是一个标准的代码优先EF实体,在整个地方都有数据注释。查询构造看起来像这样:

IQueryable<TEntity> query = Context.Set<TEntity>();

if (queryParams == null)
    return query;

if (!string.IsNullOrWhiteSpace(queryParams.FirstName))
{
    if (queryParams.ExactSearch)
    {
        query = query.Where(x => x.FirstName == queryParams.FirstName);
    }
    else
    {
        if (queryParams.PreferStartsWith)
        {
            query = query.Where(
                x => x.FirstName.ToLower()
                    .StartsWith(
                        queryParams.FirstName
                            .ToLower()));
        }
        else
        {            
            query = query.Where(
                x => x.FirstName.ToLower()
                    .Contains(
                        queryParams.FirstName
                            .ToLower()));
        }
    }
}

// ... repeat for all of queryParams' string props.
// DateTime, int, bool, etc have their own filters.

对于要查询的字符串字段的每个查询参数都会重复此操作。显然,这导致了很多重复的代码。我希望能够编写一个具有如下签名的过滤器:

public static IQueryable<TEntity> Search<TEntity>(
    this IQueryable<TEntity> query,
    Expression<Func<TEntity, string>> fieldExpression,
    string searchValue,
    bool exactSearch = true,
    bool useStartsWithOverContains = false) {...}

然后我可以像这样食用:

if (!string.IsNullOrWhiteSpace(queryParams.FirstName))
{
    query = query.Search(
                x => x.FirstName,
                queryParams.FirstName,
                queryParams.ExactSearch,
                queryParams.PreferStartsWith);
}

以下是我为该扩展方法定义的最接近的方法,但是如上所述,它会产生“ LINQ to Entities不支持'Invoke'”错误:

public static IQueryable<TEntity> Search<TEntity>(
    this IQueryable<TEntity> query,
    Expression<Func<TEntity, string>> fieldExpression,
    string searchValue,
    bool exactSearch = true,
    bool useStartsWithOverContains = false)
{
    if (string.IsNullOrWhiteSpace(searchValue))
        return query;

    searchValue = searchValue.Trim();

    Expression<Func<TEntity, bool>> expression;

    if (exactSearch)
    {
        var x = Expression.Parameter(typeof(TEntity), "x");

        var left = Expression.Invoke(fieldExpression, x);
        var right = Expression.Constant(searchValue);
        var equalityExpression = Expression.Equal(left, right);

        expression = Expression.Lambda<Func<TEntity, bool>>(
            equalityExpression,
            x);
    }
    else
    {
        searchValue = searchValue.ToLower();
        var x = Expression.Parameter(typeof(TEntity), "x");

        var fieldToLower = Expression.Call(
            Expression.Invoke(fieldExpression, x),
            typeof(string).GetMethod(
                "ToLower",
                Type.EmptyTypes));
        var searchValueExpression =
            Expression.Constant(searchValue);

        var body = Expression.Call(
            fieldToLower,
            typeof(string).GetMethod(
                useStartsWithOverContains ? "StartsWith" : "Contains",
                new[] { typeof(string) }),
            searchValueExpression);

        expression = Expression.Lambda<Func<TEntity, bool>>(
            body,
            x);
    }

    return query.Where(expression);
}

我开始加入我提到的Compose method,但是我很快就迷路了,因此将其删除。

打开任何指导!谢谢!

1 个答案:

答案 0 :(得分:1)

通过构成表达式比通过每次尝试手动构造表达式要容易得多。编写起来更快,如此,出错的可能性要小得多,并且实际上以您可以在末尾实际读取的代码结束。您所需要做的就是编写代码,以了解如何在组合表达式中使用该值,而您原来的代码中已有该值。

public static IQueryable<TEntity> Search<TEntity>(
    this IQueryable<TEntity> query,
    Expression<Func<TEntity, string>> fieldExpression,
    string searchValue,
    bool exactSearch = true,
    bool useStartsWithOverContains = false)
{
    if (string.IsNullOrWhiteSpace(searchValue))
        return query;

    searchValue = searchValue.Trim();

    if (exactSearch)
    {
        return query.Where(fieldExpression.Compose(field => field == searchValue));
    }
    else if (useStartsWithOverContains)
    {
        return query.Where(fieldExpression.Compose(field => field.StartsWith(searchValue.ToLower())));
    }
    else
    {
        return query.Where(fieldExpression.Compose(field => field.Contains(searchValue.ToLower())));
    }
}

请注意,您可能应该使用“比较”之类的枚举,而不要使用两个布尔值。例如,现在有人可以说他们不想要确切的确定,但是他们确实想使用始于。只需使用三个选项中的一个参数即可。