Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dealing with Queries in a Repository Pattern with multiple concrete implementations?

This is more of an academic curiosity but I'm trying to figure out how best to accomplish the following.

Imagine a situation where you have an Person object

public class Person {
    public string Name {get;set;}
    public int Age {get;set;}
}

and a Repository Contract for Retrieving them from some persistence store...

public class IPersonRepository {
    public IEnumerable<Person> Search(*** SOME_METHOD_SIGNATURE ***);
}

Your consumer application really doesn't care about the specific implementation. It's just gonna grab the correct concrete Implementation from Unity/Ninject & start querying.

IPersonRespository repo = GetConcreteImplementationFromConfig();
repo.Search( ... );

What I'm wondering is what would you use for your Method Signature here that's both flexible & extensible regardless of the implementation.

Option 1.

public IEnumerable<Person> Search(Expression<Func<Person, bool>> expression);

This is nice because if you're using a LINQ Capable (e.g. EntityFramework) data context, you can just pass the expression directly to your context. This option seems to fall down though if you're implementation has to use hand crafted stored procs/sql/ADO.NET etc...

Option 2.

public IEnumerable<Person> Search(PersonSearch parameters);

public class PersonSearch {
    public int? Age {get;set;}
    public string FullName {get;set;}
    public string PartialName { get; set; }
}

This one seems the most flexible (in the sense that it would work with either Linq, Plain Old SQL.

But it just stinks of "Writing your own query language" because you need to account for every possible query the consumer might want to do. e.g. Age >= 18 && Age <=65 && Name LIKE '%John%'

Option 3.

Are there any other options ?

like image 530
Eoin Campbell Avatar asked Oct 26 '11 15:10

Eoin Campbell


3 Answers

  1. Your statement that "option 1 seems to fall down though if you're implementation has to use hand crafted stored procs/sql/ADO.NET etc" is incorrect. Is is completely possible to generate a model just like the one in option 2, by means of interpreting the query expression with an ExpressionVisitor implementation.

  2. If you are going to return an IEnumerable<T> instead of an IQueryable<T> from the LINQ Search method, you should incorporate some mechanism to support (1) paging, and (2) sorting.

  3. I really dislike option #2. It does not make explicit what you are searching for. e.g. If the age is null, does that mean you are searching for users with null ages, or you are ignoring the age parameter? What if Name and PartialName are both specified? With the LINQ approach you can do stuff like StartsWith, Contains, Equals, etc.

  4. A repository pattern implementation is supposed to abstract away the data access logic, and expose a business-oriented interface. By directly accessing the repository with a generic interface (Expression<Func<Person,bool>>), you are losing out on this a little bit, because the interface does not convey the intention to the caller. There are several ways to do this better.

    • Implementing a LINQ expression-based Specification Pattern creates more strongly typed queries. So, instead of querying for adults by Search(person => person.Age.HasValue && person.Age.Value > 18), you would use a specification syntax like Search(new PersonIsAdultSpecification());, where the specification wraps the underlying LINQ expression, but exposes a business-oriented interface.
      • Personally, I like this approach, but Ayende calls it 'architecting in the pit of doom', because it can easily lead to over-engineering. His alternative suggestion is to wrap specific queries like this in extension methods. I think this is probably equally viable, but I prefer having the strongly-typed object.
      • The easiest way to do this would be to actually declare the realistic queries you will perform on the IPersonRepository interface. So, the interface would actually declare a SearchForAdults() method.

In general, Whenever you are querying the database, you should be deliberately trying to get certain data. Whenever you query the repository, you should be deliberately trying to get business objects that satisfy a certain business constraint. Why not make this business constraint more explicit? Defining a good interface for any service depends on the consumer of that service. There isn't a generic magic bullet repository interface, but you can create a better or worse one in context of your specific application. The idea is to remember why you are using the repository pattern in the first place, and that is to simplify the logic and create a more intuitive access point.

like image 66
smartcaveman Avatar answered Nov 09 '22 21:11

smartcaveman


I'm not sure there is such a thing as an "offical source" in this case, but I did lead the design of LINQ for Visual Basic so maybe that counts for something. Anyway here's my take on it:

The problem you seem to be posing is that you want to be able to transmit a predicate expression to a backend "regardless of the [backend] implementation." However, it seems to me that the two options you list are really just variants of each other, in that in both cases the backend has to be able to understand the predicate specification that's being passed in. Option one happens to be more general and flexible than option two (but it is also much more of a pain to translate). Option two is more restricted than option two but is a simpler structure to deal with (although as you allow more and more complex queries to be specified in the second option, the structure will inevitably come more and more to resemble the structure from the first option).

The bottom line is that there is no good answer to your question. There is no universal expression structure that you can be sure all providers will support, so if you really want to have a Search method that works over any provider regardless of implementation, you'd have to be willing to fall back to running the predicate locally if the provider doesn't support, say, expression trees. Depending on the scenario, that may or may not work for you.

On the other hand, if (as is more likely) the list of possible providers you'll receieve is finite, you'd probably be better off deciding what is most important to you (ease of use/expressibility vs. ease of implementation) and pick what constraints you want to put on the providers. Expression trees would certainly be the nicest in many ways for the consumer application programmer, so if you think you can get away with that restriction go for it. Otherwise, some sort of simpler custom structure will probably cause you less pain and heartache. (I don't recommend trying to translate expression trees yourself unless you've got a lot of extra time and patience.)

like image 1
panopticoncentral Avatar answered Nov 09 '22 23:11

panopticoncentral


One thing that always bothered me about the Repository pattern is it's inflexibility when it comes down to things like grouping, aggregating counts and so on. That's why I decided to also provide a seperate Interface (e.g. IExtendedRepository) that comes with a funcy method signature like this:

TResult Aggregate<TResult>(Func<IQueryable<TEntity>, TResult> aggregatorFunc);

This method is a bit of a monster tool which you can use in many neat ways:

var countSomething = repository.Aggregate(x => x.Where(y => y.SomeProperty).Count());

Notice that (depending on your repository implementation) the Count() call will compile down into an SQL Count, so it's NOT an in-memory Count().

like image 1
Christoph Avatar answered Nov 09 '22 21:11

Christoph