2

Попытка создания действительно простого шаблона репозитория и сервисного слоя здесь. (.NET 4, C#, LINQ, хотя этот вопрос является частично языковым агностиком). Примечание: это только R & D.Советы с шаблоном проектирования хранилища/сервисного уровня

Моя цель - минимизировать количество определений методов в моем сервисном слое.

Вот мой Repository контракта:

interface IFooRepository 
{ 
    IEnumerable<Foo> Find(); 
    void Insert(Foo foo); 
    void Update(Foo foo); 
    void Delete(Foo foo); 
} 

Ничего нового.

Теперь, вот что им (пытается) иметь в своем договоре обслуживания:

interface IFooDataService 
{ 
    public IEnumerable<Foo> Find(FooSearchArgs searchArgs); 
} 

По существу, любой конкретный «Foo» обладает многими свойствами (ID, имя и т.д.), которые я хотел бы быть возможность поиска.

Итак, я не хочу иметь метод поиска 1x для каждого другого свойства, я просто хочу один - таким образом, когда создаю дополнительные свойства, мне не нужно изменять контракты.

«FooSearchArgs» - это просто POCO со всеми различными свойствами «Foo».

Итак, это то, что им пытаются сделать, вот мои вопросы:

  • Является ли это плохой дизайн? Если да, то каковы альтернативы?
  • Как реализовать эту фильтрацию на уровне сервиса? Должен ли я проверить, какие свойства «FooSearchArgs» установлены, а затем продолжать фильтрацию? (если это, то query.where, если это, query.where и т. д.). У кого-нибудь есть идея умного метода расширения LINQ IEnumerable для этого? (т.е. repository.WhereMeetsSearchCriteria(fooSearchArgs))

Оцените справку.

ответ

3

Мы используем нечто очень похожее. Единственное, что вам нужно решить, - это разоблачить IQueryable вне репозитория. Метод find возвращает IEnumerable, который может быть IQueryable, возвращенный из вашего предложения where.

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

repository.Find(predicate).Where(x => x.SomeValue == 1); 

Выражение будет скомпилировано только в том случае, если вы приступите к использованию возвращенных данных, и здесь находится недостаток. Поскольку вы попадаете в базу данных только тогда, когда вы действительно используете результаты, вы можете в конечном итоге попытаться вызвать базу данных после того, как ваш сеанс (nhibernate) или соединения были закрыты.

Мое личное предпочтение заключается в использовании шаблона спецификации, в котором вы передаете свой метод поиска, для выполнения запроса используется объект ISpecification.

public interface ISpecification<TCandidate> 
{ 
    IQueryable<TCandidate> GetSatisfyingElements(IQueryable<TCandidate> source); 
} 

public class TestSpecification : ISpecification<TestEntity> 
{ 
    public IQueryable<TestEntity> GetSatisfyingElements(IQueryable<TestEntity> source) 
    { 
     return source.Where(x => x.SomeValue == 2); 
    } 
} 

public class ActiveRecordFooRepository: IFooRepository 
{ 
    ... 

    public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> specification) where TEntity : class 
    { 
     ... 

     return specification.GetSatisfyingElements(ActiveRecordLinq.AsQueryable<TEntity>()).ToArray(); 

     ... 
    } 

    public TEntity FindFirst<TEntity>(ISpecification<TEntity> specification) where TEntity : class 
    { 
     return specification.GetSatisfyingElements(ActiveRecordLinq.AsQueryable<TEntity>()).First(); 
    } 
} 

После того, как запрос выполняется репозиторий вызовов ToArray или ToList на полученный IQueryable вернулся из спецификации, так что запрос оценивается там и тогда. Хотя это может показаться менее гибким, чем разоблачение IQueryable, оно имеет несколько преимуществ.

  1. Запросы выполняются немедленно и предотвращают вызов в базу данных после закрытия сеансов.
  2. Поскольку ваши запросы теперь включены в спецификации, они могут быть подвергнуты тестированию.
  3. Спецификации являются многоразовыми, что означает, что у вас нет дублирования кода при попытке запуска похожих запросов, и любые ошибки в запросах должны быть исправлены только в одном месте.
  4. При правильной реализации вы также можете объединить свои спецификации вместе.

repository.Find(
    firstSpecification 
     .And(secondSpecification) 
     .Or(thirdSpecification) 
     .OrderBy(orderBySpecification)); 
+0

Интересный взять. Однако я предпочитаю использовать отложенное выполнение (LINQ), поскольку он дает намного более тонкий контроль над запросами (что позволяет мне создавать их на уровне сервиса). Закрытые сеансы не являются проблемой, если использовать другой шаблон дизайна для борьбы с этим (например, UnitOfWork). Спасибо за ответ, хотя (+1) - плохо прочитали шаблон спецификации. – RPM1984

+0

Вы можете найти это интересно тогда: http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/ Это пример того, как реализовать сохраняя неосведомленный репозиторий, используя спецификацию и единицу рабочих шаблонов. – Bronumski

+0

И этот: http://www.kitchaiyong.net/2009/10/repository-specification-unit-of-work.html. Это идет немного глубже. – Bronumski

0

Передача Func в качестве параметра метода поиска вашего уровня сервиса, а не FooSearchArgs, опция? В перечислениях есть метод Where (linq), который принимает параметр Func как параметр, поэтому вы можете использовать его для фильтрации результатов.

+0

Сво вариант, да - им еще изучают деревья выражений/лямбды. Есть ли вероятность, что вы могли бы расширить свой ответ на примере (относящийся к моему вышеупомянутому вопросу)? Спасибо – RPM1984

+0

Это может быть неэффективно, хотя если не использовать Linq, так как вы могли бы вернуть больше результатов из базы данных, чем требуется. – Craig

+0

@ Craig im, использующий LINQ. На данный момент есть только поддельный репозиторий, но реальный будет либо L2SQL, либо EF. – RPM1984

Смежные вопросы