none
ExpressionTrees -> multiple selection projection RRS feed

  • Question

  • I am having a problem trying to create a Expression.Call for Select whereby it will return two new objects.  I need to produce the following using expression trees.

                     var query1 = from s in dataContext.tblStore
                                 join f in dataContex.tblFood on s.SomeID equals f.SomeID
                                 where s.StoreType = new Guid("959e1659-16c7-4590-87af-48416f123456") &&
                                 f.FoodLocation == "New York"
                                 select new { s.StoreID };

                    var query2 = from s in dataContext.tblStore
                                 join f in dataContex.tblFood on s.SomeID equals f.SomeID
                                 where s.StoreType = new Guid("959e1659-16c7-4590-87af-48416f123456") &&
                                 f.FoodLocation == "New York"
                                 select new { s.StoreID, f.FoodID };

    I have figured out the code using expression trees for query1 (with a single projection); however, I am not able to get query2 to work.
    • Edited by Kosy1993 Friday, April 17, 2009 6:45 PM code edit
    Friday, April 17, 2009 3:31 PM

All replies

  • Your first problem is that the construct "new { ... }" causes the compiler to generate a new data type that becomes part of your program. Without the C# compiler seeing that syntax you won't have a type at runtime to select the data into.  

    So you'll need some type with a signature like this:

    class SomeType
    {
        public int StoreID {get; private set;}
        public int FoodID { get; private set; }
        public SomeType(int storeId, int foodId) { this.StoreID = storeId; this.FoodID = foodId; }
    }

    and then you can create a NewExpression node:

    var newx = Expression.New(typeof(SomeType), storeId, foodId);

    An alternative is to use Reflection.Emit to create a type at runtime that matches what you need.  The Dynamic LINQ sample does this.  Perhaps you could borrow & massage some code from there to do what you want.

    Or if you don't really care what the property names of the result type is you could define some general purpose generic classes like:

    class Pair<T1,T2>
    {
        public T1 Value1 {get; private set;}
        public T2 Value2 { get; private set; }
        public Pair(T1 value1, T2 value2) { this.Value1 = value1; this.Value2 = value2; }
    }




    Wayward LINQ Lacky
    Saturday, April 18, 2009 12:29 AM
    Moderator
  • Matt,

    With the code below, I am getting an InvalidOperationException "No method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied arguments.".

    Am I not creating the NewExpression correctly?  In the below code, fullExpression is the expression that has been built up to the select new {} point.   I should correct my first post, I have written code that can perform the following select: select s.StoreID; not select new { s.StoreID };

    Expression val1 = Expression.Constant(Guid.Empty, typeof(Guid));
    Expression val2 = Expression.Constant(Guid.Empty, typeof(Guid));
    ParameterExpression param = Expression.Parameter(typeof(SomeType), "SomeType");
    ConstructorInfo ctor = typeof(SomeType).GetConstructors().Where(r => r.Name == ".ctor").SingleOrDefault();
    NewExpression newX2 = Expression.New(typeof(SomeType).GetConstructor(new Type[] { typeof(Guid), typeof(Guid) }), val1, val2);
                   

    Type delType = LambdaExpression.GetFuncType(new Type[] { param.Type, param.Type });
    var selLambda = Expression.Lambda(delType, newX2, param);

    fullExpression = Expression.Call(
      typeof(Queryable),
      "Select",
      new Type[] { this.TypeITable.ElementType, param.Type },
      fullExpression, selLambda
    );

    Thank you for  your time.
    Monday, April 20, 2009 4:11 PM
  • OK, Lets try and simplify things.  I would like to achieve the following (not using dynamic linq) rather using Expression Trees.

    DataContext dc= new DataContext(connectionString);
    var query = from s in dc.tblStore
    join f in dc.tblFood on s.SomeID equals f.SomeID
    select new { s.StoreId, f.FoodId };

    The code below will create the join and return (resultSelector) by custom AnonymousType (class Anonymous<T1,T2>).  I am not able to take the results (resultSelector) and feed that into a Expression.Call ("Select"). 

    class Anonymous<T1,T2>
    {
        public T1 Value1 {get; private set;}
        public T2 Value2 { get; private set; }
        public Anonymous(T1 value1, T2 value2) { this.Value1 = value1; this.Value2 = value2; }
    }

    try
    {
      ParameterExpression storeParam= Expression.Parameter(typeof(tblStore), "store");
      Expression outerProperty = MemberExpression.Property(storeParam, "SomeID");
      if (this.IsTypeNullable(outerProperty.Type) == true)
      {
        // --- Convert the generic nullable object to its underlying basetype ---
         outerProperty = Expression.Convert(outerProperty, this.GetUnderlyingType(outerProperty.Type));
       }

    LambdaExpression outerKeySelector = Expression.Lambda(outerProperty, storeParam);

    // inner join
    ParameterExpression foodParam = Expression.Parameter(typeof(tblFood), "food");

    Expression innerProperty = MemberExpression.Property(foodParam, "SomeID");
    if (this.IsTypeNullable(innerProperty.Type) == true)
    {
      // --- Convert the generic nullable object to its underlying basetype ---
    innerProperty = Expression.Convert(innerProperty, this.GetUnderlyingType(innerProperty.Type));
    }

    var innerKeySelector = Expression.Lambda(innerProperty, foodParam);

    var ctor = typeof(Anonymous<tblStore, tblFood>).GetConstructor(new Type[] { typeof(tblFood), typeof(tblFood) });
                  
    List<Expression> args = new List<Expression>();
    args.Add(storeParam);
    args.Add(foodParam);

    var newExpr = Expression.New(ctor,args.ToArray());


    Expression anonymousProjectionExpression = newExpr;
    var anonymousProjection_ParameterExpression = new ParameterExpression[] { storeParam, foodParam};

    var resultSelector = Expression.Lambda(anonymousProjectionExpression, anonymousProjection_ParameterExpression);

    ITable storeTable = dc.GetTable(tblStore.Type);
    ITable foodTable = dc.GetTable(tblFood.Type);

    var expression = Expression.Call(
         typeof(Queryable), "Join",
         new Type[] { storeTable.Type, foodTable.AsQueryable().ElementType, outerKeySelector.Body.Type, resultSelector.Body.Type },
                    storeTable.Expression,
                    foodTable.AsQueryable().Expression,
                    Expression.Quote(outerKeySelector),
                    Expression.Quote(innerKeySelector),
                    Expression.Quote(resultSelector));

    This is where I am confused.......  Do I need to create another Anonymous class to achieve my Select new {s.StoreId,f.FoodId}  The scratch code produces an InvalidOperationException "No method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied arguments.".

    Scratch code:
    Expression storeStoreId = MemberExpression.Property(anonymousProjection_ParameterExpression[0], "StoreId");
    Expression  foodFoodId = Expression.Property(anonymousProjection_ParameterExpression[1], "FoodId");

    var ctor2 = typeof(Anonymous<Func<tblStore,Guid>,Func<tblFood,Guid>>).GetConstructor(new Type[] { typeof(Func<tblStore,Guid>), typeof(Func<tblStore,Guid>) });
                   
    List<Expression> args2 = new List<Expression>();
    args2.Add(Expression.Lambda(storeStoreId ,storeParam));
    args2.Add(Expression.Lambda(foodFoodId ,foodParam));

    var newExpr2 = Expression.New(ctor2, args2.ToArray());

    var myLambda = Expression.Lambda(newExpr2, anonymousProjection_ParameterExpression);

                    expression = Expression.Call(
                        typeof(Queryable),
                        "Select",
                        new Type[] { storeTable.ElementType, newExpr2..Type },
                        expression, myLambda
                        );

    Any Advice would be greatly appreciated. 
    • Edited by Kosy1993 Tuesday, April 21, 2009 3:16 PM New Scratch Code
    Tuesday, April 21, 2009 2:48 PM
  • You don't need to use the select method explicilty.  You've already done the select in the Join's result selector.  In fact, this is your problem.  After the join, the result sequence is now of type Anonymous<tblStore,tblFoo> not whatever it was before the join.  In fact, if you write out the query in C# query syntax, even C# won't add an additional call to the Select method after the call to Join, since Join has the result-selector argument available.


    Wayward LINQ Lacky
    Wednesday, April 22, 2009 3:35 PM
    Moderator