动态构建表达式树以进行排序

时间:2016-05-09 20:35:29

标签: c# linq sorting expression-trees

我的项目中有一个扩展名,让我用一个字符串对IEnumerable进行排序,这样就可以更加动态地进行排序。

所以,如果我有这些模型:

public MyModel
{
    public int Id {get; set;}
    public string RecordName {get; set;}
    public ChildModel MyChild {get; set;}
}

public ChildModel 
{
    public int ChildModelId {get; set;}
    public DateTime SavedDate {get; set;}
}

我可以分两种方式:

myList.OrderByField("RecordName ");

myList.OrderByField("MyChild.SavedDate");

但是,如果我的对象具有ICollection属性,例如ICollection<ChildModel> MyChildren,我可以像我这样硬编码:

myList
    .OrderBy(m => m.MyChildren
        .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);

得到我想要的东西。

我的问题是,如何更新我的扩展方法以允许获得相同的结果:

myList.OrderByField("MyChildren.SavedDate");

这是我目前的扩展名:

public static class MkpExtensions
{
    public static IEnumerable<T> OrderByField<T>(this IEnumerable<T> list, string sortExpression)
    {
        sortExpression += "";
        string[] parts = sortExpression.Split(' ');
        bool descending = false;
        string fullProperty = "";

        if (parts.Length > 0 && parts[0] != "")
        {
            fullProperty = parts[0];

            if (parts.Length > 1)
            {
                descending = parts[1].ToLower().Contains("esc");
            }

            ParameterExpression inputParameter = Expression.Parameter(typeof(T), "p");
            Expression propertyGetter = inputParameter;
            foreach (string propertyPart in fullProperty.Split('.'))
            {
                PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart);
                if (prop == null)
                    throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'");
                propertyGetter = Expression.Property(propertyGetter, prop);
            }

            Expression conversion = Expression.Convert(propertyGetter, typeof(object));
            var getter = Expression.Lambda<Func<T, object>>(conversion, inputParameter).Compile();

            if (descending)
                return list.OrderByDescending(getter);
            else
                return list.OrderBy(getter);
        }

        return list;
    }
}

我在考虑检查prop的类型并执行if... else语句,但我不确定。

也许是这样的:

foreach (string propertyPart in fullProperty.Split('.'))
{
    var checkIfCollection = propertyGetter.Type.GetInterfaces()//(typeof (ICollection<>).FullName);
        .Any(x => x.IsGenericType &&
            (x.GetGenericTypeDefinition() == typeof(ICollection<>) || x.GetGenericTypeDefinition() == typeof(IEnumerable<>)));

    if (checkIfCollection)
    {
        // Can I get this to do something like 
        // myList.OrderBy(m => m.MyChildren.Max(c => c.SavedDate));

        // So far, I can get the propertyGetter type, and the type of the elements:
        var pgType = propertyGetter.Type;
        var childType = pgType.GetGenericArguments().Single();

        // Now I want to build the expression tree to get the max
        Expression left = 
            Expression.Call(propertyGetter, pgType.GetMethod("Max", System.Type.EmptyTypes));
        // But pgType.GetMethod isn't working
    }
    else
    {
        PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart);
        if (prop == null)
            throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'");
        propertyGetter = Expression.Property(propertyGetter, prop);
    }
}

1 个答案:

答案 0 :(得分:0)

Max函数是一种扩展方法,而不是IEnumerableICollection的成员方法。你必须从它的班级Enumerable中调用它。

以下是如何通过表达式树调用Max的示例:

IEnumerable<int> list = new List<int> { 3, 5, 7, 2, 12, 1 };
var type = typeof(Enumerable); //This is the static class that contains Max

//Find The overload of Max that matches the list
var maxMethod = type.GetMethod("Max", new Type[] { typeof(IEnumerable<int>) });
ParameterExpression p = Expression.Parameter(typeof(IEnumerable<int>));

//Max is static, so the calling object is null
var exp = Expression.Call(null, maxMethod, p); 
var lambda = Expression.Lambda<Func<IEnumerable<int>, int>>(exp, p);
Console.WriteLine(lambda.Compile()(list));