【问题标题】:Finding a property name within a lambda expression [duplicate]在 lambda 表达式中查找属性名称
【发布时间】:2021-12-09 10:23:18
【问题描述】:

我有一个编码问题要解决,我知道我应该遵循的路径,但仍然缺少一些东西。我的任务是更改代码以使其更通用并减少冗余。

整个课程如下。看起来有点假,问题是冗余本身。

using System; 
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace TestRedundance
{
    public class EntityRedundance
    {
        public class Entity
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
            public DateTime ValidFrom { get; set; }
        }

        private void AddSorting(IQueryable<Entity> query, List<string> sortingColumns, bool orderDescending)
        {

            if (!sortingColumns.Any())
            {
                query = query.OrderBy(x => x.Name);
                return;
            }

            foreach (var column in sortingColumns)
            {

                switch (column)
                {

                    case "Name":
                        query = orderDescending ? query.OrderByDescending(x => x.Name) : query.OrderBy(x => x.Name);
                        break;
                    case "Description":
                        query = orderDescending ? query.OrderByDescending(x => x.Description) : query.OrderBy(x => x.Description);
                        break;
                    case "ValidFrom":
                        query = orderDescending ? query.OrderByDescending(x => x.ValidFrom) : query.OrderBy(x => x.ValidFrom);
                        break;

                }
            }
        }
    }
}

据我了解,冗余在于在每个 switch case 条件中重复相同的确切代码,只更改要排序的列。

我的意图是将其更改为单行,将我的实体的属性名称与列名称进行比较,使用反射,然后替换该单行的 switch-case 块。但是 lambda 表达式不允许我这样做。类似于下面的行,因为它的 sintax 错误而被注释。

//query = orderDescending ? query.OrderByDescending(x => 
//x.GetType().GetProperties() : query.OrderBy(x => x.Name);

如果有任何建议,我将不胜感激。

非常感谢。

【问题讨论】:

  • 您的 lambda 表达式只是 Func&lt;Entity, T&gt; where T : [string | DateTime]。如果不出意外,您可以为它们编写一个选择器。
  • 请注意,您的代码不正确 - 如果您使用多个 OrderBy 链接,那么最后一个是唯一应用的排序。如果您希望在 LINQ 中进行链式排序,则必须使用 ThenBy

标签: c# lambda reflection


【解决方案1】:

您的代码存在一些问题。首先,在动态添加要排序的列的同时保留强类型需要在方法中隐藏列选择 lambda,以便您拥有一致的签名,而不管列的类型如何。其次,LINQ 排序要求OrderBy 后跟ThenBy 以进行链式排序,这意味着您还有另一个类型问题,因为ThenBy 需要IOrderedQueryable&lt;T&gt;,因此分配给query 将不起作用。第三,在方法返回后分配给query 不会改变任何内容,因为这是AddSorting 方法的参数,而OrderByThenBy 方法返回的新对象将在方法返回时丢失。

所以要解决这些问题,首先我们可以创建一些帮助字典来将bool (orderDescending) 映射到每种列的特定排序方法,一个用于OrderBy,一个用于ThenBy

static Dictionary<bool, Func<IQueryable<Entity>, Expression<Func<Entity, string>>, IOrderedQueryable<Entity>>> stringOrderByDirMap = new() {
    { false, Queryable.OrderBy<Entity, string> },
    { true, Queryable.OrderByDescending<Entity, string> },
};

static Dictionary<bool, Func<IOrderedQueryable<Entity>, Expression<Func<Entity, string>>, IOrderedQueryable<Entity>>> stringThenByDirMap = new() {
    { false, Queryable.ThenBy<Entity, string> },
    { true, Queryable.ThenByDescending<Entity, string> },
};

static Dictionary<bool, Func<IQueryable<Entity>, Expression<Func<Entity, DateTime>>, IOrderedQueryable<Entity>>> dateTimeOrderByDirMap = new() {
    { false, Queryable.OrderBy<Entity, DateTime> },
    { true, Queryable.OrderByDescending<Entity, DateTime> },
};

static Dictionary<bool, Func<IOrderedQueryable<Entity>, Expression<Func<Entity, DateTime>>, IOrderedQueryable<Entity>>> dateTimeThenByDirMap = new() {
    { false, Queryable.ThenBy<Entity, DateTime> },
    { true, Queryable.ThenByDescending<Entity, DateTime> },
};

使用这些,您可以创建几个字典(一个用于OrderBy,一个用于ThenBy)将列名映射到一个lambda函数,该函数接受bool并返回一个映射IQueryable&lt;T&gt;的lambda或 IOrderedQueryable&lt;T&gt;IOrderedQueryable&lt;T&gt; 对应的列:

static Dictionary<string, Func<bool, Func<IQueryable<Entity>, IOrderedQueryable<Entity>>>> firstSort = new() {
    { "Name", desc => q => stringOrderByDirMap[desc](q, e => e.Name) },
    { "Description", desc => q => stringOrderByDirMap[desc](q, e => e.Description) },
    { "ValidFrom", desc => q => dateTimeOrderByDirMap[desc](q, e => e.ValidFrom) },
};

static Dictionary<string, Func<bool, Func<IOrderedQueryable<Entity>, IOrderedQueryable<Entity>>>> thenSort = new() {
    { "Name", desc => q => stringThenByDirMap[desc](q, e => e.Name) },
    { "Description", desc => q => stringThenByDirMap[desc](q, e => e.Description) },
    { "ValidFrom", desc => q => dateTimeThenByDirMap[desc](q, e => e.ValidFrom) },
};

通过所有这些设置,您的 AddSorting 方法变为

private IQueryable<Entity> AddSorting(IQueryable<Entity> query, List<string> sortingColumns, bool orderDescending) {
    if (!sortingColumns.Any())
        return query.OrderBy(x => x.Name);

    IOrderedQueryable<Entity> sortedQuery = null;

    foreach (var column in sortingColumns) {
        if (sortedQuery == null)
            sortedQuery = firstSort[column](orderDescending)(query);
        else
            sortedQuery = thenSort[column](orderDescending)(sortedQuery);
    }
    
    return sortedQuery;
}

鉴于您的示例只有三列,使用反射构建 Expression 树似乎有点过头了,但是,您可以使用反射从列的类型和列表中填充 firstSortthenSort 字典,而不是对其进行编码初始值。您需要为每种可能的列类型编写适当的 OrderByThenBy 映射。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-21
    相关资源
    最近更新 更多