在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
之后,但无法编译
我认为使用“查询”语法比使用“方法”或“流利”语法更容易进行此查询。试试这个:
var historyOfCallsQry =
from cbh in ctx.CallbackHistory.Include(c => c.Caller)
where cbh.CompanyId == companyId
group cbh by cbh.Caller.PhoneNumber into g
select new
{
PhoneNumber = g.Key,
LastFiveCalls = g.OrderByDescending(c => c.LastCallTimeStamp).Take(5)
};
var historyOfCalls = historyOfCallsQry.AsNoTracking().ToList();
这个想法是使用匿名类型来“存储”分组的结果。
在查询中更早地调用ToListAsync将导致所有其他linq语句不被编译,因为ToListAsync将返回Task,因此本质上您将需要首先等待结果或调用.Result(这将阻塞当前线程)。我的建议是将查询拆分为:
例如
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);
请记住,通过呼叫分组,您会得到分组
var callers = await context.CompanyDiaryDbSet
.Include(c => c.CallbackHistory)
.Where(c=> c.CompanyId == companyId)
.AsNoTracking()
.ToListAsync(cancellationToken).ConfigureAwait(false);