Основанный на Linq альтернативный предикат <T>? - PullRequest
9 голосов
/ 06 мая 2010

У меня есть интерфейс с именем ICatalog, как показано ниже, где у каждого ICatalog есть имя и метод, который будет возвращать элементы на основе функции Predicate<Item>.

public interface ICatalog
{
     string Name { get; }
     IEnumerable<Item> GetItems(Predicate<Item> predicate);
}

Конкретная реализация каталога может быть связана с каталогами в различных форматах, таких как XML или база данных SQL.

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

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

Это похоже на проблему, которую можно решить с помощью Linq, но я довольно новичок в этом. Должен ли мой интерфейс возвращать IQueryable вместо этого? Меня сейчас не интересует, как на самом деле реализовать SQL-версию моего ICatalog. Я просто хочу убедиться, что мой интерфейс позволит это сделать в будущем.

Ответы [ 2 ]

7 голосов
/ 06 мая 2010

Роб указал, как вы можете это сделать (хотя более классический подход LINQ может занять Expression<Func<Item,bool>> и, возможно, вернуть IQueryable<IFamily>).

Хорошей новостью является то, что если вы хотите использовать предикат с LINQ-to-Objects (для вашего xml-сценария), вы можете просто использовать:

Predicate<Item> func = predicate.Compile();

или (для другой подписи):

Func<Item,bool> func = predicate.Compile();

и у вас есть делегат (func) для тестирования ваших объектов.

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

Проблема в том, что вы не можете надежно высмеивать (с помощью LINQ-to-Objects) что-либо, связанное со сложными хранилищами данных; например, следующее будет хорошо работать в ваших модульных тестах, но не будет работать «по-настоящему» с базой данных:

var foo = GetItems(x => SomeMagicFunction(x.Name));

static bool SomeMagicFunction(string name) { return name.Length > 3; } // why not

Проблема в том, что только некоторые операции могут быть переведены в TSQL. Вы получаете ту же проблему с IQueryable<T> - например, EF и LINQ-to-SQL поддерживают разные операции над запросом; даже First() ведет себя по-разному (EF требует, чтобы вы сначала явно указали его, а LINQ-to-SQL - нет).

Итак, в итоге:

  • может работать
  • но подумайте, хотите ли вы это сделать; более классический интерфейс хранилища / службы черного ящика может быть более тестируемым
5 голосов
/ 06 мая 2010

Вам не нужно идти до конца и создавать IQueryable реализацию

Если вы объявите свой метод GetItems как:

IEnumerable<IFamily> GetItems(Expression<Predicate<Item>> predicate);

Тогда ваш реализующий класс может проверить выражение, чтобы определить, о чем идет речь.

Прочтите статью IQueryable , потому что она объясняет, как создать посетителя дерева выражений, для которого вам потребуется создать простую версию.

...