Implementing IExpandProvider for NHibernate.LINQ
For the uninitiated, IExpandProvider is another interface that ADO.NET Data Services support to allow the expansion feature of Data Services to work. Essentially, expansion is about eager loading of related entities as part of a Data Service Query. For example, if you had a Data Services query that said:
This URI would go retrieve the Product, but if you wanted to retrieve the Category (a related entity) as part of the single call, you could annotate the URI with a query string parameter called $expand to indicate the paths to eager load:
In fact, the expansion support allows you to specify multiple paths (separated by commas) to eager load:
The way it works inside the Data Service is that checks to see if the context object specified in the Data Service support IExpandProvider (the built-in Entity Framework provider does this automatically). If it supports the interface, it calls the ApplyExpansions method. At that point, the provider is supposed to take the IQueryable interface and change the query to support the expansions. Remember, the methodology of Data Services is to take the URI syntax and convert the query into an IQueryable instance. At the end of the process it executes this query so the IExpandProvider happens before the database is ever involved. All it needs to do is to expand the expression tree to add the expressions necessary to expand the nodes.
So how did I go about it? My first step was to add expansion support to the LINQ interface. Before I got started, eager loading was the responsibility of the user of the LINQ query. There was no way to make eager loading happen in the expression tree. I modeled the support after the way that Entity Framework supports eager loading and added a new expression (called Expand in the NHibernate.LINQ provider) that instructs the underlying provider to eager load a certain path. Interestingly the ICriteria support in NHibernate already had support for eager loading so it was more plumbing to the NHibernate functionality that any real work. The expand works during the data source of the LINQ query:
var query = from timesheet in session.Linq<Timesheet>().Expand("Entries") where timesheet.Entries.Count > 0 select timesheet;
By adding the Expand method call, it adds a hint to the query to eager load the Entries on the Timesheet object. Now that expansion is supported in the LINQ provider, I can wire up the IExpandProvider to simply add that expression for every path.
To support it, I simple walk through each of the paths that are passed to the ApplyExpansions method, but its not quite that simple. The method signature is:
IEnumerable ApplyExpansions(IQueryable queryable, ICollection<ExpandSegmentCollection> expandPaths);
The first property is the query that you are going to modify for the expansions. The second parameter is a list of ExpansionSegmentCollections. Yup, a collection of a collection. I am unclear why this is so, but nevertheless, I simply go through each collection of collections (I’ve never been passed more than one, but perhaps MS can explain why). The ExpandSegmentCollection is a collection of ExpandSegment objects. This object supports two pieces of information: Name and Filter. Name is the path that is requested in the URI syntax; and **Filter **which it is unclear the exact use case. In my implementation, I simply threw an error if I found a filter since I couldn’t find any code path that caused them so I didn’t know how to modify the query to support them. My guess is that its there for future use.
So there you go, its added. This new interface in coordination with the IUpdateable makes any NHibernate object supported in not only LINQ but also ADO.NET Data Services. What do you think?