Kamarey
Kamarey

Reputation: 11079

Dynamically created expressions

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?

Upvotes: 3

Views: 3265

Answers (4)

respinoza
respinoza

Reputation: 11

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

Upvotes: 1

Kevin Sucre
Kevin Sucre

Reputation:

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);
        }

Upvotes: 3

Kamarey
Kamarey

Reputation: 11079

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

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1503290

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 :)

Upvotes: 0

Related Questions