Thursday, May 17, 2012

Mix of Queryable and Enumerable

Some useful observation from Expressions world.

Let's imagine we have the following class:


public class Foo
{
 public int[] Items { getset; }
}



And we use it in some IQueryable request in a following manner:


var queryable = new[] { new Foo {Items = new[] { 1, 2, 3 }} }.AsQueryable();

var q = queryable.Select(x => x.Items.Select(y => y % 2 == 0));


Note, that Items implements IEnumerable only, so this query is represented as:
var q = Queryable.Select(queryable, x => Enumerable.Select<intbool>(x.Items, y => y % 2 == 0));

Enumerable.Select takes Func<,> as its second parameters and we're expecting that this functor will be transformed into some method. But in this case, functor inside a Select method will be translated in Expressions as well:



ParameterExpression CS1 = Expression.Parameter(typeof(Foo), "x");
ParameterExpression CS3 = Expression.Parameter(typeof (int), "y");

MemberExpression memberExpression = Expression.Property(CS1, items);
IQueryable<IEnumerable<bool>> q1 = queryable.Select(
  Expression.Lambda<Func<FooIEnumerable<bool>>>(
    Expression.Call(null, select, new Expression[]
      {
        memberExpression,
        Expression.Lambda<Func<intbool>>(
          Expression.Equal(
            Expression.Modulo(CS3,
                              Expression.Constant(2, typeof (int))),
            Expression.Constant(0, typeof (int))), new[] {CS3})
      }), new[] {CS1}));
The same could be used for creating your own Expressions - if you use methods that accept functors, do not compile them in place, just insert as Expression.Lambda.

Note, that if Items implement IQueryable rather than IEnumerable, the nested Lamba expression will be quoted:
 
public class Bar
{
 public IQueryable<int> Items { getset; }
}
var queryable = new[] { new Bar {Items = new[] { 1, 2, 3 }.AsQueryable()} }.AsQueryable();

var q = queryable.Select(x => x.Items.Select(y => y % 2 == 0));

ParameterExpression CS1 = Expression.Parameter(typeof(Foo), "x");
ParameterExpression CS3 = Expression.Parameter(typeof (int), "y");

MemberExpression memberExpression = Expression.Property(CS1, items);
IQueryable<IEnumerable<bool>> q1 = queryable.Select(
  Expression.Lambda<Func<FooIEnumerable<bool>>>(
    Expression.Call(null, select, new Expression[]
      {
        memberExpression, Expression.Quote(
        Expression.Lambda<Func<intbool>>(
          Expression.Equal(
            Expression.Modulo(CS3,
                              Expression.Constant(2, typeof (int))),
            Expression.Constant(0, typeof (int))), new[] {CS3})
        )
      }), new[] {CS1}));
And this is the only purpose for Expression.Quote method.

No comments:

Post a Comment