Мы все ищем один и тот же IRepository? - PullRequest
11 голосов
/ 07 ноября 2008

Я пытался придумать способ написать универсальные репозитории, которые работают с различными хранилищами данных:

public interface IRepository
{
    IQueryable<T> GetAll<T>();
    void Save<T>(T item);
    void Delete<T>(T item);
}
public class MemoryRepository : IRepository {...}
public class SqlRepository : IRepository {...}

Я бы хотел работать с одинаковыми классами домена POCO в каждом. Я также рассматриваю аналогичный подход, где у каждого класса домена есть свой собственный репозиторий:

public interface IRepository<T>
{
    IQueryable<T> GetAll();
    void Save(T item);
    void Delete(T item);
}
public class MemoryCustomerRepository : IRepository {...}
public class SqlCustomerRepository : IRepository {...}

Мои вопросы: 1) Возможен ли первый подход? 2) Есть ли преимущество у второго подхода.

Ответы [ 3 ]

5 голосов
/ 07 ноября 2008
  1. Первый подход осуществим, я делал нечто подобное в прошлом, когда писал свою собственную структуру отображения, предназначенную для RDBMS и XmlWriter / XmlReader. Вы можете использовать этот подход для упрощения модульного тестирования, хотя я думаю, что теперь у нас есть превосходные инструменты OSS для этого.

  2. Второй подход - это то, что я сейчас использую с сопоставителями IBATIS.NET . Каждый маппер имеет интерфейс, и каждый маппер [мог бы] обеспечить ваши основные операции CRUD. Преимущество заключается в том, что каждый преобразователь для класса домена также имеет определенные функции (такие как SelectByLastName или DeleteFromParent), которые выражаются интерфейсом и определяются в конкретном преобразователе. Из-за этого мне не нужно реализовывать отдельные репозитории, как вы предлагаете - наши конкретные средства отображения нацелены на базу данных. Для выполнения модульных тестов я использую StructureMap и Moq для создания репозиториев в памяти, которые работают так же, как ваш Memory*Repository. Его меньше классов для реализации и управления и меньше работы в целом для очень проверяемого подхода. Для данных, совместно используемых модульными тестами, я использую шаблон построителя для каждого класса домена, который имеет методы WithXXX и AsSomeProfile (AsSomeProfile просто возвращает экземпляр построителя с предварительно настроенными данными теста).

Вот пример того, что я обычно получаю в своих юнит-тестах:

// Moq mocking the concrete PersonMapper through the IPersonMapper interface
var personMock = new Mock<IPersonMapper>(MockBehavior.Strict);
personMock.Expect(pm => pm.Select(It.IsAny<int>())).Returns(
    new PersonBuilder().AsMike().Build()
);

// StructureMap's ObjectFactory
ObjectFactory.Inject(personMock.Object);

// now anywhere in my actual code where an IPersonMapper instance is requested from
// ObjectFactory, Moq will satisfy the requirement and return a Person instance
// set with the PersonBuilder's Mike profile unit test data
4 голосов
/ 07 февраля 2009

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

Некоторые репозитории доступны только для чтения, некоторые - только для вставки (без обновления, без удаления), у некоторых - только определенные поиски ...

Используя GetAll return IQueryable, ваша логика запросов попадет в ваш код, возможно, на прикладной уровень.

Но все же интересно использовать предоставляемый вами интерфейс для инкапсуляции объектов Linq Table<T>, чтобы вы могли заменить его реализацией в памяти для целей тестирования.

Поэтому я предлагаю назвать его ITable<T>, дать ему тот же интерфейс, что и у объекта linq Table<T>, и использовать его внутри вашего конкретного репозитория домена (не вместо).

Затем вы можете использовать свои определенные репозитории в памяти, используя реализацию ITable<T> в памяти.

Самый простой способ реализовать ITable<T> в памяти - это использовать List<T> и получить интерфейс IQueryable<T>, используя метод расширения .AsQueryable ().

public class InMemoryTable<T> : ITable<T>
{
    private List<T> list;
    private IQueryable<T> queryable;

   public InMemoryTable<T>(List<T> list)
   { 
      this.list = list;
      this.queryable = list.AsQueryable();
   }

   public void Add(T entity) { list.Add(entity); }
   public void Remove(T entity) { list.Remove(entity); }

   public IEnumerator<T> GetEnumerator() { return list.GetEnumerator(); }

   public Type ElementType { get { return queryable.ElementType; } }
   public IQueryProvider Provider {     get { return queryable.Provider; } }
   ...
}

Вы можете работать изолированно с базой данных для тестирования, но с действительно конкретными репозиториями, которые дают больше информации о домене.

2 голосов
/ 10 марта 2010

Это немного поздно ... но взгляните на реализацию IRepository по адресу CommonLibrary.NET на codeplex. У него довольно хороший набор функций.

Что касается вашей проблемы, я вижу много людей, использующих такие методы, как GetAllProducts (), GetAllEmployees () в их реализации хранилища. Это избыточно и не позволяет вашему хранилищу быть универсальным. Все, что вам нужно, это GetAll () или All (). Решение, предоставленное выше, решает проблему именования.

Это взято из документации CommonLibrary.NET онлайн:

0.9.4 Beta 2 имеет мощную реализацию репозитория.

* Supports all CRUD methods ( Create, Retrieve, Update, Delete )
* Supports aggregate methods Min, Max, Sum, Avg, Count
* Supports Find methods using ICriteria<T>
* Supports Distinct, and GroupBy
* Supports interface IRepository<T> so you can use an In-Memory table for unit-testing
* Supports versioning of your entities
* Supports paging, eg. Get(page, pageSize)
* Supports audit fields ( CreateUser, CreatedDate, UpdateDate etc )
* Supports the use of Mapper<T> so you can map any table record to some entity
* Supports creating entities only if it isn't there already, by checking for field values.
...