Linq queries with parameters for your ORM IRepository
Ricardo Cavalcanti has raised question on fluent passing of parameters into the queries encapsulated by the QueryFor (specification) pattern.
Let’s talk about the easiest option of passing parameters, first. It requires no new code at all and is just about chaining queries:
var list = customers .Find<ImportantCustomers>() .Where(c => c.City == "Ufa");
Where comes from the in line extension method provided by Linq.
However, in certain situations Linq might be not enough. This involves queries that encapsulate come complex business logic or require parameter pre-processing, that you want to hide away.
Let’s consider the following query:
public sealed class ValuableCustomers : QueryFor<Customer>
{
public ImportanceLevel Importance = ImportanceLevel.Important;
public override IQueryable<Customer> GetSatisfyingElements(
IQueryable<Customer> source)
{
switch (Importance)
{
case ImportanceLevel.Important:
return source.Where(c =>
c.Plan == CustomerPlan.Normal
|| c.TotalPayments > 10000);
case ImportanceLevel.Silver:
return source.Where(c =>
c.Plan == CustomerPlan.Partner
|| c.TotalPayments > 15000);
case ImportanceLevel.Gold:
return source.Where(c =>
c.Plan == CustomerPlan.Enterprise
|| c.TotalPayments > 20000);
default:
throw new ArgumentOutOfRangeException();
}
}
}
Note, that we are using in line Linq queries here, to reduce code noise and avoid writing all these “from … in … select” (this is feasible for the simple queries).
By default, this query could be used just like any other QueryFor instance:
var valuable = customers.Find<ValuableCustomers>();
and it will return all customers as if we were using ValuableCustomers query with the property Importance matching ImportanceLevel.Normal (default value).
Now, if we want to specify query parameters in one line, then we would need to slightly alter our IRepository (and the implementations):
public interface IRepository<T>
{
IQueryable<T> Find<K>() where K : QueryFor<T>, new();
IQueryable<T> Find<K>(Action<K> init) where K : QueryFor<T>, new();
IQueryable<T> Find(string criteria);
IQueryable<T> Query();
//...
}
New method is Find that accepts generic Action argument. Its implementation could be:
public IQueryable<T> Find<K>(Action<K> init)
where K : QueryFor<T>, new()
{
var query = new K();
init(query);
return Find(query);
}
And then we could structure our query usage code:
var valuable = customers .Find<ValuableCustomers>(c => c.Importance = ImportanceLevel.Gold);
Obviously, you can set up multiple arguments at once with this approach.
One reason, why I like this approach, is that it becomes a little bit more testable. While mocking the IRepository interface in your unit tests, you no longer need to bother with the IQueriable and actual query logics. You just make sure that the IRepository gets the specific type of the query with the specific parameters, and then in the response you can return some newly created list of customers.
Nota bene: here’s the reason why the code below (although more concise) is not that good:
var filtered = customers .Find<ValuableCustomers>(CustomerPlan.Gold, 20000);
In order to implement it, you would need to drop in method like this into the IRepository and implementations:
[Obsolete]
public IQueryable<T> Find<K>(params object[] args)
where K : QueryForWithParams<T>, new()
{
var k = new K();
k.Init(args);
return Find(k);
}
and then add class inheriting from QueryFor that allows to init the query in generic way with some parameters:
[Obsolete]
public abstract class QueryForWithParams<T> : QueryFor<T>
{
public abstract void Init(params object[] args);
}
with the sample implementation:
public sealed class ValuableCustomers : QueryForWithParams<Customer>
{
private CustomerPlan _customerPlan = CustomerPlan.Normal;
private int _totalPayments = 10000;
public override IQueryable<Customer> GetSatisfyingElements(IQueryable<Customer> source)
{
return source.Where(c =>
c.Plan == _customerPlan || c.TotalPayments > _totalPayments);
}
public override void Init(params object[] args)
{
Throw.If(args.Length != 2);
_customerPlan = (CustomerPlan) args[0];
_totalPayments = (int) args[0];
}
}
.NET compiler would not be able to help us if we mess up with the arguments order or pass in the argument of the wrong type. The problem would sit silently there, till the execution hits that kind of booby trap.
Obviously, it is possible to cover scenarios like this in the unit tests. But adding extra C# lines of code where it could be easily avoided – is simply inefficient.
Simple rule of thumb that could be used:
Casting variable to another type could mean that something is wrong with the code.
PS: if the discussion on this topic spikes interest and keeps on going, it will go into the “ORM IRepository” sub-section of the ORM + IoC series
2 Comments to Linq queries with parameters for your ORM IRepository
object[] is not oan option, I agree!
What about customers.Find(new ValuableCustomersWith(ImportanceLevel.Gold))?
Building the QueryFor Class with a Parameterized Constuctor. It keeps the type checking, and parameter would be required. Multiple contructor can make default values. It may loose fluency, though because of the ‘new’ word.
Yes, constructor is an obvious option (and there you could also use C# short-cuts for initializing properties along with the constructor).
The difference between these two options (constructor and Action initializer) is marginal. But I’d stick to the Action, since (apart from looking cool) it has the same syntax flow as Linq and thus it’d be easier to comprehend mixed code (given a number of approaches it is always advised to consistently stick to one, to reduce diversity in the code and, thus, make its maintainability more efficient)
Leave a comment
Search
Archives
Recent Comments
- aCoder on Extension methods for interfaces
- Requirements for the Photon .NET project | Rinat Abdullin on IRepository, cross-cutting concerns and flexibility
- Nicholas Blumhardt on Extension methods for interfaces
- aCoder on IRepository, cross-cutting concerns and flexibility
- Rinat Abdullin on Blog upgraded
- Rinat Abdullin on Extension methods for interfaces
- IRepository, cross-cutting concerns and flexibility | Rinat Abdullin on Extension methods for interfaces
- Bill Pierce on Blog upgraded
- aCoder on Extension methods for interfaces

July 19, 2008