Implementing ORM-independent Linq queries

Sunday, July 6th, 2008 | Snippets

We’ll get back from the Boo + Business + DSL to the XPO + ORM + IOC series for a little bit.

Nicholas Blumhardt has just written an interesting and extremely thorough article on Implementing the Specification Pattern via Linq. Let’s try to play with the idea and take it a one step down the road. Here we can:

1. Add ORM abstraction by implementing generic IRepository (of T) interface:

public interface IRepository<T>
{
	IQueryable<T> Find(QueryFor<T> query);
	IQueryable<T> Find(string criteria);
	IQueryable<T> Find<K>() where K : QueryFor<T>, new();
	IQueryable<T> Query();

	// ...
}

NHibernate-specific implementation sample is listed in Nick’s blog (you can also check out Rhino Commons, Repository[of T] and Unit Of Work @ Ayende). Here’s the piece from XPO-specific implementation:

public sealed class XpoRepository<T> : IRepository<T>
	where T : XPBaseObject
{
	private readonly Session _session;

	public XpoRepository(Session session)
	{
		_session = session;
	}

	public IQueryable<T> Find<K>() where K : QueryFor<T>, new()
	{
		return Find(new K());
	}

	public IQueryable<T> Find(QueryFor<T> queryFor)
	{
		var query = new XPQuery<T>(_session);
		return queryFor.GetSatisfyingElements(query);
	}

	public IQueryable<T> Query()
	{
		return new XPQuery<T>(_session);
	}

	// ...

Registration of this XPO ORM repository in the Autofac container could be as simple as this:

builder
	.RegisterGeneric(typeof (XpoRepository<>))
	.As(typeof (IRepository<>)).ContainerScoped();

So then we can implement our query classes.

Note: I’m using “QueryFor” instead of “Specification” in my listing.

public abstract class QueryFor<T>
{
	public abstract IQueryable<T> GetSatisfyingElements(IQueryable<T> source);
}
public sealed class ImportantCustomers : QueryFor<Customer>
{
	public override IQueryable<Customer> GetSatisfyingElements(IQueryable<Customer> source)
	{
		return
			from c in source
			where c.Plan == CustomerPlan.Enterprise || c.TotalPayments > 10000
			select c;
	}
}

With that we are able to query our repository like this:

var customers = container.Resolve<IRepository<Customer>>();

var list = customers.Find<ImportantCustomers>();

foreach (var c in list)
{
	Console.WriteLine(c.Name);
}

Note, how generic syntax plays out nicely in “Find - Important Customers”

2. Instead of spreading query logics between multiple classes, we can collect them in one extension class:

public static class CustomerQueries
{
	public static IQueryable<Customer> FindImportant(this IQueryable<Customer> repo)
	{
		return
			from c in repo
			where c.Plan == CustomerPlan.Enterprise || c.TotalPayments > 10000
			select c;
	}
	public static IQueryable<Customer> FindPartners(this IQueryable<Customer> customers)
	{
		return
			from c in customers
			where c.Plan == CustomerPlan.Partner
			select c;
	}
}

This extension API is still independent from the specific ORM (XPO in our situation) and could be used like:

var list2 = customers.Query().
	FindPartners().
	FindImportant();

foreach (var c in list2)
{
	Console.WriteLine(c.Name);
}

Note, how we have chained multiple queries together.

Here comes the question now - how would you test these QueryFor(Specification) or extension query classes in your code?

PS: in the next post on ORM+IOC series we’ll talk a bit more about these options (and some other) along with their pros and cons.

Tags: , , ,

9 Comments to Implementing ORM-independent Linq queries

Nicholas Blumhardt
July 7, 2008

Too cool Rinat! I love:

repository.Find();

Reads very nicely.

I like the Specification/QueryFor version better than the extension method approach because it is natural to use this version in the IsSatisfiedBy() case, but it doesn’t take much of a leap to get to creating IsImportant() as an extension on Customer either.

[...] Blumhardt wrote about implementing the Specification Pattern using Linq.  And Rinat Abdullin did something similar using XPO.  These are both very neat approaches.  But there is something I was wondering [...]

Rinat Abdullin
July 7, 2008

Nick,

agreed. Specification approach is obviously more scalable and flexible. Yet, it adds to the complexity of the solution.

So basing on the solution specifics one can pick from putting methods directly into the CustomerRepository (like Ayende likes to do), abstract them into extensions or use Specification micro-framework.

It should not be that hard to switch from one to another, unless Find(string criteria) is used…

[...] About « Implementing ORM-independent Linq queries [...]

Ricardo Cavalcanti
July 16, 2008

Using the customers.Find<>() approach, would it be possible to pass some parameter to the query?
like customers.Find<CustomersWhoPayMoreThan>(1000)

I only see a way to pass parameter through
customers.Find(new CustomersWhoPayMoreThan(1000)).

really nice pattern. Relly fluid and easy to test.

Rinat Abdullin
July 17, 2008

Ricardo,

it is possible to do customers.Find[CustomersWhoPayMoreThan](1000), but this approach is not recommended, since you are loosing control of the parameters here.

Better (more logically correct and stable) option is to use either chained queries or inline activation of the parametrized queries. I’ll expand on that in the next post.

[...] Cavalcanti has raised question on fluent passing of parameters into the queries specified by the QueryFor [...]

[...] across this generic interface in other readings as well.  Specifically Rinat Abdullin has a few posts about [...]

[...] [http://www.iridescence.no/post/Linq-the-Specification-Pattern-and-Encapsulation.aspx] Implementing ORM-independent Linq queries [...]

Leave a comment

RSS

Search

Archives