【问题标题】:EF 3.1: Overcome LINQ GroupBy SQL translation problem [duplicate]EF 3.1:克服 LINQ GroupBy SQL 翻译问题 [重复]
【发布时间】:2020-01-24 08:42:02
【问题描述】:

在 MS SQL Server 中,我有一个表,其中包含呼叫联系人的历史记录(即另一个表)。 EF访问的实体如下:

public partial class CallbackHistory
{
    public int HistoryId { get; set; }
    public int CompanyId { get; set; }
    public int CallerId { get; set; }
    public DateTime LastCallTimeStamp { get; set; }

    public virtual CompanyDiary Caller { get; set; }
    public virtual Company Company { get; set; }
}

public partial class CompanyDiary
{
    public CompanyDiary()
    {
        DatiCallbackHistory = new HashSet<DatiCallbackHistory>();
    }
    public int CallerId { get; set; }
    public string NickName { get; set; }
    public string PhoneNumber { get; set; }
    public string Email { get; set; }
    public int CompanyId { get; set; }

    public virtual Company Company { get; set; }
    public virtual ICollection<CallbackHistory> CallbackHistory { get; set; }
}

我需要按日期降序获取最近 5 次呼叫单个号码的列表。

很遗憾,我想出了以下无法转换为 SQL 的查询:

var historyOfCalls = await
                    context.CallbackHistoryDbSet
                    .Include(historyEntry => historyEntry.Caller)
                    .Where(historyEntry => historyEntry.CompanyId == companyId)
                    .GroupBy(s => s.Caller.PhoneNumber)
                    .Select(s => s.OrderByDescending(historyEntry => historyEntry.LastCallTimeStamp).FirstOrDefault())
                    .Take(5)
                    .AsNoTracking()
                    .ToListAsync(cancellationToken).ConfigureAwait(false);

这是我得到的错误:

System.AggregateException
  HResult=0x80131500
  Message=One or more errors occurred. (The LINQ expression '(GroupByShaperExpression:
KeySelector: (c.PhoneNumber), 
ElementSelector:(EntityShaperExpression: 
    EntityType: CallbackHistory
    ValueBufferExpression: 
        (ProjectionBindingExpression: EmptyProjectionMember)
    IsNullable: False
)
)
    .OrderByDescending(historyEntry => historyEntry.LastCallTimeStamp)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.)
  Source=System.Private.CoreLib

Inner Exception 1:
InvalidOperationException: The LINQ expression '(GroupByShaperExpression:
KeySelector: (c.PhoneNumber), 
ElementSelector:(EntityShaperExpression: 
    EntityType: CallbackHistory
    ValueBufferExpression: 
        (ProjectionBindingExpression: EmptyProjectionMember)
    IsNullable: False
)
)
    .OrderByDescending(historyEntry => historyEntry.LastCallTimeStamp)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

问题似乎在于我正在对导航属性进行分组。

我可以重写此查询以使其可转换为 SQL 吗?

我不知道什么时候用这个查询切换到Linq to objects 因为我已经接到了ToListAsync 的电话。我试图在查询中的Select 之后移动它,但它没有编译

【问题讨论】:

  • 查询一开始就有问题。 LINQ 不能产生不能用 SQL 表示的东西。您想要的 SQL 查询是什么样的? Select(s =&gt; s.OrderByDescending(historyEntry =&gt; historyEntry.LastCallTimeStamp).FirstOrDefault()) 应该返回什么?您不能将ORDER BY 子句放在SELECT 子句中。您是否尝试模拟 T-SQL 的 FIRST() OVER(ORDER BY...)?这是一个报告查询,而 ORM不是为处理这些而构建的。
  • 另一个问题 - Select(s =&gt; s.OrderByDescending(historyEntry =&gt; historyEntry.LastCallTimeStamp).FirstOrDefault()) 返回一整行,因此您也无法使用 FIRST()。您是否尝试检索每个公司和客户的最新记录?在 T-SQL 中,您将使用计算排名的 CTE 来执行此操作,例如使用 ROWNUMBER() 和仅返回 RN=1 的行的外部查询。您无法在 LINQ 中表达这一点。 GROUP BY 也没有完成
  • 有时使用“其他”LINQ 语法更容易。示例(Northwind 数据库):var categoryId = 2; var productQuery = (from item in context.Categories let s = item.Products.OrderByDescending(h =&gt; h.ProductName).FirstOrDefault() where item.CategoryID == categoryId group item.Products by s.ProductName into g select g); 这是一个类似的查询,它使用let 语句允许从降序排序列表中选择第一项。尝试以类似的方式重新制定您的查询。
  • @PanagiotisKanavos Select 在这里返回整个记录。见here。我正在尝试按公司和不同的电话号码检索最新的 5 条历史记录。我使用 GroupBy 而不是 distinct,因为它可能更合适。
  • @OlivierMATROT 而这在 SQL 中是不可能的。要获取最后 5 条记录,您需要在 CTE 或嵌套查询中使用 ROWCOUNT,而不是 GROUP BY 或 DISTINCT。 GROUP BY 和 DISTINCT 根据某些字段消除记录,当您想根据这些字段对它们进行排序和排名时

标签: c# sql-server entity-framework-core ef-core-3.1


【解决方案1】:

在查询的前面调用 ToListAsync 将导致所有其他 linq 语句无法编译,因为 ToListAsync 将返回一个 Task 所以基本上你需要先等待结果或调用 .Result (这将是当前线程的阻塞) .我的建议是将查询拆分为:

  1. 获取数据
  2. 投影数据

例如

    var historyOfCalls = await context.CallbackHistoryDbSet
        .Include(historyEntry => historyEntry.Caller)
        .Where(historyEntry => historyEntry.CompanyId == companyId)
        .AsNoTracking()
        .ToListAsync(cancellationToken).ConfigureAwait(false);

    var projection = historyOfCalls 
        .GroupBy(s => s.Caller.PhoneNumber);

记住,通过调用 group by 你会得到一个 Grouping,所以当调用 Select 时你有一个 Key 属性(电话号码)和一个 value 属性。我建议通过使用调用方 DbSet 并包含其调用方历史记录来反转您的查询,然后从那里分组并使用 group by 上的一些重载来选择将值更正到 TV 中。

    var callers = await context.CompanyDiaryDbSet
        .Include(c => c.CallbackHistory)
        .Where(c=> c.CompanyId == companyId)
        .AsNoTracking()
        .ToListAsync(cancellationToken).ConfigureAwait(false);

【讨论】:

    猜你喜欢
    • 2020-09-10
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 2021-09-05
    • 2020-06-15
    • 2011-06-12
    相关资源
    最近更新 更多