Question

T is a type that may or may not have a specific property, lets say 'City'. For the types that have a property named 'City', I would like to restrict the records such that only residents of the Gotham are returned and they are sorted.

public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) 
{

    var type = typeof(T);
    var property = type.GetProperty(ordering);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExp = Expression.Lambda(propertyAccess, parameter);
    MethodCallExpression resultExp = Expression.Call(
                typeof(Queryable), 
                "OrderBy", 
                new     Type[] { type, property.PropertyType }, 
                source.Expression, 
                Expression.Quote(orderByExp));


    string propertyToRestrictOn = "City";
    string restrictedValue = "Gotham";
    var restrictedProperty = type.GetProperty(propertyToRestrictOn);
    if(null ! = restrictedProperty )
    {
      // TODO: What to add here so than only those records are returned that have a 
      // property named City and the value is 'Gotham'???
    }

   return source.Provider.CreateQuery<T>(resultExp);
}

if possible please name/link some helpful literature here as well just in case I have to create more complex queries i.e. mix And/OR

The code was borrowed from How do I apply OrderBy on an IQueryable using a string column name within a generic extension method?

Was it helpful?

Solution 2

I think you're making this harder than you have to. In the first part of your code (the OrderBy()), you don't actually need to generate the whole query expression, just the lambda inside it. And in the second part (the optional Where()) you can do pretty much the same thing, just add Expression.Equal() and Expression.Constant():

public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering)
{
    var type = typeof(T);
    var property = type.GetProperty(ordering);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    // necessary for value types to work
    var cast = Expression.Convert(propertyAccess, typeof(object));
    var orderByExp = Expression.Lambda<Func<T, object>>(cast, parameter);

    IQueryable<T> result = source.OrderBy(orderByExp);

    string propertyToRestrictOn = "City";
    string restrictedValue = "Gotham";
    var restrictedProperty = type.GetProperty(propertyToRestrictOn);
    if (restrictedProperty != null)
    {
        var restrictionParameter = Expression.Parameter(type, "p");
        var restrictionPropertyAccess =
            Expression.MakeMemberAccess(restrictionParameter, restrictedProperty);
        var restrictionEquality =
            Expression.Equal(restrictionPropertyAccess,
                             Expression.Constant(restrictedValue));
        var whereExp =
            Expression.Lambda<Func<T, bool>>(restrictionEquality, restrictionParameter);

        result = result.Where(whereExp);
    }

   return result;
}

Also, if your method is going to do more than just ordering, I think you shouldn't call it OrderBy().

OTHER TIPS

I'm not quite sure if I have understood you correctly, but I think I was in the same situation a few months ago. The posted code here was my solution.

I think you should be especially interested in line 24.


Edit:

PropertyInfo p = ... // I used reflection in my examply to get properties with a certain Attribute

var expressionParameter = Expression.Parameter(typeof(SomeClass), "lambda");    
var parameter = new [] { expressionParameter };

var propertyAccess = Expression.Property(expressionParameter, p);
var nullCheck = Expression.NotEqual(propertyAccess, Expression.Constant(null, p.PropertyType));
var nullCheckLambda = Expression.Lambda<Func<SomeClass, Boolean>>(nullCheck, parameter);

var containsMethodInfo = typeof(String).GetMethod("Contains", new[] { typeof(String) });
var contains = Expression.Call(propertyAccess, containsMethodInfo, Expression.Constant("ell"));
var containsLambda = Expression.Lambda<Func<SomeClass, Boolean>>(contains, new[] { expressionParameter });

var predicate = Expression.Lambda<Func<SomeClass, Boolean>>(
// line 24 
Expression.AndAlso(nullCheckLambda.Body, containsLambda.Body), parameter);

Console.WriteLine(predicate.ToString());

You're half-way there already. You have the ordered expression already, so you're just going to use the Queryable.OrderBy expression call in a Queryable.Where expression (or vice-versa, doesn't really matter).

if(null != restrictedProperty )
{
    var notEqualExp = Expression.NotEqual(parameter,
                            Expression.Constant(restrictedValue, typeof(string)));
    resultExp = Expression.Call(
            typeof(Queryable), 
            "Where", 
            new Type[] { type }, 
            resultExp, 
            Expression.Lambda(notEqualExp, parameter));
}

Haven't worked with building Expressions by hand in a while, so this is purely being done from memory. However, it should at least get you started and give you something to work with.

P.S. I would actually perform this check BEFORE the OrderBy method call. That way you end up with Queryably.Where(...).OrderBy(...) instead. But I guess if this is getting translated by the provider anyway, then shouldn't matter. However, just something I'd do to reduce any ambiguity of the generated query.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top