【问题标题】:Creating Sorting Func<IQueryable<T>, IOrderedQueryable<T>>?创建排序函数<IQueryable<T>, IOrderedQueryable<T>>?
【发布时间】:2013-10-18 04:14:35
【问题描述】:

我想在扩展方法中创建一个排序通用 Func Creating Func&lt;IQueryable&lt;T&gt;, IOrderedQueryable&lt;T&gt;&gt;

public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(this KeyValuePair<string, SortingType> keyValuePair)
{
    //I expect the following result
    //Func<IQueryable<T>, IOrderedQueryable<T>> orderby = q => q.OrderByDescending(c => c.Name);

    //keyValuePair.Key is name of property in type of T that I should sort T by it
    switch (keyValuePair.Value)
    {
        case SortingType.Ascending:
            // Creating Ascending Sorting Func
            break;

        case SortingType.Descending:
            // Creating Descending Sorting Func
            break;

        default :
            break;
    }
}

你能指导我吗,我该怎么做?

编辑:

此排序也包含 T 的导航属性的计数。
例如:

// keyValuePair.Key equals "User.Count"
// User is a navigation property of T
Func<IQueryable<T>, IOrderedQueryable<T>> orderby = q => q.OrderByDescending(c => c.User.Count);

编辑:

我将GetSelector改成如下,但bodyExpression出现异常。

public static Expression GetSelector<T>(string propertyName)
{
    ParameterExpression parameter = Expression.Parameter(typeof(T));
    if (propertyName.Contains("."))
    {
        propertyName = propertyName.Substring(0, propertyName.IndexOf("."));
        Type navigationPropertyCollectionType = typeof(T).GetProperty(propertyName).PropertyType;

        if (navigationPropertyCollectionType.GetGenericTypeDefinition() == typeof(ICollection<>))
        {                        
            Expression countParameter = Expression.Parameter(navigationPropertyCollectionType, "c");
            MemberExpression countExpression = Expression.Property(countParameter, "Count");
//Exception: Instance property 'Users(ICollection`1)' is not defined for type 'System.Int32'
            var bodyExpression = Expression.Property(countExpression, propertyName, countParameter);
            return Expression.Lambda(bodyExpression, parameter);
        }
    }
    MemberExpression bodyMemberExpression = Expression.Property(parameter, typeof(T).GetProperty(propertyName));
    return Expression.Lambda(bodyMemberExpression, parameter);
} 

【问题讨论】:

    标签: c# linq entity-framework func


    【解决方案1】:

    因此,我们首先需要一个方法,该方法可以获取选择器表达式,该选择器表达式在给定属性名称时选择该属性值。它需要从头开始构建表达式:

    public static Tuple<Expression, Type> GetSelector<T>(IEnumerable<string> propertyNames)
    {
        var parameter = Expression.Parameter(typeof(T));
        Expression body = parameter;
    
        foreach (var property in propertyNames)
        {
            body = Expression.Property(body,
                body.Type.GetProperty(property));
        }
    
        return Tuple.Create(Expression.Lambda(body, parameter) as Expression
            , body.Type);
    }
    

    请注意,由于这会产生一系列属性,因此该方法还返回最终属性的类型,因为从调用者的角度来看,这不是特别容易访问的信息。

    因为我们在调用 Selector 时不知道属性的返回类型 所以我们只好把这个方法的返回类型留给Expression。我们不能将其转换为 Expression&lt;Func&lt;T, Something&gt;&gt;。我们可以让它返回一个Expression&lt;Func&lt;T, object&gt;&gt;,这将适用于所有选择引用类型的属性,但这不能装箱值类型,因此在这些情况下会引发运行时异常。

    现在,由于我们不知道该表达式的确切类型,我们不能直接调用OrderByOrderByDescending。我们需要通过反射获取这些方法并使用MakeGenericMethod,以便可以根据使用反射检查该属性使用正确的类型来创建它们。

    public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(
        this Tuple<IEnumerable<string>, SortingType> sortCriteria)
    {
        var selector = GetSelector<T>(sortCriteria.Item1);
        Type[] argumentTypes = new[] { typeof(T), selector.Item2 };
    
        var orderByMethod = typeof(Queryable).GetMethods()
            .First(method => method.Name == "OrderBy"
                && method.GetParameters().Count() == 2)
                .MakeGenericMethod(argumentTypes);
        var orderByDescMethod = typeof(Queryable).GetMethods()
            .First(method => method.Name == "OrderByDescending"
                && method.GetParameters().Count() == 2)
                .MakeGenericMethod(argumentTypes);
    
        if (sortCriteria.Item2 == SortingType.Descending)
            return query => (IOrderedQueryable<T>)
                orderByDescMethod.Invoke(null, new object[] { query, selector.Item1 });
        else
            return query => (IOrderedQueryable<T>)
                orderByMethod.Invoke(null, new object[] { query, selector.Item1 });
    }
    

    【讨论】:

    • 感谢您的回复,但它不适用于排序导航属性计数。请参阅主要问题中的编辑部分。您能指导我如何做吗?
    • @Mohammad 那是因为您没有按名称对属性进行排序。您需要更改 GetSelector 方法,为所需的每个属性访问添加 Property 调用。
    • @Mohammad 您在此答案中看到的内容应该足以应对这种变化;您不需要任何未在此答案中使用的工具。只需将一个Property 调用的结果传递给另一个。
    • @Mohammad Expression.Property 的第一个参数是您从中获取属性的对象。这可能是参数,或者(例如)另一个属性调用的结果。
    • @Mohammad 查看我的编辑。简而言之,你试图处理你的一个例子的确切情况,而不是仅仅处理一般情况(老实说这更容易)。而不是以某种方式特殊的大小写“计数”,它只允许提供一系列属性访问。您甚至可以提供一个空数组并按“自身”排序。至于为什么它不起作用,我没有仔细看,但我对一个可能的问题的第一个猜测是你需要稍微修复另一个函数,以便它调用OrderBy泛型类型参数。
    【解决方案2】:

    您需要使用Expressions

        public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(this KeyValuePair<string, SortingType> keyValuePair)
        {
    
            Func<IQueryable<T>, IOrderedQueryable<T>> result;
    
            var p1 = Expression.Parameter(typeof (T), "p1");
            var prop = Expression.PropertyOrField(p1, keyValuePair.Key);
            var lambada = Expression.Lambda<Func<T, object>>(prop, new ParameterExpression[] {p1});
    
            //keyValuePair.Key is name of property in type of T that I should sort T by it
            switch (keyValuePair.Value)
            {
                case SortingType.Ascending:
                    result = source => source.OrderBy(lambada);
                    break;
    
                case SortingType.Descending:
                    result = source => source.OrderByDescending(lambada);
                    break;
    
                default:
                    throw new NotImplementedException();
                    break;
            }
    
            return result;
        }
    

    【讨论】:

    • 请注意,这仅在属性的返回值为引用类型时才有效。值类型不会使用此代码装箱到对象中。
    • 哎呀,我运行测试时没有注意到这一点。你如何解决这个问题?
    • 已经为此工作了一段时间。它使问题复杂化。您需要使用反射来调用OrderBy,以便可以将Type 对象作为通用参数传递。快完成了。
    • 我完成了;看我的回答。
    • @ScottChamberlain 感谢您的回复,请参阅主要问题中的编辑部分
    猜你喜欢
    • 1970-01-01
    • 2020-12-22
    • 1970-01-01
    • 2013-09-03
    • 2012-04-27
    • 2015-02-02
    • 2011-04-05
    • 2011-10-09
    相关资源
    最近更新 更多