Question

Well, I have a problem with creating expression tree for SelectMany.. especially at the typeArguments part..

So, I have a database with tables like below:

[Group] (one to many) [GroupDetail] (many to one) [Item] (one to many) [ItemDetail]

  • GroupDetail.group is a Group
  • GroupDetail.item is an Item
  • ItemDetail.item is an Item
  • Item.itemDetail is a collection of ItemDetail
  • Group.groupDetail is a collection of GroupDetail

so you can see that group detail is simply a many to many link for Group and Item and (one to many) is a one to many relation..

For example, the data is like below:

Group, GroupDetail, Item, ItemDetail
------------------------------------
gr1, grDt1, ItemA, PartsAA
gr1, grDt1, ItemA, PartsAB
gr1, grDt2, ItemB, PartsBA
gr1, grDt2, ItemB, PartsBB

gr2, grDt3, ItemC, PartsCA
gr2, grDt4, ItemA, PartsAA
gr2, grDt4, ItemA, PartsAB

gr3, grDt4, ItemD, PartsDA
gr3, grDt5, ItemE, PartsEA

I want to select items and each of it's detail by a group search and return it as a collection of some sort of view class..

Similar like this function below:

public IQueryable<ItemGroupDetailView> getViewQ(IQueryable<GroupDetail> myQ)
{
    return myQ.SelectMany(
    m => m.item.itemDetail,
        (m, n) => new ItemGroupDetailView
        {
            groupName = m.group.name,
            groupDetailCount = m.group.groupDetail.Count,
            item = new ItemView
            {
                itemName = n.item.name,
                itemDetailCount = n.item.itemDetail.Count
            },
            itemDetail = new ItemDetailView
            {
                itemDetailName = n.name
            }        
        }
    );
}

simply like that above BUT I want it to be a dynamic exp tree instead, so maybe I can just use it like:

Filter filter = new Filter("gr1","ItemA"); // just a filter

var myQ = getSearchQ(filters); // it gets all the where etc, everything is fine here.. 
var viewQ = getViewQ(myQ);  // simply to convert the data to the view,.. where all the errors are
var finalQ = ApplyLimit(ApplyGrouping(ApplySorting(ApplySelect(myQ)));   // paging, sorting, grouping, etc.. 

// run the select.. get the count etc..

now I want to make it dynamic but I seems to get it wrong on the SelectMany part

This is roughly how I do the SelectMany things:

step 1: I bind the property/field assignment.. it came from some sort of list-string-configuration-kinda-thing that map the assignment

PropertyInfo pInfo; 
MemberExpression mExp;
// parse getproperty reflect etc... 
List<MemberAssignment> memberAssginments = new List<MemberAssignment>();
memberAssginments.Add(Expression.Bind(pInfo, mExp); 

step 2: then the usual member init

MemberInitExpression mie =
    Expression.MemberInit(Expression.New
        (typeof(ItemGroupDetailView)), memberAssginments);

so I get this:

new ItemGroupDetailView
    {
        groupName = m.group.name,
        groupDetailCount = m.group.groupDetail.Count,
        item = new ItemView
        {
            itemName = n.item.name,
            itemDetailCount = n.item.itemDetail.Count
        },
        itemDetail = new ItemDetailView
        {
            itemDetailName = n.name
        }        
    }

step 3: then get the expression collectionSelector & resultSelector

ParamterExpression m = Expression.Parameter(typeof(GroupDetail),"m");
ParamterExpression n = Expression.Parameter(typeof(ItemDetail),"n");    

Expression<Func<GroupDetail, ItemDetail, ItemGroupDetailView>> exp2 =     
    Expression.Lambda<Func<GroupDetail, ItemDetail, ItemGroupDetailView>>
        (mie, new ParameterExpression[] { m, n });

I think I get what I need, exp2 (resultSelector):

    (m, n) => new ItemGroupDetailView
    {
        groupName = m.group.name,
        groupDetailCount = m.group.groupDetail.Count,
        item = new ItemView
        {
            itemName = n.item.name,
            itemDetailCount = n.item.itemDetail.Count
        },
        itemDetail = new ItemDetailView
        {
            itemDetailName = n.name
        }        
    }

and with similar way I get the other clause, exp1 (collectionSelector)

MemberExpression mEx = .... reflect get property/field etc..

Expression<Func<GroupDetail, IEnumerable<ItemDetail>>> exp1 = 
    Expression.Lambda<Func<GroupDetail, IEnumerable<ItemDetail>>>(mEx, m);

so I get this:

m => m.item.itemDetail

step 4: then get the selectMany MethodCallExpression itself

MethodCallExpression selectManyExp =
     Expression.Call(
        typeof(Queryable),
        "SelectMany",
        new Type[] { 
            typeof(GroupDetail), 
            typeof(Expression<Func<GroupDetail, IEnumerable<ItemDetail>>>),  
            typeof(Expression<Func<GroupDetail, ItemDetail, ItemGroupDetailView>>) 
        },
        new Expression[] { Expression.Quote(exp1), Expression.Quote(exp2) }
    );

It doesnt work at all..

(No generic method 'SelectMany' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.)

so I think the main question here is:

  1. How do I build an expression tree for such selectMany query
  2. How do I build an expression query that has resultSelector & collectionSelector and multiple parameters..
  3. and why does the code below works, but the Expression.Call always error..
myQ.SelectMany(exp1, exp2);

I guess I dont understand how SelectMany or Expression Tree works.. :(

but, I need this to be dynamic because the property/field assignment binding and source, selector and result is dynamic

public IQueryable<TView> getViewQ(IQueryable<T> myQ)
{    
    // some code..
}

EDIT 1 :

Switching the exp1 and exp2.. now exp1 is the collectionSelector and exp2 is the resultSelector..

EDIT 2 :

Furthermore I tried several things: first, I change the type argument like what Mike said below, but the error still the same

MethodCallExpression selectManyExp =
     Expression.Call(
        typeof(Queryable),
        "SelectMany",
        new Type[] { 
            typeof(GroupDetail), 
            typeof(ItemDetail),  
            typeof(ItemGroupDetailView) 
        },
        new Expression[] { Expression.Quote(exp1), Expression.Quote(exp2) }
    );

then I try some reflection this and that to check

System.Reflection.MethodInfo sminfo = null;
System.Reflection.MethodInfo sminfo2 = null;

IEnumerable<System.Reflection.MethodInfo> sminfos = typeof(Queryable)
    .GetMethods(System.Reflection.BindingFlags.Static
        | System.Reflection.BindingFlags.Public)
    .Where(xxx => xxx.Name.Equals("SelectMany"));

foreach (System.Reflection.MethodInfo mi in sminfos)
{
    if (mi.GetParameters().Count() == 3)
    {
        sminfo = mi;
    }
}

/*
I ran this step by step to make sure that the method I get in sminfo is:
public static IQueryable<TResult> SelectMany<TSource, TCollection, TResult>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, IEnumerable<TCollection>>> collectionSelector,
    Expression<Func<TSource, TCollection, TResult>> resultSelector
);
*/

sminfo2 = sminfo.MakeGenericMethod(
    new Type[] { 
        typeof(GroupDetail), typeof(ItemDetail), typeof(ItemGroupDetailView) 
    });

MethodCallExpression selectManyExp = 
    Expression.Call(sminfo2, new Expression[] { exp1, exp2 });

and I get different error: (Incorrect number of arguments supplied for call to method ..)

and it tells me that the method required 3 parameters instead of 2, and the one I miss is the IQueryable<GroupDetail> source

so I get back to the Expression.Call and add the source parameter

MethodCallExpression selectManyExp =
     Expression.Call(
        typeof(Queryable),
        "SelectMany",
        new Type[] { 
            typeof(GroupDetail), 
            typeof(ItemDetail),  
            typeof(ItemGroupDetailView) 
        },
        new Expression[] { myQ.Expression, exp1, exp2 }
    );

return (IQueryable<ItemGroupDetailView>)myQ.Provider.CreateQuery(selectManyExp);

and it works.. :D

Sorry for the messy and long post,.. my English is bad.. :(

Was it helpful?

Solution

It looks like you've conflated the type parameters with the formal parameters. I believe your type arguments should look like below:

MethodCallExpression selectManyExp =
    Expression.Call(
        typeof(Queryable),
        "SelectMany",
        new Type[] { 
            typeof(GroupDetail), 
            typeof(ItemDetail),  
            typeof(ItemGroupDetailView) 
        },
        new Expression[] { Expression.Quote(exp1), Expression.Quote(exp2) }
);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top