Search code examples
c#expression-trees

How to use expressions to sort a collection based on an inherited interface property


This problem has been discussed to an extent in this question: Create Generic Expression from string property name but perhaps I'm missing the answer, or it is subtly different.

I have the following queryable extension method:

public static IQueryable<TSource> OrderByPropertyDescending<TSource>(IQueryable<TSource> source, string propertyName)
    {
        var sourceType = typeof (TSource);
        var parameter = Expression.Parameter(sourceType, "item");
        var orderByProperty = Expression.Property(parameter, propertyName);
        var orderBy = Expression.Lambda(orderByProperty, new[] { parameter });
        return Queryable.OrderByDescending(source, (dynamic) orderBy);
    }

For the purposes of this problem let us assume that the queryable source instance has a property called 'Created' which is a type of DateTime. If we define a class that has the property 'Created' on it and then OrderByDescending then the above will work fine. e.g.

var queryable = new List<EntityClassWithCreatedProperty>().AsQueryable();
var result = queryable.OrderByPropertyDescending("Created").ToList();

If we do the same thing but with an interface such as ICreated which has the Created property on it:

public interface ICreated
{
    DateTime Created { get; }
}

Then the following also works:

var queryable = new List<ICreated>().AsQueryable();
var result = queryable.OrderByPropertyDescending("Created").ToList();

If however, you have the following interface hierarchy:

public interface ITimestamped : ICreated
{
     ...
}

Then the following will fail:

var queryable = new List<ITimestamped>().AsQueryable();
var result = queryable.OrderByPropertyDescending("Created").ToList();

With the similar error message to that of the other question: Instance property 'Created' is not defined for type ITimestamped. I'm assuming that I need to find the property on the declaring type of interface (which I can do by crawling the source type) but then what do I do with it? Most attempts I have tried result in the incorrect original source type not being cast-able back to the IQueryable. Do I need to use a ConvertType call somewhere? Thanks.


Solution

  • I would use the other Expression.Property() method that takes a propertyInfo, e.g

    public static IQueryable<TSource> OrderByPropertyDescending<TSource>(IQueryable<TSource> source, string propertyName)
    {
        var sourceType = typeof (TSource);
        var parameter = Expression.Parameter(sourceType, "item");
        var propertyInfo = FindMyProperty(sourceType, propertyName);
        var orderByProperty = Expression.Property(parameter, propertyInfo);
        var orderBy = Expression.Lambda(orderByProperty, new[] { parameter });
        return Queryable.OrderByDescending(source, (dynamic) orderBy);
    }
    
    private static PropertyInfo FindMyProperty(Type type, string propertyName)
    {
        return type.GetProperty(propertyName);
    }
    

    At this point your question has nothing to do with expressions, it's a simple reflection question, and you have to find the correct way to get the properties you want. There are a lot of scenarios here. For your exact one, meaning get property from parent interface, you can do something like:

    private static PropertyInfo FindMyProperty(Type type, string propertyName)
    {
        var result = type.GetProperty(propertyName);
        if (result == null)
        {
            foreach(var iface in type.GetInterfaces())
            {
                var ifaceProp = FindMyProperty(iface, propertyName);
                if (ifaceProp != null)
                    return ifaceProp;
            }
    
        }
        return result;
    }
    

    DISCLAIMER! This is by no means the best way to get a property from a type, but it should work in your case. You should google around to find an implementation for FindMyProperty that satisfies all your requirements :)