2010-02-27 2 views
14

Я занимаюсь разработкой своего приложения ASP.NET MVC, и я наткнулся на пару интересных мыслей.Дизайн слоя абстракции базы данных - с помощью IRepository правильный путь?

Многие образцы, которые я видел, описывают и используют шаблон хранилища (IRepository), так что я сделал это, когда учился MVC.

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

В настоящее время у меня есть базовый IUserRepository, который определяет методы, такие как FindById(), SaveChanges() и т.д.

В настоящее время, когда я хочу, чтобы загрузить/запрос таблицы пользователей в БД, я что-то вдоль линий следующий:

private IUserRepository Repository; 

    public UserController() 
     : this(new UserRepository()) 
    { } 

    [RequiresAuthentication] 
    [AcceptVerbs(HttpVerbs.Get)] 
    public ActionResult Edit(string ReturnUrl, string FirstRun) 
    { 
     var user = Repository.FindById(User.Identity.Name); 

     var viewModel = Mapper.Map<User, UserEditViewModel>(user); 
     viewModel.FirstRun = FirstRun == "1" ? true : false; 

     return View("Edit", viewModel); 
    } 

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")] 
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl) 
    { 
     //Map the ViewModel to the Model 
     var user = Repository.FindById(User.Identity.Name); 

     //Map changes to the user 
     Mapper.Map<UserEditViewModel, User>(viewModel, user); 

     //Save the DB changes 
     Repository.SaveChanges(); 

     if (!string.IsNullOrEmpty(ReturnUrl)) 
      return Redirect(ReturnUrl); 
     else 
      return RedirectToAction("Index", "User"); 
    } 

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

Я нашел большой вопрос относительно использования общего интерфейса репозитория IRepository<T>here и также представлял собой идею статического RepositoryFactory в ряде блогов. В основном только один экземпляр хранилища хранится когда-либо, и он получен через этот завод

Так что мой вопрос вращается вокруг того, как люди делают это в приложениях и что считается хорошей практикой.

У людей есть отдельные репозитории на основе каждого стола (IUserRepository)?
Используют ли они общий IRepository<T>?
Используют ли они статическую фабрику хранилища?
Или что-то еще полностью?

EDIT: Я просто понял, что я, вероятно, следует спросить, а также:

Оказывает частный IRepository на каждом контроллере хороший способ пойти? или я должен создавать новый IRepository каждый раз, когда я хочу его использовать?

BOUNTY EDIT: Я начинаю щедрость, чтобы получить еще несколько перспектив (не то, что Тим не помог).

Мне больше любопытно узнать, что делают люди в своих приложениях MVC или что они думают, это хорошая идея.

+1

Общие статические хранилища! УРА! SingletonSquared, просто то, что нам нужно! ;-) –

+0

@ Sky, никогда не может быть достаточно. Я лично думаю о статически частичных общих хранилищах. –

ответ

24

Некоторые очень очевидные проблемы с идеей родового IRepository<T>:

  • Он предполагает, что каждый объект использует один и тот же тип ключа, который не является истинным практически любой нетривиальной системы. Некоторые объекты будут использовать GUID, другие могут иметь какой-то естественный и/или составной ключ. NHibernate может поддерживать это довольно хорошо, но Linq to SQL довольно плохо в нем - вам нужно написать много хакерского кода для автоматического сопоставления ключей.

  • Это означает, что каждый репозиторий может обрабатывать только один тип сущности и поддерживает только самые тривиальные операции. Когда репозиторий отнесен к такой простой CRUD-обертке, он очень мало используется. Вы могли бы просто передать клиенту IQueryable<T> или Table<T>.

  • Предполагается, что вы выполняете точно такие же операции над каждой сущностью. На самом деле это будет очень далеко от истины. Конечно, возможно вы хотите получить это Order по его ID, но, скорее всего, вы хотите получить список Order объектов для конкретного клиента и в пределах определенного диапазона дат. Понятие полностью общего IRepository<T> не позволяет факту, что вы почти наверняка захотите выполнить разные типы запросов для разных типов объектов.

Весь смысл хранилище шаблона для создания абстракции над шаблонами доступа к общим данным. Я думаю, что некоторым программистам надоедает создание репозиториев, поэтому они говорят: «Эй, я знаю, я создам один über-репозиторий, который может обрабатывать любой тип сущности!» Это замечательно, за исключением того, что репозиторий практически бесполезен для 80% того, что вы пытаетесь сделать. Это нормально как базовый класс/интерфейс, но если это полная работа, которую вы делаете, вы просто ленитесь (и гарантируете будущие головные боли).


В идеале я мог бы начать с общим хранилищем, который выглядит примерно так:

public interface IRepository<TKey, TEntity> 
{ 
    TEntity Get(TKey id); 
    void Save(TEntity entity); 
} 

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

public interface IOrderRepository : IRepository<int, Order> 
{ 
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID); 
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate); 
    IPager<Order> GetOrdersByProduct(int productID); 
} 

И так далее - вы получаете идею. Таким образом, у нас есть «общий» репозиторий, если нам когда-либо понадобится невероятно упрощенная семантика retrieve-by-id, но в целом мы никогда не собираемся передавать это, конечно, не к классу контроллера.


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

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

Примером этого принципа является:

public class OrderController : Controller 
{ 
    public OrderController(IOrderRepository orderRepository) 
    { 
     if (orderRepository == null) 
      throw new ArgumentNullException("orderRepository"); 
     this.OrderRepository = orderRepository; 
    } 

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... } 
    // More actions 

    public IOrderRepository OrderRepository { get; set; } 
} 

Другими словами, контроллер имеет понятия не имею, как создать хранилище, это не должно. Если у вас есть строительство репозиториев, это создает связь, которую вы действительно не хотите. Причина, по которой контроллеры образцов ASP.NET MVC имеют беззадачные конструкторы, которые создают конкретные репозитории, заключается в том, что сайты должны быть способны компилироваться и выполняться без принуждения к созданию всей инфраструктуры Injection Dependency.

Но на производственной площадке, если вы не передаете зависимость репозитория через конструктор или общедоступное свойство, вы теряете время, располагая репозиториями, потому что контроллеры по-прежнему тесно связаны с уровнем базы данных , Вы должны быть в состоянии написать тестовый код, как это:

[TestMethod] 
public void Can_add_order() 
{ 
    OrderController controller = new OrderController(); 
    FakeOrderRepository fakeRepository = new FakeOrderRepository(); 
    controller.OrderRepository = fakeRepository; //<-- Important! 
    controller.SubmitOrder(...); 
    Assert.That(fakeRepository.ContainsOrder(...)); 
} 

Вы не можете это сделать, если ваш OrderController собирается покинуть и создать свой собственный репозиторий. Этот метод тестирования не должен делать никакого доступа к данным, он просто гарантирует, что контроллер вызывает правильный метод репозитория, основанный на действии.


Это еще не DI, разумеется, это просто подделка/издевательство. Когда DI появляется на картинке, когда вы решаете, что Linq to SQL недостаточно для вас, и вы действительно хотите HQL в NHibernate, но вам понадобится 3 месяца, чтобы все портировать, и вы хотите иметь возможность сделайте это одно хранилище за раз. Так, например, с помощью DI Framework как Ninject, все, что вам нужно сделать, это изменить:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>(); 
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>(); 
Bind<IProductRepository>().To<LinqToSqlProductRepository>(); 

To:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>(); 
Bind<IOrderRepository>().To<NHibernateOrderRepository>(); 
Bind<IProductRepository>().To<NHibernateProductRepository>(); 

И там вы, теперь все, что зависит от IOrderRepository использует версия NHibernate, вам нужно было только изменить одну строку кода в отличие от потенциально сотен строк. И мы запускаем версии Linq to SQL и NHibernate бок о бок, портируя функциональность по частям, не повредив ничего посередине.


Таким образом, чтобы суммировать все моменты, которые я сделал:

  1. Не следует полагаться исключительно на общий IRepository<T> интерфейс. Большая часть функциональности, которую вы хотите получить в репозитории, составляет , а не generic. Если вы хотите включить IRepository<T> на верхних уровнях иерархии классов/интерфейсов, это нормально, но контроллеры должны зависеть от конкретных репозиториев, поэтому вам не придется менять код в 5 разных местах, когда вы обнаружите, что в общем хранилище отсутствуют важные методы.

  2. Контроллеры должны принимать репозитории снаружи, а не создавать свои собственные. Это важный шаг в устранении сцепления и улучшении тестируемости.

  3. Обычно вы хотите подключить контроллеры с использованием инфраструктуры Injection Dependency, и многие из них могут быть легко интегрированы с ASP.NET MVC.Если это слишком много для вас, вы должны использовать какой-то статический поставщик услуг, чтобы вы могли централизовать всю логику создания репозитория. (В долгосрочной перспективе вам, вероятно, будет легче просто изучить и использовать инфраструктуру DI).

+0

это фантастический ответ и действительно достойный щедрости. Спасибо за все советы и мысли. –

+1

Я знаю, что это старый вопрос, но это отличный ответ. На это просто ответил так много вопросов, которые я имел об использовании стандартного интерфейса IRepository. Спасибо! –

+0

Отличный ответ, независимо от того, сколько ему лет. Это отличный совет. – slimflem

3

У людей есть отдельные репозитории на основе каждой таблицы (IUserRepository)? Я имею тенденцию иметь репозиторий для каждого агрегата, а не для каждой таблицы.

Используют ли они общий IRepository? , если возможно, да

Используют ли они статическую фабрику хранилища? Я предпочитаю инъекцию экземпляра репозитория через контейнер МОК

+3

Мне интересно, что вы подразумеваете под агрегатом. –

+2

Я думаю, что ссылка на агрегат относится к доменной модели - я прочитал о «агрегатах» в контексте Domain Driven Design (DDD), но, я думаю, это должно применяться ко всем доменам. Эрик Эванс определяет совокупность как «группу объектов, которые принадлежат друг другу (группа отдельных объектов, представляющих единицу)» – Ahmad

+0

действительно то, что я намерен сказать –

1

Вот как я его использую.Я использую IRepository для всех операций, которые являются общими для всех моих репозиториев.

public interface IRepository<T> where T : PersistentObject 
{ 
    T GetById(object id); 
    T[] GetAll(); 
    void Save(T entity); 
} 

и я использую также посвятил ITRepository для каждого aggregate для операции, которые различны для этого хранилища. Например, для пользователя я буду использовать IUserRepository добавить метод, различны для UserRepository:

public interface IUserRepository : IRepository<User> 
{ 
    User GetByUserName(string username); 
} 

Реализация будет выглядеть следующим образом:

public class UserRepository : RepositoryBase<User>, IUserRepository 
{ 
    public User GetByUserName(string username) 
    { 
     ISession session = GetSession(); 
     IQuery query = session.CreateQuery("from User u where u.Username = :username"); 
     query.SetString("username", username); 

     var matchingUser = query.UniqueResult<User>(); 

     return matchingUser; 
    } 
} 


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject 
{ 
    public virtual T GetById(object id) 
    { 
     ISession session = GetSession(); 
     return session.Get<T>(id); 
    } 

    public virtual T[] GetAll() 
    { 
     ISession session = GetSession(); 
     ICriteria criteria = session.CreateCriteria(typeof (T)); 
     return criteria.List<T>().ToArray(); 
    } 

    protected ISession GetSession() 
    { 
     return new SessionBuilder().GetSession(); 
    } 

    public virtual void Save(T entity) 
    { 
     GetSession().SaveOrUpdate(entity); 
    } 
} 

чем в UserController будет выглядеть так:

public class UserController : ConventionController 
{ 
    private readonly IUserRepository _repository; 
    private readonly ISecurityContext _securityContext; 
    private readonly IUserSession _userSession; 

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession) 
    { 
     _repository = repository; 
     _securityContext = securityContext; 
     _userSession = userSession; 
    } 
} 

Чем создается репозиторий с использованием шаблона инъекции зависимостей с использованием фабрики пользовательских контроллеров. Я использую StructureMap в качестве слоя инъекции зависимостей.

Уровень базы данных - NHibernate. ISession является шлюзом к базе данных в этом сеансе.

Я предлагаю вам посмотреть на структуру CodeCampServer, вы можете многому научиться у нее.

Другой проект, который вы можете узнать из него, - Who Can Help Me. Который я еще недостаточно копаю в нем.

+0

Это что? –

+0

Я забыл упомянуть, что он использует NHibernate в качестве слоя сохранения. Интерфейс ISession дает вам то, что вам нужно для связи с сервером для этого сеанса. –

+0

Ahhh, теперь это имеет смысл :) –

0

У людей есть отдельные репозитории на основе каждой таблицы (IUserRepository)?

Да, это был лучший выбор по 2 причинам:

  • мой DAL основан на Linq-на-Sql (но мои DTOS интерфейсы, основанные на лиц LTS)
  • выполненные операции атомный (добавление является атомарной операцией, экономия является еще один и т.д.)

Используют ли они общий IRepository?

Да, исключительно, вдохновило на схеме/Value DDD Entity Я создал IRepositoryEntity/IRepositoryValue и общий IRepository для других вещей.

Используют ли они статический хранилище?

Да и нет: я использую контейнер МОК вызывается с помощью статического класса. Ну ... мы можем сказать, что это своего рода завод.

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

0

Вы можете найти отличную Generic Repopsitory библиотеки, которая была написана для того, чтобы его использовать в качестве осины WebForms: ObjectDataSource на CodePlex: MultiTierLinqToSql

Каждые из моих контроллеров имеют частные репозиториев для действий они должны поддерживать.

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