Are we all looking for the same IRepository?
Asked Answered
S

3

11

I've been trying to come up with a way to write generic repositories that work against various data stores:

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 {...}

I'd like to work against the same POCO domain classes in each. I'm also considering a similar approach, where each domain class has it's own repository:

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

My questions: 1)Is the first approach even feasible? 2)Is there any advantage to the second approach.

Slur answered 7/11, 2008 at 0:2 Comment(0)
W
5
  1. The first approach is feasible, I have done something similar in the past when I wrote my own mapping framework that targeted RDBMS and XmlWriter/XmlReader. You can use this sort of approach to ease unit testing, though I think now we have superior OSS tools for doing just that.

  2. The second approach is what I currently use now with IBATIS.NET mappers. Every mapper has an interface and every mapper [could] provide your basic CRUD operations. The advantage is each mapper for a domain class also has specific functions (such as SelectByLastName or DeleteFromParent) that are expressed by an interface and defined in the concrete mapper. Because of this there's no need for me to implement separate repositories as you're suggesting - our concrete mappers target the database. To perform unit tests I use StructureMap and Moq to create in-memory repositories that operate as your Memory*Repository does. Its less classes to implement and manage and less work overall for a very testable approach. For data shared across unit tests I use a builder pattern for each domain class which has WithXXX methods and AsSomeProfile methods (the AsSomeProfile just returns a builder instance with preconfigured test data).

Here's an example of what I usually end up with in my unit tests:

// 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
Widespread answered 7/11, 2008 at 4:4 Comment(0)
P
4

Actually there is a general consensus now that Domain repositories should not be generic. Your repository should express what you can do when persisting or retrieving your entities.

Some repositories are readonly, some are insert only (no update, no delete), some have only specific lookups...

Using a GetAll return IQueryable, your query logic will leak into your code, possibly to the application layer.

But it's still interesting to use the kind of interface you provide to encapsulate Linq Table<T> objects so that you can replace it with an in memory implementation for test purpose.

So I suggest, to call it ITable<T>, give it the same interface that the linq Table<T> object, and use it inside your specific domain repositories (not instead of).

You can then use you specific repositories in memory by using a in memory ITable<T> implementation.

The simplest way to implement ITable<T> in memory is to use a List<T> and get a IQueryable<T> interface using the .AsQueryable() extension method.

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; } }
   ...
}

You can work in isolation of the database for testing, but with true specific repositories that give more domain insight.

Padraic answered 6/2, 2009 at 23:27 Comment(0)
C
2

This is a bit late... but take a look at the IRepository implementation at CommonLibrary.NET on codeplex. It's got a pretty good feature set.

Regarding your problem, I see a lot of people using methods like GetAllProducts(), GetAllEmployees() in their repository implementation. This is redundant and doesn't allow your repository to be generic. All you need is GetAll() or All(). The solution provided above does solve the naming problem though.

This is taken from CommonLibrary.NET documentation online:

0.9.4 Beta 2 has a powerful Repository implementation.

* 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.
Carcinogen answered 10/3, 2010 at 17:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.