Как реализовать шаблон Pipes and Filters с помощью LinqToSQL / Entity Framework / NHibernate? - PullRequest
5 голосов
/ 24 марта 2009

При сборке в DAL Repository я наткнулся на концепцию под названием «Трубы и фильтры». Я читал об этом здесь , здесь и видел скринкаст из здесь . Я все еще не уверен, как идти о реализации этого шаблона. Теоретически все звучит хорошо, но как мы на самом деле реализуем это в сценарии предприятия?

Буду признателен, если у вас есть какие-либо ресурсы, советы или примеры для объяснения этого шаблона в контексте картографических данных / ORM, упомянутых в вопросе.

Заранее спасибо !!

1 Ответ

11 голосов
/ 29 марта 2009

В конечном счете, LINQ для IEnumerable<T> - это реализация каналов и фильтров. IEnumerable<T> - это API потокового - это означает, что данные лениво возвращаются, когда вы их запрашиваете (через блоки итераторов), вместо загрузки всего сразу и возврата большого буфера записей.

Это означает, что ваш запрос:

var qry = from row in source // IEnumerable<T>
          where row.Foo == "abc"
          select new {row.ID, row.Name};

есть:

var qry = source.Where(row => row.Foo == "abc")
            .Select(row = > new {row.ID, row.Name});

когда вы перечислите это, оно будет лениво потреблять данные. Вы можете увидеть это графически с помощью Jon Skeet Visual LINQ . Единственные вещи, которые ломают трубу, это вещи, которые вызывают буферизацию; OrderBy, GroupBy и т. Д. Для работы с большими объемами Джон и я работали над Push LINQ для выполнения агрегатов без буферизации в таких сценариях.

IQueryable<T> (доступно большинству инструментов ORM - LINQ-to-SQL, Entity Framework, LINQ-to-NHibernate) - немного другой зверь; Поскольку ядро ​​базы данных будет выполнять большую часть тяжелой работы, есть вероятность, что большинство шагов уже выполнено - все, что осталось, это потребить IDataReader и спроецировать это на объекты / значения - но это все еще обычно труба (IQueryable<T> реализует IEnumerable<T>), если вы не позвоните .ToArray(), .ToList() и т. д.

Что касается использования на предприятии ... На мой взгляд то, что можно использовать IQueryable<T> для написания составных запросов внутри хранилища, но они не должны оставлять хранилище - так как это сделало бы внутреннюю работу хранилища подчиненной вызывающей стороне, так что вы не смогли бы правильно выполнить модульное тестирование / профиль / оптимизацию / и т. д. Я взял на себя умные действия в хранилище, но возвращаю списки / массивы. Это также означает, что мой репозиторий не знает о реализации.

Это позор - так как соблазн "вернуть" IQueryable<T> из метода репозитория довольно велик; например, это позволит вызывающей стороне добавить пейджинг / фильтры / и т. д., но помните, что они на самом деле еще не использовали данные. Это делает управление ресурсами болью. Кроме того, в MVC и т. Д. Вам необходимо убедиться, что контроллер вызывает .ToList() или аналогичный, чтобы не представление, управляющее доступом к данным (иначе, опять же, вы не можете объединить проверьте контроллер правильно).

A безопасное (IMO) использование фильтров в DAL может быть следующим:

public Customer[] List(string name, string countryCode) {
     using(var ctx = new CustomerDataContext()) {
         IQueryable<Customer> qry = ctx.Customers.Where(x=>x.IsOpen);
         if(!string.IsNullOrEmpty(name)) {
             qry = qry.Where(cust => cust.Name.Contains(name));
         }
         if(!string.IsNullOrEmpty(countryCode)) {
             qry = qry.Where(cust => cust.CountryCode == countryCode);
         }
         return qry.ToArray();
     }
}

Здесь мы добавили фильтры на лету, но ничего не произойдет, пока мы не позвоним ToArray. На этом этапе данные получены и возвращены (используя контекст данных в процессе). Это может быть полностью проверено модулем. Если мы сделали нечто подобное, но только что вернули IQueryable<T>, вызывающая сторона может сделать что-то вроде:

 var custs = customerRepository.GetCustomers()
       .Where(x=>SomeUnmappedFunction(x));

И вдруг наш DAL начинает выходить из строя (не может перевести SomeUnmappedFunction в TSQL и т. Д.). Тем не менее, вы можете сделать много интересных вещей в хранилище.

Единственная болевая точка здесь заключается в том, что это может подтолкнуть вас к нескольким перегрузкам для поддержки различных шаблонов вызовов (с / без подкачки и т. Д.). Пока необязательные / именованные параметры не поступят, я считаю, что лучший ответ здесь - использовать методы расширения в интерфейсе; Таким образом, мне нужна только одна конкретная реализация репозитория:

class CustomerRepository {
    public Customer[] List(
        string name, string countryCode,
        int? pageSize, int? pageNumber) {...}
}
interface ICustomerRepository {
    Customer[] List(
        string name, string countryCode,
        int? pageSize, int? pageNumber);
}
static class CustomerRepositoryExtensions {
    public static Customer[] List(
          this ICustomerRepository repo,
          string name, string countryCode) {
       return repo.List(name, countryCode, null, null); 
    }
}

Теперь у нас есть виртуальные перегрузки (как методы расширения) для ICustomerRepository - так что наш абонент может использовать repo.List("abc","def") без указания подкачки.


Наконец - без LINQ использование труб и фильтров становится намного более болезненным. Вы будете писать какой-то текстовый запрос (TSQL, ESQL, HQL). Вы можете явно добавлять строки, но это не очень "труба / фильтр". «Criteria API» немного лучше - но не так элегантно, как LINQ.

...