tags:

views:

459

answers:

4

I'm creating a dynamic expression, which will order items in a list by some rule (lambda exp.). This is the code:

Expression<Func<String, String>> exp = o => o;

MethodCallExpression orderByExp = Expression.Call(typeof(Enumerable), "OrderBy",
    new Type[] { typeof(String), exp.Body.Type }, Expression.Parameter(typeof(IEnumerable<String>), "list"), exp);

Now I want to execute previously created expression on specific data to sort it, but it fails because of some strange exceptions like "Lambda Parameter not in scope" or "Argument expression is not valid".

var data = new String[] { "asdasdasd", "asdads", "123", "xcvxcvs", "ASDSD" };

// one of attempts: doesn't work
var result = data.AsQueryable().Provider.CreateQuery<String>(orderByExp);

Can somebody help me with this?

A: 

Is there any particular reason you're not just calling:

data.AsQueryable().OrderBy(exp);

Do you even need to use IQueryable here? I get the feeling I'm missing some of the big picture. Are you actually going to be calling this as part of LINQ to SQL (or LINQ to Entities)? If it's just within LINQ to Objects, can't you just use data.OrderBy(exp)?

Basically, some more explanation would be helpful :)

Jon Skeet
The whole picture is next:I want to create some query (rules like order, where, may be else) on some data, which I don't have right now. But I know its type. This query will later be sent to some web service, which has the data and will run the query on it.
Kamarey
and forgot, this is a simple LINQ to Objects.
Kamarey
Okay, now I'm confused - if you're sending the query to a web service, it doesn't sound like it's really LINQ to Objects. What do you want to do the ordering - the web service, or the local process?
Jon Skeet
More correctly I want to send an expression (expression tree) to a web service, which will retrieve a data from DB, run my expression/query on that data and return the result data. I'm not sure all this called LINQ to Objects.
Kamarey
Okay. Aside from the fun you're going to have serializing the expression tree, I believe just using Queryable.OrderBy (as per my answer) should be okay. Have you tried it yet?
Jon Skeet
Yes, I used some ExpressionSerialization lib, which found somewhere in internet which serializes/deserializes object of Expression type.
Kamarey
Downvoters: please give comments to explain.
Jon Skeet
A: 

This is the working code:

Expression<Func<String, String>> exp = o => o;
var list = Expression.Parameter(typeof(IEnumerable<String>), "list");

MethodCallExpression orderByExp = Expression.Call(typeof(Enumerable), "OrderBy",
    new Type[] { typeof(String), exp.Body.Type }, list, exp);

var lambda = Expression.Lambda<Func<IEnumerable<String>, IEnumerable<String>>>(orderByExp, list);
var data = new String[] { "asdasdasd", "asdads", "123", "xcvxcvs", "ASDSD" };
var result = lambda.Compile()(data);
  1. To execute the MethodCallExpression you should wrap it in lambda expression.
  2. Be sure you use the same instance of parameter expression ('list'), when creating a MethodCallExpression and LambdaExpression, and not two separate instances even with the same name, otherwise you will get the: "Lambda Parameter not in scope" exception without much explanation.

thanks experts

Kamarey
+1  A: 

order any enumerable by a property(no reflection):

public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> items, string property, bool ascending)
        {
            var MyObject = Expression.Parameter(typeof (T), "MyObject");
            var MyEnumeratedObject = Expression.Parameter(typeof (IEnumerable<T>), "MyEnumeratedObject");
            var MyProperty = Expression.Property(MyObject, property);
            var MyLamda = Expression.Lambda(MyProperty, MyObject);
            var MyMethod = Expression.Call(typeof(Enumerable), ascending ? "OrderBy" : "OrderByDescending", new[] { typeof(T), MyLamda.Body.Type }, MyEnumeratedObject, MyLamda);
            var MySortedLamda = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(MyMethod, MyEnumeratedObject).Compile();
            return MySortedLamda(items);
        }
Yeah, it's a good way. But in my situation I need sort/filter more than just by property name (my example just a simple case to explain the other problem). I need to execute a simple lambda like o => o.Users.Any(user => user.Type == 1). Except this your solution is ok. Get my +1:)
Kamarey
A: 

I had the nearly the same problem working with Linq, i decided to write a extension function, and some of the ideas were taken from the answers of this question:

<Extension()> _
Public Function OrderBy(Of T)(ByVal query As IEnumerable(Of T), ByVal sortColumn As String, ByVal direction As String) As IEnumerable(Of T)
        Dim methodName As String = String.Format("OrderBy{0}", If(direction.ToLower() = "asc", "", "Descending"))
        Dim parameter As ParameterExpression = Expression.Parameter(GetType(T), "p")
        Dim memberAccess As MemberExpression = Nothing

        For Each _property As Object In sortColumn.Split(".")
            memberAccess = MemberExpression.Property(If(memberAccess, CType(parameter, Expression)), _property)
        Next

        Dim orderByLambda As LambdaExpression = Expression.Lambda(memberAccess, parameter)
        '
        Dim myEnumeratedObject As ParameterExpression = Expression.Parameter(GetType(IEnumerable(Of T)), "MyEnumeratedObject")

        Dim result As MethodCallExpression = Expression.Call(GetType(Enumerable), _
                  methodName, _
                  New System.Type() {GetType(T), memberAccess.Type}, _
                  myEnumeratedObject, _
                  orderByLambda)

        Dim lambda = Expression.Lambda(Of Func(Of IEnumerable(Of T), IEnumerable(Of T)))(result, myEnumeratedObject)
        Return lambda.Compile()(query)
    End Function
respinoza