Мы все ищем один и тот же IRepository?

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

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 ответа

  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

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

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

При использовании GetAll, возвращающего 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; } }
   ...
}

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

Это немного поздно... но взглянем на реализацию 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.
Другие вопросы по тегам