Entity Framework Core - 计算字段排序

Posted

技术标签:

【中文标题】Entity Framework Core - 计算字段排序【英文标题】:Entity Framework Core - Sorting on Calculated Field 【发布时间】:2021-02-02 16:43:28 【问题描述】:

我有一个类似的模型:

Public class Item 
  public int Id  get; set; 
  public string Name  get; set; 
  public DateTime? StartTime  get; set; 
  public DateTime? EndTime  get; set; 
  public int? TotalTime
  
    get
    
      return Convert.ToInt32(EndTime.Value - StartTime.Value).TotalMinutes);
    
  
  etc...

然后我得到一个项目列表:

IQueryable<Items> items = itemsContext.Items.Where(e => e.Id > 0);

我做了一些计算,运行了一些其他代码,最后我想返回按 Name ASC 和 TotalTime DESC 排序的 Items 列表。这是我尝试过的:

items = items.OrderBy(e => e.Name).ThenByDescending(e => e.TotalTime);

但我得到一个错误:“无法翻译 LINQ 表达式。要么以可翻译的形式重写查询,要么通过插入对 AsEnumerable()、AsAsyncEnumerable 的调用显式切换到客户端评估()、ToList() 或 ToListAsync()。”

我也试过这个,但得到同样的错误:

items = items.OrderBy(e => e.Name).ThenByDescending(e => e.TotalTime ?? 0);

如何按 TotalTime 对我的数据集进行排序?

【问题讨论】:

不要那样做。 TotalTime 属性无法计算一个服务器,因为 EF 看不到表达式树,只是属性调用。隐藏在属性 getter 下的代码。所以 EF 不能翻译这个表达式, 顺便说一句,避免使用Convert.ToInt32,因为它不能让您控制错误处理。您可以安全地将TimeSpan.TotalMinutes 直接转换为Int32 对于此类记录内计​​算,将计算列添加到数据库并将其映射到计算后的 EF 模型始终是最佳选择。 【参考方案1】: 由于TotalTime 不属于您的数据库,因此您需要使用[NotMapped] 对其进行注释。 那么您需要先加载项目并然后按它们排序。 但是,如果您想在数据库服务器中执行分页或排序,则需要在查询中直接使用 EndTimeStartTime

方法一:先加载,再在应用代码中排序:

List<Items> loadedItems = await itemsContext.Items
    .Where( e => e.Id > 123 )
    .ToListAsync()
    .ConfigureAwait(false); // `ConfigureAwait(false)` MAY be optional, depending on your application.

List<Items> sortedItems = loadedItems
    .OrderBy(e => e.Name)
    .ThenByDescending(e => e.TotalTime)
    .ToList(); // <-- Note this is NOT `ToListAsync` because the items are already loaded into memory.

方法二:在 SQL 中排序:

List<Items> loadedSortedItems = await itemsContext.Items
    .Where( e => e.Id > 123 )
    .OrderBy( e => e.Name )
    .ThenByDescending( e => e.EndTime - e.StartTime )
    .ToListAsync()
    .ConfigureAwait(false);

对你的班级的改变

我会把你的类修改成这样:

public class Item 
  public int       Id         get; set; 
  public string    Name       get; set; 
  public DateTime? StartTime  get; set; 
  public DateTime? EndTime    get; set; 
  
  [NotMapped]
  public int? TotalTimeMinutes
  
    get
    
        if( this.EndTime == null || this.StartTime == null ) return null;

        TimeSpan diff = this.EndTime.Value - this.StartTime.Value;
        return (Int32)Math.Round( diff.TotalMinutes );
    
  
NotMapped 属性向 Entity Framework 表明它应该忽略该属性。 注意属性如何包含单位名称(“分钟”),否则如果属性是毫秒、秒、分钟等,则不明显。 还要注意属性如何检查StartTimeEndTime 是否为空并在这种情况下返回null。您的代码没有,这意味着如果数据库中有 NULL 值或任何 Item 对象实例未完全初始化,您的程序将崩溃。 如果StartTimeEndTime 的值不能NULL,那么您需要将NOT NULL 添加到您的数据库并更新您的EF 实体类,以便属性为DateTime 而不是DateTime?

【讨论】:

好的。谢谢。如果您没有 getter 和 setter 两者,则 [NotMapped] 不是必需的另外,感谢有关时间计算的说明。我的意思是写一个简单的例子,它很容易理解并且代表了一个更复杂的情况。我通过在尝试排序之前先调用 ToList() 解决了我的问题。 第二种方法不正确 @HamedHajiloo 请解释为什么你觉得它不正确。【参考方案2】:

您应该将 SQL DateDiff 方法转换为 EF Core。 对于这种方法,您可以使用以下方法。

EF.Functions.DateDiffMinute(c.StartDate, c.EndDate ?? DateTime.Now)

【讨论】:

以上是关于Entity Framework Core - 计算字段排序的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 快速开始

EF6 System.Data.Entity.Core.EntityKey 在 Entity Framework Core 中的等价物是啥?

张高兴的 Entity Framework Core 即学即用:创建第一个 EF Core 应用

Entity Framework Core 迁移命令

Entity Framework Core - 计算字段排序

Entity Framework Core 性能优化