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

Это один из способов, используя defaultdict :

# the setup
>>> from collections import defaultdict
>>> dict1 = {'A': 25, 'B': 41, 'C': 32}
>>> dict2 = {'A': 21, 'B': 12, 'C': 62}

# the preperation
>>> dicts = [dict1, dict2]
>>> final = defaultdict(list)

# the logic
>>> for k, v in ((k, v) for d in dicts for k, v in d.iteritems()):
    final[k].append(v)

# the result
>>> final 
defaultdict(, {'A': [25, 21], 'C': [32, 62], 'B': [41, 12]})

14
задан Community 23 May 2017 в 12:24
поделиться

5 ответов

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

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

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

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

Весь смысл шаблона репозитория заключается в создании абстракции поверх общих шаблонов доступа к данным.Я думаю, некоторым программистам надоедает создание репозиториев, поэтому они говорят: «Эй, я знаю, я создам один сверхрепозиторий, который может обрабатывать объекты любого типа!» И это здорово, за исключением того, что репозиторий практически бесполезен для 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 появляется тогда, когда вы решаете, что 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 . Большая часть функциональных возможностей, которые вы хотите получить от репозитория, специфична , а не универсальных . Если вы хотите включить IRepository на верхние уровни иерархии классов / интерфейсов, это нормально, но контроллеры должны зависеть от конкретных репозиториев, чтобы вы не закончили необходимость изменить свой код в 5 разных местах, когда вы обнаружите, что в общем репозитории отсутствуют важные методы.

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

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

24
ответ дан 1 December 2019 в 09:32
поделиться

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

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

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

3
ответ дан 1 December 2019 в 09:32
поделиться

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

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

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

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

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

Они используют фабрику статического репозитория?

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

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

0
ответ дан 1 December 2019 в 09:32
поделиться

Вот как я его использую. Я использую 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;
    }
}

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

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

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

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

1
ответ дан 1 December 2019 в 09:32
поделиться

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

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

0
ответ дан 1 December 2019 в 09:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: