Как остаться СУХИМ, используя LINQ to Entities и вспомогательные методы? - PullRequest
8 голосов
/ 15 августа 2011

Допустим, у меня есть особый способ решить, соответствуют ли некоторые строки следующим образом:

public bool stringsMatch(string searchFor, string searchIn)
{
  if (string.IsNullOrEmpty(searchFor))
  {
    return true;
  }

  return searchIn != null &&
    (searchIn.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
     searchIn.Contains(" " + searchFor));
}

Я хотел бы извлечь совпадения из базы данных с помощью Linq To Entities и этого помощника.Однако, когда я пытаюсь это сделать:

IQueryable<Blah> blahs = query.Where(b => stringsMatch(searchText, b.Name);

я получаю «LINQ to Entities не распознает метод ...»

Если я переписываю код как:

IQueryable<Blah> blahs = query.Where(b =>
      string.IsNullOrEmpty(searchText) ||
      (b.Name != null &&
        (b.Name.Trim().ToLower().StartsWith(searchText.Trim().ToLower()) ||
         b.Name.Contains(" " + searchText)));

Что логически эквивалентно, тогда все работает нормально.Проблема в том, что код не так удобен для чтения, и мне приходится переписывать его для каждого отдельного объекта, которому я хочу соответствовать.

Насколько я могу судить по таким вопросам, как этот , то, что я хочу сделать, сейчас невозможно, но я надеюсь, что что-то упустил, правда?

Ответы [ 2 ]

5 голосов
/ 15 августа 2011

Если все «бла» (классы), которые вы будете фильтровать, имеют одинаковую структуру, вы можете использовать простой метод, подобный этому.Основное отличие состоит в том, что он возвращает выражение, которое Linq должен уметь анализировать, и вводит весь экземпляр и фильтрует по имени, а не только по имени строки.

    public static Expression<Func<T, bool>> BuildStringMatch<T>(string searchFor) where T : IHasName
    {
        return b =>
              string.IsNullOrEmpty(searchFor) ||
              (b.Name != null &&
                (b.Name.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
                 b.Name.Contains(" " + searchFor)));
    }

Вы можете использовать этот методкак это:

    IQueryable<Blah> blahs = query.Where(BuildStringMatch<Blah>(searchText));

Это предполагает, что все ваши классы, которые вы хотите фильтровать, реализуют некоторый интерфейс, такой как:

    public interface IHasName
    {
        string Name { get; }
    }

Если вы хотите фильтровать по разным свойствам,Я не думаю, что это то, что вы можете сделать с таким простым кодом.Я полагаю, что вам нужно создать Expression самостоятельно с помощью отражения (или с помощью библиотеки, которая использует отражение) - это все еще возможно, но гораздо сложнее.

Редактировать: Похоже, вам нужно динамическое поведение,поэтому я позаимствовал некоторую логику из ответа dtb на этот вопрос и придумал следующее:

public static Expression<Func<T, bool>> BuildStringMatch<T>(Expression<Func<T, string>> property, string searchFor)
{
    var searchForExpression = Expression.Constant(searchFor, typeof(string));
    return
        Expression.Lambda<Func<T, bool>>(
            Expression.OrElse(
                Expression.Call(typeof(string), "IsNullOrEmpty", null, searchForExpression),
                Expression.AndAlso(
                    Expression.NotEqual(property.Body, Expression.Constant(null, typeof(string))),
                    Expression.OrElse(
                        Expression.Call(Expression.Call(Expression.Call(property.Body, "Trim", null), "ToLower", null), "StartsWith", null,
                            Expression.Call(Expression.Call(searchForExpression, "Trim", null), "ToLower", null)),
                        Expression.Call(property.Body, "Contains", null, Expression.Call(typeof(string), "Concat", null, Expression.Constant(" "), searchForExpression))
                    )
                )
            ),
            property.Parameters
        );
}

Вы бы использовали его следующим образом:

        IQueryable<Blah> blahs2 = query.Where(BuildStringMatch<Blah>(b => b.Name, searchText));

Он длинный и многословный, но вы можете видеть, как он похож на оригинальный метод, написанный на простом коде C #.Примечание. Я не тестировал этот код, поэтому может возникнуть несколько небольших проблем, но это общая идея.

4 голосов
/ 15 августа 2011

Используя свободно доступную библиотеку под названием LINQKit (как упомянуто @Eranga), эта задача становится разумной.Используя LINQKit, код, который у меня теперь есть, выглядит следующим образом:

protected Expression<Func<T, bool>> stringsMatch(string searchFor, Expression<Func<T, string>> searchIn)
{
  if (string.IsNullOrEmpty(searchFor))
  {
    return e => true;
  }

  return
    e =>
    (searchIn.Invoke(e) != null &&
      (searchIn.Invoke(e).Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
       searchIn.Invoke(e).Contains(" " + searchFor)));
}

И должен вызываться следующим образом (обратите внимание на вызов AsExpandable ())

IQueryable<Blah> blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name));

Магические части - searchIn.Вызывать (e) вызовы и использовать AsExpandable (), который добавляет слой-обертку, который позволяет им работать.

Бит AsExpandable () подробно объясняется первоначальным автором здесь .

Обратите внимание, что некоторые детали выражений все еще немного туманны, поэтому, пожалуйста, добавьте комментарий / отредактируйте этот ответ, если его можно сделать лучше / короче / яснее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...