为最近项目的日志 table 排序一对多关系
Sorting a 1-to-many relationship for a log table for the most recent item
所以我有一个 table,设备,其中包含资产。有一个 table 以一对多的关系连接到这个,记录每次清点、由谁以及清点的位置。它历史地存储这些,以便他们可以回过头来找出谁在什么时候做了什么。我的问题是,当我尝试按 InventoryDate 排序时,我在 table 上显示最后一个库存日期并希望按服务器端排序。
我使用 jquery datatables 来显示数据,并尝试根据对排序箭头的点击对 asc/desc 进行排序。错误是:
'DbSortClause 表达式必须具有顺序可比的类型。
参数名称:key'
int totalDBRecords = eqp.Count();
int filteredDBRecords = totalDBRecords;
int pageSize = dtp.length != -1 ? dtp.length : totalDBRecords;
int skip = dtp.start;
string search = dtp.search.value;
string dir = dtp.order[0].dir.ToUpper();
//string orderFilter;
IQueryable<eqp> orderFilter;
IQueryable<eqp> pageData = eqp;
//Column Sorting
switch (dtp.columns[dtp.order[0].column].name)
{
case "c1":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c1)).ThenBy(d => d.c1) : pageData.OrderByDescending(d => d.c1);
break;
case "c2":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c2)).ThenBy(d => d.c2) : pageData.OrderByDescending(d => d.c2);
break;
case "c3":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c3)).ThenBy(d => d.c3) : pageData.OrderByDescending(d => d.c3);
break;
case "c4":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c4)).ThenBy(d => d.c4) : pageData.OrderByDescending(d => d.c4);
break;
case "c5":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c5)).ThenBy(d => d.c5) : pageData.OrderByDescending(d => d.c5);
break;
default:
orderFilter = pageData.OrderByDescending(d => d.c3).ThenByDescending(n => n.c0);
break;
}
pageData = orderFilter;
...
...
filteredDBRecords = pageData.Count();
pageData = pageData.Skip(skip).Take(pageSize);
...
return new JsonResult() { Data = model, MaxJsonLength = Int32.MaxValue };```
首先,声明如下:
IQueryable<Equipment> orderFilter;
IQueryable<Equipment> pageData = equipment;
pageData = orderFilter;
...实际上什么都不做,它们只是让代码更难阅读。设置对彼此的引用不会像 copy/preserve 这些引用指向的数据那样做任何事情。这通常还会导致代码“忘记”它实际使用的引用的错误,并且链式操作最终会相互覆盖,从而导致意外结果。
这段代码也没什么意义:
pageData.OrderBy(d => string.IsNullOrEmpty(d.Serial)).ThenBy(d => d.Serial) :
pageData.OrderByDescending(d => d.Serial)
您试图告诉 EF 在实体上订购 string.IsNullOrEmpty()
而不是 属性。事实上,您的实体 中的字符串可能 为 null 并且可能需要特别考虑到订购中,这与您所写的不同。
通常对于条件排序,您会使用类似的东西:
switch (dtp.columns[dtp.order[0].column].name)
{
case "Serial":
employees = dir == "ASC"
? employees.OrderBy(d => d.Serial)
: employees.OrderByDescending(d => d.Serial);
break;
// ...
}
如果你想处理#null 序列值首先或最后等等,那么:
? employees.OrderBy(d => d.Serial ?? "00000")
: employees.OrderByDescending(d => d.Serial ?? "00000");
...用合适的默认值替换“00000”。
下一个细节可能是您希望能够根据多个排序标准进行排序,而您的示例仅考虑第一个排序标准。这有点棘手,因为您需要在第一个条件下 OrderBy
然后在所有后续条件下 ThenBy
。有一个稍微 hacky work-around 使这个简单:
employees = employees.OrderBy(e => 0); // Does nothing, but allows us to `ThenBy` our multiple conditions.
foreach ( var condition in dtp.order)
{
var isAscending = condition.dir.ToUpper() == "ASC";
switch (dtp.columns[condition.column].name)
{
case "Serial":
employees = isAscending
? employees.ThenBy(d => d.Serial)
: employees.ThenByDescending(d => d.Serial);
break;
// ...
}
}
这样您就可以传递多个条件并将排序附加到 IQueryable
。
所有 linq OrderBy
方法以动态方式重用都很烦人。
首先,我建议将查询投影到您显示的结果中。特别是为了在尝试对结果进行排序之前计算 d.GovEquipmentInvLogs.Max(x => x.InventoryDate)
一次。
然后我会引入一个扩展方法来根据参数调用.[Order/Then]By[Descending]
方法;
public IOrderedQueryable<T> Order<T, V>(this IQueryable<T> query, Expression<Func<T, V>> key, bool then, bool desc)
=> (desc)
? (then ? ((IOrderedQueryable<T>)query).ThenByDescending(key) : query.OrderByDescending(key))
: (then ? ((IOrderedQueryable<T>)query).ThenBy(key) : query.OrderBy(key));
现在您可以大大整理您的 switch 语句。尽管您也可以消除它,但如果您希望求助于动态创建表达式树来识别每个 属性。但这是一个单独的 SO 问题,已经有了答案。
所以我有一个 table,设备,其中包含资产。有一个 table 以一对多的关系连接到这个,记录每次清点、由谁以及清点的位置。它历史地存储这些,以便他们可以回过头来找出谁在什么时候做了什么。我的问题是,当我尝试按 InventoryDate 排序时,我在 table 上显示最后一个库存日期并希望按服务器端排序。
我使用 jquery datatables 来显示数据,并尝试根据对排序箭头的点击对 asc/desc 进行排序。错误是:
'DbSortClause 表达式必须具有顺序可比的类型。 参数名称:key'
int totalDBRecords = eqp.Count();
int filteredDBRecords = totalDBRecords;
int pageSize = dtp.length != -1 ? dtp.length : totalDBRecords;
int skip = dtp.start;
string search = dtp.search.value;
string dir = dtp.order[0].dir.ToUpper();
//string orderFilter;
IQueryable<eqp> orderFilter;
IQueryable<eqp> pageData = eqp;
//Column Sorting
switch (dtp.columns[dtp.order[0].column].name)
{
case "c1":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c1)).ThenBy(d => d.c1) : pageData.OrderByDescending(d => d.c1);
break;
case "c2":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c2)).ThenBy(d => d.c2) : pageData.OrderByDescending(d => d.c2);
break;
case "c3":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c3)).ThenBy(d => d.c3) : pageData.OrderByDescending(d => d.c3);
break;
case "c4":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c4)).ThenBy(d => d.c4) : pageData.OrderByDescending(d => d.c4);
break;
case "c5":
orderFilter = dir == "ASC" ? pageData.OrderBy(d => string.IsNullOrEmpty(d.c5)).ThenBy(d => d.c5) : pageData.OrderByDescending(d => d.c5);
break;
default:
orderFilter = pageData.OrderByDescending(d => d.c3).ThenByDescending(n => n.c0);
break;
}
pageData = orderFilter;
...
...
filteredDBRecords = pageData.Count();
pageData = pageData.Skip(skip).Take(pageSize);
...
return new JsonResult() { Data = model, MaxJsonLength = Int32.MaxValue };```
首先,声明如下:
IQueryable<Equipment> orderFilter;
IQueryable<Equipment> pageData = equipment;
pageData = orderFilter;
...实际上什么都不做,它们只是让代码更难阅读。设置对彼此的引用不会像 copy/preserve 这些引用指向的数据那样做任何事情。这通常还会导致代码“忘记”它实际使用的引用的错误,并且链式操作最终会相互覆盖,从而导致意外结果。
这段代码也没什么意义:
pageData.OrderBy(d => string.IsNullOrEmpty(d.Serial)).ThenBy(d => d.Serial) :
pageData.OrderByDescending(d => d.Serial)
您试图告诉 EF 在实体上订购 string.IsNullOrEmpty()
而不是 属性。事实上,您的实体 中的字符串可能 为 null 并且可能需要特别考虑到订购中,这与您所写的不同。
通常对于条件排序,您会使用类似的东西:
switch (dtp.columns[dtp.order[0].column].name)
{
case "Serial":
employees = dir == "ASC"
? employees.OrderBy(d => d.Serial)
: employees.OrderByDescending(d => d.Serial);
break;
// ...
}
如果你想处理#null 序列值首先或最后等等,那么:
? employees.OrderBy(d => d.Serial ?? "00000")
: employees.OrderByDescending(d => d.Serial ?? "00000");
...用合适的默认值替换“00000”。
下一个细节可能是您希望能够根据多个排序标准进行排序,而您的示例仅考虑第一个排序标准。这有点棘手,因为您需要在第一个条件下 OrderBy
然后在所有后续条件下 ThenBy
。有一个稍微 hacky work-around 使这个简单:
employees = employees.OrderBy(e => 0); // Does nothing, but allows us to `ThenBy` our multiple conditions.
foreach ( var condition in dtp.order)
{
var isAscending = condition.dir.ToUpper() == "ASC";
switch (dtp.columns[condition.column].name)
{
case "Serial":
employees = isAscending
? employees.ThenBy(d => d.Serial)
: employees.ThenByDescending(d => d.Serial);
break;
// ...
}
}
这样您就可以传递多个条件并将排序附加到 IQueryable
。
所有 linq OrderBy
方法以动态方式重用都很烦人。
首先,我建议将查询投影到您显示的结果中。特别是为了在尝试对结果进行排序之前计算 d.GovEquipmentInvLogs.Max(x => x.InventoryDate)
一次。
然后我会引入一个扩展方法来根据参数调用.[Order/Then]By[Descending]
方法;
public IOrderedQueryable<T> Order<T, V>(this IQueryable<T> query, Expression<Func<T, V>> key, bool then, bool desc)
=> (desc)
? (then ? ((IOrderedQueryable<T>)query).ThenByDescending(key) : query.OrderByDescending(key))
: (then ? ((IOrderedQueryable<T>)query).ThenBy(key) : query.OrderBy(key));
现在您可以大大整理您的 switch 语句。尽管您也可以消除它,但如果您希望求助于动态创建表达式树来识别每个 属性。但这是一个单独的 SO 问题,已经有了答案。