【问题标题】:Convert this LINQ to dynamic Expression tree将此 LINQ 转换为动态表达式树
【发布时间】:2016-05-11 02:50:18
【问题描述】:

我的项目中有一个扩展,可以让我用字符串对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;
    }
}

【问题讨论】:

标签: c# linq lambda expression-trees


【解决方案1】:

基本上,您应该使用以下Expression.Call 重载,它允许您构建用于调用静态泛型方法的表达式(什么是所有 LINQ 扩展方法)。

像这样构建等价的表达式

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

你可以使用下面的sn-p:

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

【讨论】:

  • 我正在使用 LINQPad 来解决这个问题。使用您的 Expression.Call,我收到消息 'System.Linq.Enumerable' 类型上的没有通用方法 'OrderByDescending' 与提供的类型参数和参数兼容。我错过了使用条款吗? childTypeorderByExp.Body.Type 都是 DateTimepropertyGetter.TypeSystem.Collections.Generic.ICollection'1[PublicationSystem.Model.ActionItem]
  • childType 应该是 ChildModel。您的示例类不正确,MyChildren 应为IEnumerable&lt;ChildModel&gt;,或ICollection&lt;ChildModel&gt;List&lt;ChildModel&gt;,以使您的手动表达式示例甚至可以编译。
  • 我使用了错误的变量来获取类型。我现在正在取得进展。
猜你喜欢
  • 2015-06-26
  • 2010-12-04
  • 1970-01-01
  • 2020-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多