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 ?
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.
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.
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.
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.
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.
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.
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.)
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().
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With