I don’t know how many of you need to analyze LINQ queries – but since this took me a few hours to get right I thought I’d save you some of the trouble in case you do.
I am working on this pet project of mine (I’ll blog about that soon in a separate post) where I have the need to take LINQ queries and among other things verify that that the sources are from a specific types. The usual way to go about it is to write a LINQ provider (see for example Bart De Smet’sseries of posts on building a Linq provider for LDAP) – but that’s not my case since I want to use the regular LINQ for object (I don’t want to build my own LINQ) and I don’t want to directly run the queries, rather I want to accept them as parameters. An example would probably make it clearer.
var engine = new ReceptorEngine(new[] { "receptorTests" });
var loginRecords = engine.GetEventSource<Login>();
engine.AddQuery(() => from names in loginRecords.Stream
group names by names.Name
into logins
from login in logins
let next = logins.FirstOrDefault(t => t.LoginTime > login.LoginTime)
let nextNext =null == next ? null : logins.FirstOrDefault(t => t.LoginTime > next.LoginTime)
where !login.Successful && (null != next && !next.Successful) && (null != nextNext && !nextNext.Successful)
select login,
BasicVerifyName);
So to reiterate what I wanted to do: the AddQuery method accepts a LINQ query and an action. I wanted to make sure the “from” clauses in query are all of a certain type i.e. that loginRecords is of type EventSource (actually that it implements ImEventSource<> which is a generic interface). You’d notice that there is another “from” clause logins but the it is from results of the original so it is legal (but we want to make sure the query won’t be considered invalid because of it)
Getting from a LINQ query to a LINQ Expression
In order to do any analysis of the LINQ query you need to make it into a LINQ Expression. The first little trick to make this happen was to change the signature of the AddQuery method from it’s original definition:
public void AddQuery<TOutput>(Func<IEnumerable<TOutput>> query, Action<TOutput> triggeredAction)
To:
public void AddQuery<TOutput>(Expression<Func<IEnumerable<TOutput>>> query, Action<TOutput> triggeredAction)
The query itself hides inside the Body property of the Expression we get (i.e. query.Body)
Traversing the Expression tree
The next challenge is to traverse the expression tree we have. It is quite annoying actually since there are a lot of types of expressions (Lambda, Member, Invocation etc.) some of them are binary and some unary and whatnot. Fortunately, Microsoft’s MSDN site provides sample code for a LINQ expression visitor (though it took me sometime to locate it). So now I can feed my expression to the visitor and it walks through it.
The last bits are the actual validation checks. In my case I am interested in the MemberExpressions (which contain, among other things the definitions for the from clauses). Since we are walking all of the expression tree we can’t make a singe check so we need to mark for interesting things we find while traversing the tree. For instance one check I have is that there’s at least one from clause which implement the ImEventSource<> interface (another would be to make sure no non-allowed types are there etc.)
private void CheckForEventSource(Type memberType)
{
if (memberType.GetInterfaces().Any(interfaceInfo => interfaceInfo.IsGenericType && interfaceInfo.GetGenericTypeDefinition() == typeof(ImEventSource<>)))
numberOfEventSources++;
}
The last interesting bit here is the check of the generic interface. When you want to check for a non-generic interface you can simply use typeof(InterfaceName).IsAssignableFrom(typeof(TypeYouWantToCheck) – unfortunately that only works for concerete interfaces (i.e. non-generic or where the generic parameter is specified). The code above solves this problem by using the Reflections InterfaceInfo class
to sum up, working with LINQ expressions involves a lot of nitty gritty details some of them are quite annoying and most of them don’t have a lot of documentation on the internet. I hope that this post will help some other troubled soul like myself solve her troubles quicker :)
Illustration by svilen001
[…] This post was mentioned on Twitter by Arnon Rotem-Gal-Oz, LINQ Feeds. LINQ Feeds said: LINQ Expressions fun | Cirrus Minor http://bit.ly/9OKisO […]
Nice I think we can use someone like you in our company !