29

I've just had to switch out an ORM and it was a relatively daunting task, because the query logic was leaking everywhere. If i'd ever had to develop a new application, my personal preference would be to encapsulate all query logic (using an ORM) to futureproof it for change. Repository pattern is quite troublesome to code and maintain so i was wondering if there are any other patterns to solve the problem ?

I can foresee posts about not adding extra complexity before it's actually needed, being agile etc. but i'm only interested about the existing patterns solving a similar problem in a simpler way.

My first thought was to have a generic type repository, to which i add methods as needed to specific type repository classes via extension methods, but unit testing static methods is awfully painful. IE:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}
2
  • 5
    What exactly is it about the Repository pattern that you find troublesome to code and maintain? Jan 1, 2013 at 23:42
  • 1
    There are alternatives out there. One such is using Query Object pattern which although I haven't used seems like a good approach. Repository IMHO is a good pattern if used right but not the be all and end all
    – dreza
    Jan 2, 2013 at 6:14

5 Answers 5

17

First of all: A generic repository should be considered to be a base class and not complete implementations. It should help you to get the common CRUD methods in place. But you still have to implement the query methods:

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

Second:

Do not return IQueryable. It's a leaky abstraction. Try to implement that interface yourself or use it without knowledge about the underlying db provider. For instance, every db provider has it's own API for eagerly load entities. That's why it's a leaky abstraction.

Alternative

As an alternative you can use queries (for instance as defined by the Command/Query separation pattern). CQS is described in wikipedia.

You basically create query classes which you invoke:

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

the great thing with queries is that you can use different data access methods for different queries without cluttering the code. you can for instance use a web service in one, nhibernate in another and ADO.NET in a third.

If you are interested in a .NET implementation read my article: http://blog.gauffin.org/2012/10/griffin-decoupled-the-queries/

6
  • Wouldn't the alternative pattern You suggested create even more clutter on larger projects seeing as what would be a method in the Repository Pattern is now a whole class ?
    – Dante
    Jan 2, 2013 at 15:19
  • 4
    If your definition of having small classes is cluttering, well yes. In that case you should not try to follow the SOLID principles either as applying them will always produce more (smaller) classes.
    – jgauffin
    Jan 2, 2013 at 15:23
  • 2
    Smaller classes are better, but wouldn't the navigation of existing query classes be more troublesome, than to say, browsing the intellisense of a (domain) repository to see, if the neccessary functionality lies in there and if not, implement it.
    – Dante
    Jan 2, 2013 at 15:32
  • 4
    The project structure becomes more important. If you put all queries in a namespace like YourProject.YourDomainModelName.Queries it will be easy to navigate.
    – jgauffin
    Jan 2, 2013 at 16:11
  • One thing I'm finding hard to do in a good way with CQS is common queries. E.g., given a user, one query gets all associated students (diff grades, own children etc.). Now another query needs to get children for an user and then do some operation on them - so query 2 can leverage the common logic in query 1 but there is no simple way of doing that. I haven't been able to find any CQS implementation that facilitates this easily. Repositories help in this regard a lot. Not a fan of either pattern but just sharing my take.
    – Mrchief
    Jan 23, 2016 at 23:07
9

First think I say is that ORM is already big enough abstraction over your database. Some ORMs provide binding to all common relational databases (NHibernate has bindings to MSSQL, Oracle, MySQL, Postgress, etc..) So building new abstraction on top of it doesn't seem profitable to me. Also, argummenting, that you need to "abstract away" this ORM is meaningless.

If you still want to build this abstraction, I would go against Repository pattern. It was great in age of pure SQL, but it is quite troublesome in face of modern ORM. Mainly because you end up re-implementing most of the ORM features, like CRUD operations and querying.

If I was to build such abstractions, I would use these rules/patterns

  • CQRS
  • Use as much as possible of existing ORM features
  • Encapsulate only complex logic and queries
  • Try to have "plumbing" of the ORM hidden inside an architecture

In concrete implementation, I would use CRUD operations of ORM directly, without any wrapping. I would also do simple queries directly in code. But complex queries would be encapsulated in their own objects. To "hide" the ORM, I would try to inject the data context transparently into an service/UI objects and have same done to query objects.

Last thing I would like to say, is that many people use ORM without knowing how to use it and how to make best "profit" out of it. People recommending repositories are usually of this kind.

As a recommended reading, I would say Ayende's blog, especially this article.

2
  • +1 "Last thing I would like to say, is that many people use ORM without knowing how to use it and how to make best "profit" out of it. People recommending repositories are usually of this kind"
    – Misters
    Apr 29, 2015 at 0:44
  • Many people don't use ORMs because those abstractions are bad for writing complex queries. Of course, if the only thing you do is update blog posts, retrieve reviews from database and join 1 or 2 tables at best then an ORM is fine. But if you're working with gigantic database systems which span across several servers then ORMs are the downfall of productivity and maintainability. Jul 25, 2020 at 9:00
1

The problem with the leaky implementation is that you need many different filter conditions.

You can narrow this api if you implement a repository method FindByExample and use it like this

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);
0

Consider making your extensions operate on IQueryable instead of IRepository.

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

To unit test:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

No fancing mocking required for testing. You can also combine these extension methods to create more complex queries as needed.

1
  • 6
    -1 a) IQueryable is a bad mother, or rather a GOD interface. Implementing it is very had and there are very few (if any) complete LINQ to Sql providers. b) Extension methods make extension (one of the fundemental OOP principles) impossible.
    – jgauffin
    Jan 2, 2013 at 8:33
0

I wrote a pretty neat query object pattern for NHibernate here: https://github.com/shaynevanasperen/NHibernate.Sessions.Operations

It works by using an interface like this:

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

Given a POCO entity class like this:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

You can build query objects like this:

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

And then use them like this:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });
3
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. Aug 13, 2015 at 21:43
  • My apologies, I was in a hurry. Answer updated to show some example code.
    – Shayne
    Aug 14, 2015 at 0:14
  • 1
    +1 for improving the post with more content.
    – Songo
    Aug 14, 2015 at 3:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.