我有一个编码问题要解决,我有点知道我应该遵循的路径,但仍然缺少一些东西。我的任务是修改代码,使其更通用,减少冗余。
整个类给出如下。这似乎有点愚蠢,问题是冗余本身。
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;
}
}
}
}
}
据我所知,冗余是在每个开关情况下重复相同的代码,只改变要排序的列。
我的意图是将其更改为单行,使用Reflection将我的实体的属性名称与列名进行比较,然后替换这一行的switch-case块。但是表达式不允许这样做。就像下面这行,因为它是错误的,所以被注释了。
//query = orderDescending ? query.OrderByDescending(x =>
//x.GetType().GetProperties() : query.OrderBy(x => x.Name);
我很感激你的建议。
许多谢谢。
您的代码有几个问题。首先,在动态添加要排序的列时保持强类型需要在方法中隐藏列选择lambda,以便无论列的类型如何,都具有一致的签名。其次,LINQ排序要求OrderBy
后面跟着ThenBy
进行链排序,这意味着您有另一个类型问题,因为ThenBy
期望IOrderedQueryable<T>
,因此分配给query
将不起作用。第三,在方法返回后,给query
赋值不会改变任何东西,因为它是AddSorting
方法的一个参数,而OrderBy
和ThenBy
方法返回的新对象将在方法返回时丢失。
因此,为了解决这些问题,首先我们可以创建一些辅助字典,将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
,并返回一个lambda,该lambda将IQueryable<T>
或IOrderedQueryable<T>
映射到相应列的IOrderedQueryable<T>
:
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
树似乎有些多余,但是,您可以使用反射从列的类型和列表中填充firstSort
和thenSort
字典,而不是对它们的初始值进行编码。您需要为每种可能的列类型编写适当的OrderBy
和ThenBy
映射。