Saturday, May 12, 2007

Adding support for projections to Linq to Google Desktop

I this post I'm going to show how support for projections was added to the Linq To Google Desktop experiment.

In order to support queries like:


var e10 = from t in gd
where t is GDFileResult &&
t.Contains("sql")
select ((GDFileResult)t).Location;

foreach (string s in e10) {
Console.WriteLine("The file name is: " + s);
}




We need to add support for handling the Select method. The query described above is processed by the compiler as:


gd.Where( ... ).Select( ... );


Given that the Where method processing returns an instance of GDQuery, then the processing of the Select method call is done in the GDQuery.CreateQuery method. The implementation of this method is the following:


public IQueryable<T> CreateQuery<T>(System.Linq.Expressions.Expression expression)
{
if (IsSelectMethodCall(expression))
{
MethodCallExpression methodCallExpr = (MethodCallExpression)expression;

UnaryExpression unaryQuoteExpr =
(UnaryExpression)methodCallExpr.Arguments[1];

LambdaExpression lambdaExpr =
(LambdaExpression)unaryQuoteExpr.Operand;

GDQuery query =
(GDQuery)((ConstantExpression)methodCallExpr.Arguments[0]).Value;

return query.AsEnumerable<GDResult>().Select(
(Func<GDResult,T>)lambdaExpr.Compile()).AsQueryable<T>();
}
else
{
throw new NotSupportedException("Not supported method call");
}

}


What this method does is to delegate the execution to the public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector); method , which is the extension method fro IEnumerable. The only thing we also need to do is to compile the lambda expression that represents the filter.

This is done this way because elements in the projection section cannot be used to make the Google Desktop query more specific. This is not the case of Linq To SQL where the projection section affects the way the query is processed by the DBMS.

Now we can write queries like:


var e11 = from t in gd
where t is GDFileResult &&
t.Contains("sql")
select new { FileName = ((GDFileResult)t).Location,
Info = new FileInfo(((GDFileResult)t).Location)};

foreach (var fResult in e11)
{
Console.WriteLine(fResult.Info.Name);
}