EF 3.1:通过SQL翻译问题解决LINQ Group

问题描述 投票:1回答:2

在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之后,但无法编译

c# sql-server entity-framework-core ef-core-3.1
2个回答
0
投票

我认为使用“查询”语法比使用“方法”或“流利”语法更容易进行此查询。试试这个:

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();

这个想法是使用匿名类型来“存储”分组的结果。


0
投票

在查询中更早地调用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);

请记住,通过呼叫分组,您会得到分组,因此在呼叫选择时,您具有Key属性(电话号码)和and value属性。我建议您通过使用调用方DbSet来反转查询,并包括其调用方历史记录,然后从那里进行分组,并使用分组依据上的一些重载来选择将值更正为TV。

    var callers = await context.CompanyDiaryDbSet
        .Include(c => c.CallbackHistory)
        .Where(c=> c.CompanyId == companyId)
        .AsNoTracking()
        .ToListAsync(cancellationToken).ConfigureAwait(false);
© www.soinside.com 2019 - 2024. All rights reserved.