实体框架 - 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 })

和其他组合,但我不知道如何让订单按预期工作。

有人能帮忙吗?

实体框架 - C# LINQ 复合排序在 3 列上

由于 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)

这个想法是将CreateDateCount列转换为相同的类型,然后根据其值进行排序。对于 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是当前或上个世纪,即 .CreateDate20.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()之前进行,以最大程度地减少从数据层返回的结果。