Проектирование уровня абстракции базы данных - правильное использование IRepository?

StackOverflow https://stackoverflow.com/questions/2347337

Вопрос

Я нахожусь в процессе разработки своего приложения ASP.NET MVC, и мне в голову пришла пара интересных мыслей.

Многие образцы, которые я видел, описывают и используют шаблон репозитория (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 работает в отношении создания контроллера, когда пользователь создает ссылку (не уверен, есть ли 1 контроллер на пользователя или 1 контроллер на приложение), поэтому я не уверен в наилучшем способе действий.

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

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

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

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

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

БАУНТИ ПРАВИТЬ:Я начинаю розыгрыш премии, чтобы получить еще несколько перспектив (не то чтобы Тим не был полезен).

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

Это было полезно?

Решение

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

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

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

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

Весь смысл шаблона репозитория заключается в создании абстракция по общим шаблонам доступа к данным.Я думаю, некоторым программистам наскучивает создавать репозитории, поэтому они говорят: "Эй, я знаю, я создам один супер-репозиторий, который сможет обрабатывать объекты любого типа!" И это здорово, за исключением того, что репозиторий практически бесполезен для 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);
}

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


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

Контроллеру необходимо возьмите его хранилище из внешнего мира.Причина, по которой вы создали эти репозитории, заключается в том, что вы можете выполнить какую-то инверсию управления.Ваша конечная цель здесь - иметь возможность поменять один репозиторий на другой - например, для модульного тестирования или, если вы решите переключиться с 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 иметь конструкторы без параметров, которая создает бетонных хранилищ заключается в том, что сайты должны быть в состоянии скомпилировать и запустить, не заставляя вас, чтобы настроить весь фреймворк внедрения зависимостей.

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

[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-фреймворк типа Девятый объект, все, что вам нужно сделать, это изменить это:

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

Для:

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

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


Итак, подведем итог всем замечаниям, которые я высказал:

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

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

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

Другие советы

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

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

Используют ли они фабрику статических репозиториев?Я предпочитаю внедрение экземпляра репозитория через контейнер IOC.

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

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

и я также использую отдельный ITRepository для каждого совокупность для операции, которая отличается от этого репозитория.Например, для пользователя я буду использовать 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;
    }
}

Затем создается экземпляр репозитория с использованием шаблона внедрения зависимостей с использованием пользовательской фабрики контроллеров.я использую СтруктураКарта как мой уровень внедрения зависимостей.

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

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

Еще один проект, которому вы можете научиться, — это Кто может мне помочь.В чем я еще недостаточно разбираюсь.

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

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

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

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

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

Используют ли они фабрику статических репозиториев?

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

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

Вы можете найти отличный Общий репопситорий библиотека, написанная для возможности использования ее в качестве asp:ObjectDataSource WebForms в codeplex: Мультитиерлинктоскл

У каждого из моих контроллеров есть частные репозитории для действий, которые им необходимо поддерживать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top