【发布时间】: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 => s.OrderByDescending(historyEntry => historyEntry.LastCallTimeStamp).FirstOrDefault())应该返回什么?您不能将ORDER BY子句放在SELECT子句中。您是否尝试模拟 T-SQL 的FIRST() OVER(ORDER BY...)?这是一个报告查询,而 ORM不是为处理这些而构建的。 -
另一个问题 -
Select(s => s.OrderByDescending(historyEntry => 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 => 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