简体   繁体   English

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

[英]Dynamically build an Expression tree for sorting

I have an extension in my project that let's me sort an IEnumerable with a string, so that sorting can be done more dynamically. 我的项目中有一个扩展名,可以让我对带有字符串的IEnumerable进行排序,以便可以更动态地进行排序。

So, if I have these models: 因此,如果我有以下模型:

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

I can sort two ways: 我可以通过两种方式进行排序:

myList.OrderByField("RecordName ");

myList.OrderByField("MyChild.SavedDate");

However, if my object has an ICollection property, like ICollection<ChildModel> MyChildren I can hard code my sort like this: 但是,如果我的对象具有ICollection属性,例如ICollection<ChildModel> MyChildren ,则可以像这样对我的排序进行硬编码:

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

And get what I want. 并得到我想要的。

My question is , how can I update my extension method to allow to get the same results with this: 我的问题是 ,我该如何更新扩展方法以允许获得与此相同的结果:

myList.OrderByField("MyChildren.SavedDate");

Here is my current extension: 这是我当前的扩展名:

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

I was thinking of checking the type of the prop and doing an if... else statement, but I'm not sure. 我当时正在考虑检查prop的类型并执行if... else语句,但是我不确定。

Maybe something like this: 也许是这样的:

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

The Max function is an extension method, not a member method of IEnumerable or ICollection . Max函数是扩展方法,而不是IEnumerableICollection的成员方法。 You must call it from it's class Enumerable . 您必须从其Enumerable类中调用它。

Here is an example of how to call Max through expression tree: 这是如何通过表达式树调用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));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM