实体框架 - C# LINQ 复合排序在 3 列上
本文关键字:排序 列上 复合 LINQ 框架 实体 | 更新日期: 2023-09-27 17:59:59
我有一个包含许多字段的对象,但我想对 3 列(布尔值 [IsValid]、int [计数] 和日期 [CreateDate](进行排序,有条件地依赖于布尔值。
数据:
|| ID || IsValid || Count || CreateDate ||
==========================================
|| 1 || True || 3 || 2016-05-01 ||
|| 2 || True || 2 || 2016-07-12 ||
|| 3 || False || NULL || 2015-06-16 ||
|| 4 || False || 1 || 2015-01-01 ||
我想要的顺序是:1( 有效项目优先2(如果有效,则按计数排序,否则按创建日期排序(基本上是(o.IsValid) ? o.Count : o.CreateDate
,但这不起作用(,因此顺序将是
|| ID || IsValid || Count || CreateDate ||
==========================================
|| 2 || True || 2 || 2016-07-12 ||
|| 1 || True || 3 || 2016-05-01 ||
|| 4 || False || 1 || 2015-01-01 ||
|| 3 || False || NULL || 2015-06-16 ||
如果某些内容曾经有效,则可以再次失效,但不会重置计数。
我尝试过做这样的事情:
db.OrderBy(o => new { o.IsValid, o.Count }).ThenBy(o => new { o.IsValid, o.CreateDate })
或
db.OrderByDescending(o => o.IsValid).ThenBy(o => new { o.SortOrder, o.CreateDate })
和其他组合,但我不知道如何让订单按预期工作。
有人能帮忙吗?
由于 LINQ 查询是延迟计算的,因此可以使用连接在一起的几个不同查询来完成此操作。我不确定这对你来说有多高效,但它实现了你的目标。
class Program
{
static void Main(string[] args)
{
IEnumerable<OrderObject> db = new List<OrderObject>
{
new OrderObject { Count=1, CreateDate=DateTime.Now.Subtract(TimeSpan.FromDays(0)), IsValid=true },
new OrderObject { Count=2, CreateDate=DateTime.Now.Subtract(TimeSpan.FromDays(1)), IsValid=false },
new OrderObject { Count=3, CreateDate=DateTime.Now.Subtract(TimeSpan.FromDays(2)), IsValid=false },
new OrderObject { Count=4, CreateDate=DateTime.Now.Subtract(TimeSpan.FromDays(3)), IsValid=true },
new OrderObject { Count=5, CreateDate=DateTime.Now.Subtract(TimeSpan.FromDays(4)), IsValid=false },
};
var validItemsOrderedByCount = (from obj in db
where obj.IsValid
orderby obj.Count
select obj);
var nonValidItemsOrderedByDateCreated = (from obj in db
where obj.IsValid == false
orderby obj.CreateDate
select obj);
var combinedList = validItemsOrderedByCount
.Concat(nonValidItemsOrderedByDateCreated)
.ToList();
}
}
class OrderObject
{
public bool IsValid { get; set; }
public DateTime CreateDate { get; set; }
public int Count { get; set; }
}
或者,如果您更喜欢 LINQ 方法语法,这应该可以工作。
var validItemsMethodSyntax = db.Where(x => x.IsValid).OrderBy(x => x.Count);
var nonValidItemsMethodSyntax = db.Where(x => x.IsValid == false).OrderBy(x => x.CreateDate);
var combinedMethodSyntax = validItemsMethodSyntax
.Concat(nonValidItemsMethodSyntax)
.ToList();
或者根据要求对一个变量使用Union
和方法语法。
var usingUnion = db.Where(x => x.IsValid)
.OrderBy(x => x.Count)
.Union(db.Where(x => x.IsValid == false).OrderBy(x => x.CreateDate))
.ToList();
我能建议的最简单的是:
db.OrderByDescending(o => o.IsValid)
.ThenBy(o => o.IsValid ? DbFunctions.AddSeconds(DateTime.MinValue, o.Count) : o.CreateDate)
这个想法是将CreateDate
和Count
列转换为相同的类型,然后根据其值进行排序。对于 linq 到对象,我会将两者转换为积分表示:
.ThenBy(o => o.IsValid ? o.Count : o.CreateDate.Ticks)
但这不适用于 EF,因为它无法将.Ticks
映射到 SQL 查询。我也没有找到将datetime
表示为整数值的优雅方法,因此,另一种方法是将现有的整数值转换为datetime
。现在,我们将两列都表示为datetime
,并且可以轻松地按其值进行排序。
更新
刚刚做了一些测试,发现DbFunctions.AddSeconds(DateTime.MinValue, o.Count)
表示为 datetime2
,这可能小于 1753 年 1 月 1 日(datetime
的最小值(。此表达式的值在范围[01.01.01 00:00:00 .. 19.01.0069 3:14:07]
但受.Count >= 0
限制,因为负值会产生溢出错误。
让我们支持.Count
的负值:
DbFunctions.AddSeconds(new DateTime(70, 1, 1), o.Count)
这个表达式的值落在范围 [13.12.0001 20:45:53 .. 20.01.0138 3:14:07]
中,这肯定小于.CreateDate
值(我猜,您的最小值.CreateDate
是当前或上个世纪,即 .CreateDate
比20.01.138
大得多,直到你在古代编写软件(。所以这就是为什么我们可以扔掉.OrderByDescending(o => o.IsValid)
。
您的问题的最终答案是order by
:
db.OrderBy(o => o.IsValid
? DbFunctions.AddSeconds(new DateTime(70, 1, 1), o.Count)
: o.CreateDate)
有效值将首先出现,因为它们具有较少的datetime2
值,然后是无效值。有效值将按升序排序.Count
。
但是,您可以轻松管理订单方向以及谁先走,有效或无效。例如,将 .Count
的基准日期年份更改为 7000
而不是 70
,将.Count
乘以 -1
等。
您可以使用三元运算符执行此操作,如注释中提到的juharr:
db.ToList()
.OrderBy(e => e.IsValid)
.ThenBy(e => e.IsValid ? e.Count : 0)
.ThenBy(e => e.IsValid ? DateTime.MinValue : e.CreateDate);
请记住,如果要执行任何筛选,请在.ToList()
之前进行,以最大程度地减少从数据层返回的结果。