Создание динамического фильтра EF, который строит LINQ, где равно / содержит оператор для любого свойства строкового объекта - PullRequest
0 голосов
/ 05 декабря 2018

Короче говоря, я хочу сделать , что этот парень сделал , но с Entity Framework 6.

Реализация предложенного решения приводит к ошибке "Выражение LINQТип узла 'Invoke' не поддерживается в LINQ to Entities. " Поскольку в предлагаемом решении используется Invoke, это, очевидно, проблема.

Я понимаю, что есть способ использовать aпользовательский метод Compose для переписывания дерева выражений без использования 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);
}

Ближайшее определение, которое я получилэтот метод расширения приведен ниже, но, как уже упоминалось, он выдает, что «Invoke» не поддерживается в LINQ to Entities »:

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 Я упомянул, но я очень быстро заблудился, и поэтому удалил его.

Открыт для любых указаний!Спасибо!

1 Ответ

0 голосов
/ 05 декабря 2018

Это намного легче сделать, составляя выражения, чем пытаясь вручную создавать выражения каждый раз.Это быстрее писать, , так что намного меньше подвержено ошибкам, и на самом деле заканчивается кодом, который вы действительно можете прочитать в конце.Все, что вам нужно сделать, это написать код для того, как вы используете значение в составленном выражении, которое у вас уже есть из вашего исходного кода.

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

Обратите внимание, что вы, вероятно, должны использовать перечисление для "Сравнения"или что-то в этом роде, вместо двух логических значений.Например, сейчас кто-то может сказать, что он не хочет точно знать, но что он хочет использовать, начинается с.Просто укажите один параметр с тремя вариантами.

...