LINQ multiple join IQueryable modify result selector expression

c# entity-framework expression linq

Question

Imagine the table structure shown below.

---------
TableA
ID
Name

---------
TableB
ID
TableAID

---------
TableC
ID
TableBID

In order to combine these three tables and accept an input, I want to define a function.Expression<Func<TableA, TableB, TableC, T>> as a picker.

I thus would like anything along these lines:

public IQueryable<T> GetJoinedView<T>(Expression<Func<TableA, TableB, TableC, T>> selector)
{
    return from a in DbContext.Set<TableA>()
           join b on DbContext.Set<TableB>() a.ID equals b.TableAID
           join c on DbContext.Set<TableC>() b.ID equals c.TableBID
           select selector;
}

The aforementioned certainly does not accomplish my goals, but this will provide me with anIQueryable sort of phrase. I could use the syntax for method chains, but that would mean I would require several selectors, one for each method chain invocation. Is it possible to apply the selection to an anonymous type, like in the following incomplete function:

public IQueryable<T> GetJoinedView<T>(Expression<Func<TableA, TableB, TableC, T>> selector)
{
    var query = from a in DbContext.Set<TableA>()
                join b on DbContext.Set<TableB>() a.ID equals b.TableAID
                join c on DbContext.Set<TableC>() b.ID equals c.TableBID
                select new
                {
                    A = a, B = b, C = c
                };

    // I need the input selector to be modified to be able to operate on
    // the above anonymous type
    var resultSelector = ModifyInputSelectorToOperatorOnAnonymousType(selector);

    return query.Select(resultSelector);
}

Any suggestions for how to accomplish this?

1
6
10/28/2013 10:48:00 PM

Accepted Answer

ZZZ_tmp
13
10/28/2013 9:48:44 PM

Popular Answer

What we can do is begin by merging the data into an anonymous object exactly as you have done.

We'll start off by using this straightforward helper class and function to enable us to replace every instance of one expression in a given expression with another expression:

public class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

Here is our actual methodology. We can have our function take an expression that represents mapping the input sequence into the first argument as well as selectors for the other two parameters in order to map a sequence of these anonymous objects using a three parameter constructor. The body of the "real" selector's first parameter's selector can then be substituted for all occurrences of the first parameter there.

Be aware that in order to support type inference on the anonymous type, we must add a parameter at the beginning.

public static Expression<Func<TInput, TOutput>>
    ModifyInputSelectorToOperatorOnAnonymousType
    <TInput, TOutput, TParam1, TParam2, TParam3>(
    //this first param won't be used; 
    //it's here to allow type inference
    IQueryable<TInput> exampleParam,
    Expression<Func<TInput, TParam1>> firstSelector,
    Expression<Func<TInput, TParam2>> secondSelector,
    Expression<Func<TInput, TParam3>> thirdSelector,
    Expression<Func<TParam1, TParam2, TParam3, TOutput>> finalSelector)
{
    var parameter = Expression.Parameter(typeof(TInput), "param");

    var first = firstSelector.Body.Replace(firstSelector.Parameters.First(),
        parameter);
    var second = secondSelector.Body.Replace(secondSelector.Parameters.First(),
        parameter);
    var third = thirdSelector.Body.Replace(thirdSelector.Parameters.First(),
        parameter);

    var body = finalSelector.Body.Replace(finalSelector.Parameters[0], first)
        .Replace(finalSelector.Parameters[1], second)
        .Replace(finalSelector.Parameters[2], third);

    return Expression.Lambda<Func<TInput, TOutput>>(body, parameter);
}

Now that type inference has been satisfied, we can call it by passing in the query, a selection for the anonymous object's first, second, and third parameters, and finally our final selector:

var resultSelector = ModifyInputSelectorToOperatorOnAnonymousType(
    query, x => x.A, x => x.B, x => x.C, selector);

And you already have the rest.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow