将此LINQ转换为动态表达式树

时间:2016-05-10 17:27:31

标签: c# linq lambda expression-trees

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

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

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

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

我可以按照以下方式对我的列表进行排序:

var myList = db.MyModel.Where(m => m.IsActive);
myList
    .OrderBy(m => m.MyChildren
        .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);

或:

var myList = db.MyModel.Where(m => m.IsActive);
myList.OrderBy(m => m.MyChildren.Max(c => c.SavedDate);

但我希望能够根据用户选项动态排序。所以我想这样:

var myList = db.MyModel.Where(m => m.IsActive);
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('.'))
            {
                var checkIfCollection = propertyGetter.Type.GetInterfaces()//(typeof (ICollection<>).FullName);
                    .Any(x => x.IsGenericType &&
                        (x.GetGenericTypeDefinition() == typeof(ICollection<>) || x.GetGenericTypeDefinition() == typeof(IEnumerable<>)));

                if (checkIfCollection)
                {
                    var pgType = propertyGetter.Type;
                    var childType = pgType.GetGenericArguments().Single();
                    var childProp = childType.GetProperty(propertyPart);

                    ParameterExpression childInParam = Expression.Parameter(childType, "c");
                    var propertyAccess = Expression.Property(childInParam, childProp);                      
                    var orderByExp = Expression.Lambda(propertyAccess, childInParam);
                    // At this point, orderByExp is c => c.ActionDate

                    // Now I want to build the expression tree to handle the order by
                    XXXXX This is where I need help.
                }
                else
                {
                    // This handles a singular property. Like "MyChildren.ChildName"
                    // and this part does work
                    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)
            {
                // This would be like 
                //  list.OrderByDescending(m => m.MyChildren
                //      .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
                return list.OrderByDescending(getter);
            }
            else
            {
                // This would be like 
                //  list.OrderBy(m => m.MyChildren
                //      .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
                return list.OrderBy(getter);
            }
        }

        return list;
    }
}

1 个答案:

答案 0 :(得分:1)

基本上你应该使用以下Expression.Call重载,它允许你构建一个表达式来调用静态泛型方法(所有的LINQ扩展方法都是如此)。

构建像这样的表达式的等价物

m => m.MyChildren.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate

您可以使用以下代码段:

// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
    typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
    propertyGetter, orderByExp
);
var firstOrDefaultCall = Expression.Call(
    typeof(Enumerable), "FirstOrDefault", new Type[] { childType },
    orderByDescendingCall
);
propertyGetter = Expression.Property(firstOrDefaultCall, childProp);

但请注意,如果收集为空,您将获得NRE。

所以你最好建立一个这样的表达式:

m => m.MyChildren.OrderByDescending(c => c.SavedDate)
   .Select(c => (DateTime?)c.SavedDate).FirstOrDefault()

使用:

// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
    typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
    propertyGetter, orderByExp
);
Expression propertySelector = propertyAccess;
// If value type property and not nullable, convert it to nullable
if (propertySelector.Type.IsValueType && Nullable.GetUnderlyingType(propertySelector.Type) == null)
    propertySelector = Expression.Convert(propertySelector, typeof(Nullable<>).MakeGenericType(propertySelector.Type));
var selectCall = Expression.Call(
    typeof(Enumerable), "Select", new Type[] { childType, propertySelector.Type },
    orderByDescendingCall, Expression.Lambda(propertySelector, childInParam)
);
propertyGetter = Expression.Call(
    typeof(Enumerable), "FirstOrDefault", new Type[] { propertySelector.Type },
    selectCall
);