Call SelectMany dynamically on the System.Linq.Dynamic path

There are several ways in System.Linq.Dynamic to dynamically generate Select, Where, and other Linq expressions. But for SelectMany this is not.

The Select method has the following form:

    public static IQueryable Select(this IQueryable source, string selector, params object[] values)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (selector == null) throw new ArgumentNullException("selector");
        LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
        IQueryable result = source.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable), "Select",
                new Type[] { source.ElementType, lambda.Body.Type },
                source.Expression, Expression.Quote(lambda)));

        return result;
    }

I tried to change the code above, after several hours of work, I could not find a way out.

Any suggestions are welcome.

Ying

+3
source share
3 answers

This is already implemented for our project, let me know if it works for you!

public static IQueryable SelectMany(this IQueryable source, string selector, params object[] values)
{
    if (source == null) 
        throw new ArgumentNullException("source");
    if (selector == null) 
        throw new ArgumentNullException("selector");

    // Parse the lambda
    LambdaExpression lambda = 
        DynamicExpression.ParseLambda(source.ElementType, null, selector, values);

    // Fix lambda by recreating to be of correct Func<> type in case 
    // the expression parsed to something other than IEnumerable<T>.
    // For instance, a expression evaluating to List<T> would result 
    // in a lambda of type Func<T, List<T>> when we need one of type
    // an Func<T, IEnumerable<T> in order to call SelectMany().
    Type inputType = source.Expression.Type.GetGenericArguments()[0];
    Type resultType = lambda.Body.Type.GetGenericArguments()[0];
    Type enumerableType = typeof(IEnumerable<>).MakeGenericType(resultType);
    Type delegateType = typeof(Func<,>).MakeGenericType(inputType, enumerableType);
    lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters);

    // Create the new query
    return source.Provider.CreateQuery(
        Expression.Call(
            typeof(Queryable), "SelectMany",
            new Type[] { source.ElementType, resultType },
            source.Expression, Expression.Quote(lambda)));
}
+6
source

I added another SelectMany that reconfigures the anonymous type.

 public static IQueryable SelectMany(this IQueryable source, string selector, string resultsSelector, params object[] values)
    {
        if (source == null)
            throw new ArgumentNullException("source");
        if (selector == null)
            throw new ArgumentNullException("selector");

        // Parse the lambda 
        LambdaExpression lambda =
            DynamicExpression.ParseLambda(source.ElementType, null, selector, values);

        // Fix lambda by recreating to be of correct Func<> type in case  
        // the expression parsed to something other than IEnumerable<T>. 
        // For instance, a expression evaluating to List<T> would result  
        // in a lambda of type Func<T, List<T>> when we need one of type 
        // an Func<T, IEnumerable<T> in order to call SelectMany(). 
        Type inputType = source.Expression.Type.GetGenericArguments()[0];
        Type resultType = lambda.Body.Type.GetGenericArguments()[0];
        Type enumerableType = typeof(IEnumerable<>).MakeGenericType(resultType);
        Type delegateType = typeof(Func<,>).MakeGenericType(inputType, enumerableType);
        lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters);

        ParameterExpression[] parameters = new ParameterExpression[] { 
        Expression.Parameter(source.ElementType, "outer"), Expression.Parameter(resultType, "inner") };
        LambdaExpression resultsSelectorLambda = DynamicExpression.ParseLambda(parameters, null, resultsSelector, values);

        // Create the new query 
        return source.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable), "SelectMany",
                new Type[] { source.ElementType /*TSource*/, /*,TCollection*/resultType /*TResult*/, resultsSelectorLambda.Body.Type},
                source.Expression, Expression.Quote(lambda), Expression.Quote(resultsSelectorLambda)));
    }

I still need to figure out how to do the following using Dynamic, the goal is to return a new result object.

        var customerandorderflat = db.Customers
            .SelectMany(c => c.Orders.SelectMany(o => o.Order_Details,
                    (ord, orddetail) => new
                        {
                            OrderID = ord.OrderID,
                            UnitPrice = orddetail.UnitPrice
                        }).DefaultIfEmpty(),
                (cus, ord) => new
                {
                    CustomerId = cus.CustomerID,
                    CompanyName = cus.CompanyName,
                    OrderId = ord.OrderID == null ? -1 : ord.OrderID,
                    UnitPrice = ord.UnitPrice
                });
+1

I use NWDB when I try:

var customerandorderquery = db.Customers .SelectMany(c => c.Orders.DefaultIfEmpty()).Select("new(CustomerId, CompanyName, OrderId)"); 

I get an error because it CompanyNameis in Customersnot Orders. Thus, he does not see a combination of two objects. When I do this:

.SelectMany(c => c.Orders.DefaultIfEmpty(), (cus, ord) => new { CustomerId = cus.CustomerID, OrderId = ord.OrderID == null ? -1 : ord.OrderID }); 

It returns the desired result.

0
source

Source: https://habr.com/ru/post/1748706/


All Articles