Мне назвали интерфейс ICatalog
как показано, ниже где каждый ICatalog
имеет имя и метод, который возвратит объекты на основе a 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. Я просто хочу удостовериться, что мой интерфейс будет допускать его в будущем.
Роб указал, как это можно сделать (хотя более классический подход LINQ может использовать Expression
, и, возможно, вернуть IQueryable
).
Хорошая новость заключается в том, что если вы хотите использовать предикат с 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
- например, EF и LINQ-to-SQL поддерживают разные операции с запросом; даже просто First ()
ведет себя по-другому (EF требует, чтобы вы явно упорядочили его первым, LINQ-to-SQL - нет).
Итак, вкратце:
Вам не нужно идти до конца и создавать реализацию IQueryable
Если вы объявите свой метод GetItems как:
IEnumerable<IFamily> GetItems(Expression<Predicate<Item>> predicate);
Тогда ваш реализующий класс может проинспектировать Expression, чтобы определить, что спрашивается.
Прочитайте статью IQueryable, потому что в ней объясняется, как создать посетителя дерева выражений, который вам понадобится для создания простой версии.