【问题标题】:C#, LINQ a generic sort method to sort a list by object properties and nested propertiesC#,LINQ 一种通用排序方法,用于按对象属性和嵌套属性对列表进行排序
【发布时间】:2019-05-23 10:03:36
【问题描述】:

我的 EF 模型中有一个名为 User 的实体:

public class User
{
    public int UserId { get; set; }
    public Branch HomeLocation{ get; set; }   
    public string CellPhone { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public string Email { get; set; } 
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserCode { get; set; }
}

分支是模型中的另一个实体:

public class Branch
{    
    public int BranchId { get; set; }
    public string BranchName{ get; set; }
    public string Address { get; set; } 
}

我的要求是获取用户列表并将其显示在网格上,然后按某些列对列表进行排序(一次一个)。例如,按用户名、名字、姓氏和 HomeLocation 排序。按homelocation排序时,应按分支名称排序。

我有很多像这样显示其他数据的网格。所以我想开发一个通用的排序机制,我已经使用谷歌中的一些例子来实现它,例如this one

public class GenericSorter<T>
{
    public IEnumerable<T> Sort(IEnumerable<T> source, string sortBy, string sortDirection)
    {
        var param = Expression.Parameter(typeof(T), "item");

        var sortExpression = Expression.Lambda<Func<T, object>>
            (Expression.Convert(Expression.Property(param, sortBy), typeof(object)), param);

        switch (sortDirection.ToLower())
        {
            case "asc":
                return source.AsQueryable<T>().OrderBy<T, object>(sortExpression);
            default:
                return source.AsQueryable<T>().OrderByDescending<T, object>(sortExpression);

        } 
    }
}

但是,按家庭位置排序会失败,因为它需要按用户实体的内部属性进行排序。我也尝试过使用Dynamic LINQ library,但没有运气。

更新:请注意,我必须对 List 进行排序,而不是 IQueryable,因为我的列表包含使用 AE 加密的字段,这些字段不支持 DB 级排序。

谁能告诉我如何从内部属性实现动态排序?

更新2:我按照示例并使用扩展方法实现了排序,这就是它在我的列表中的应用方式:

var users = (from u in context.Users.Include("Branch")
                    where (u.FkBranchId == branchId || branchId == -1) && u.IsActive
                        && (searchTerm == string.Empty || (u.FirstName.Contains(searchTerm) || u.LastName.Equals(searchTerm)
                            || u.UserName.Contains(searchTerm) || u.UserCode.Contains(searchTerm)))
                    select u).ToList();

        var rowCount = users.Count;

        var orderedList = users.OrderBy(sortInfo.SortColumn).Skip(pageInfo.Skip).Take(pageInfo.PageSize).ToList();

但我收到以下错误: “System.Linq.Expressions.Expression1[System.Func2[ClientData.User,System.String]]”类型的对象无法转换为“System.Func`2[ClientData.User,System.String]”类型。

以下原因引发错误:

object result = typeof(Enumerable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda });

在此之后,我收到以下错误,在某些情况下,如评论中所述:

【问题讨论】:

  • 为什么在 Linq 中这样做,让网格为你处理排序?
  • 嗯,这是一个使用 ASP.NET GridView 的遗留应用程序,我们正在重写,对现有实现的改动很小
  • 你已经检查过这个了吗? stackoverflow.com/a/233505/3470596
  • 不,我会试试的,谢谢。
  • 如果我理解你的话,只需在代码中通过 Enumerable 更改 Queryable 就可以了。

标签: linq sorting dynamic-queries nested-properties


【解决方案1】:

改编来自@MarcGravell 找到的here 的代码。

public static class EnumerableExtensions
{
    public static IOrderedEnumerable<T> OrderBy<T>(
        this IEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "OrderBy");
    }

    public static IOrderedEnumerable<T> OrderByDescending<T>(
        this IEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "OrderByDescending");
    }

    public static IOrderedEnumerable<T> ThenBy<T>(
        this IOrderedEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "ThenBy");
    }

    public static IOrderedEnumerable<T> ThenByDescending<T>(
        this IOrderedEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "ThenByDescending");
    }

    static IOrderedEnumerable<T> ApplyOrder<T>(
        IEnumerable<T> source,
        string property,
        string methodName)
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach (string prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

        object result = typeof(Enumerable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda.Compile() });
        return (IOrderedEnumerable<T>)result;
    }
}

更新

从列表中使用它:

var list = new List<MyModel>();
list = list.OrderBy("MyProperty");

【讨论】:

  • 如何在实现类之外访问这个 OrderBy?在该示例中,实现位于 Program 类中,并在 Main() 方法中访问。
  • 放到扩展类中,请看我更新的答案
  • 我用给定的解决方案后出现的另一个错误更新了问题。
  • 哦,是的,那是因为它被封装在一个Expression中,尝试编译它。 .Invoke(null, new object[] { source, lambda.Compile() });,我会更新我的答案。
  • 谢谢,到目前为止一切都很好,但是在某些情况下我会遇到空引用异常,例如 - 当排序列具有空值或空值时。错误的来源是“匿名托管的 DynamicMethods 程序集”。原始线程中附加的屏幕截图。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-02
  • 2020-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
相关资源
最近更新 更多