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

Creating a dynamic EF filter that builds a LINQ Where equals/contains statement for any string entity property

简而言之,我想做 what this guy did,但是 Entity Framework 6.

实施建议的解决方案导致错误 "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." 由于建议的解决方案使用 Invoke,这显然是一个问题。

我知道有一种方法可以利用 a custom Compose method 在不使用 Invoke 的情况下重写表达式树,但我似乎无法理解它。

这是我要写的内容。

我使用 QueryParameters 对象动态构建了一个 IQueryable<TEntity>,该对象只是用于 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,但我很快就迷路了,因此将其删除。

接受任何指导!谢谢!

与每次都尝试手动构造表达式相比,通过编写表达式容易得多。它的编写速度更快,所以 更不容易出错,而且实际上最终得到的代码你可以在它的末尾实际阅读。您需要做的就是编写有关如何在组合表达式中使用值的代码,您已经从原始代码中获得了这些值。

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

请注意,您可能应该为 "Comparison" 或类似的东西使用枚举,而不是使用两个布尔值。例如,现在有人可以说他们不想要一个确切的确定,但他们确实想要使用 starts with。三个选项只有一个参数。