【问题标题】:How do I group/aggregate but return fields other than the aggregation field?如何分组/聚合但返回聚合字段以外的字段?
【发布时间】:2010-06-21 16:53:00
【问题描述】:

我有两个表,Tasks 和 TaskMilestones。我想要一个查询来为每个任务返回最近的过去里程碑和最近的未来 TaskMilestone。我正在使用 C#/LINQ-to-SQL。我该怎么做?

任务列:Id、TaskName TaskMilestones 列:Id、TaskId、MilestoneName、MilestoneDate

我想要一个包含以下行的返回表:TaskName、MilestoneDate、MilestoneName

我当前的解决方案导致 Linq 为每个任务查询一次数据库,这非常慢。

[编辑解决 cmets] 当前的实现很简单,不是一条语句,它只是查询任务列表,然后使用适当的 where 子句查询每个 TaskId 两次:

var x = from p in this.Database.Task
        join pm in this.Database.TaskMilestones on p.Id equals pm.TaskId
        select new
        {
            TaskId = p.Id,
            TaskName = p.Name,
            MilestoneName = m.Name,
            MilestoneDate = pm.MilestoneDate,
        };

foreach (var record in records)
{
    var y = x.Where(p => p.TaskId == record.Id && p.MilestoneDate <= dt);
    var z = x.Where(p => p.TaskId == record.Id && p.MilestoneDate > dt);

    ...

【问题讨论】:

  • 也许将您当前的解决方案添加到帖子中?
  • +1,我也有兴趣查看您当前的 LINQ。

标签: sql linq linq-to-sql


【解决方案1】:
DateTime dt = DateTime.Today;

var records =
  from p in db.Tasks
  let pastMilestone = p.TaskMilestones
    .Where(pm => pm.MilestoneDate <= dt)
    .OrderByDescending(pm => pm.MilestoneDate)
    .FirstOrDefault()
  let nextMilestone = p.TaskMilestones
    .Where(pm => pm.MilestoneDate > dt)
    .OrderBy(pm => pm.MilestoneDate)
    .FirstOrDefault()
  select new
  {
    Task = p,
    PastMilestone = pastMilestone,
    NextMilestone = nextMilestone
  }

另一种选择是加载每个项目的所有里程碑,然后使用 LinqToObjects 过滤它们:

DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Task>(p => p.TaskMilestones);
db.LoadOptions = dlo;

var records = db.Tasks;

foreach(Task record in records)
{
  TaskMilestone pastMilestone = record.TaskMilestones
        .Where(pm => pm.MilestoneDate <= dt)
        .OrderByDescending(pm => pm.MilestoneDate)
        .FirstOrDefault()
  TaskMilestone nextMilestone = record.TaskMilestones
        .Where(pm => pm.MilestoneDate > dt)
        .OrderBy(pm => pm.MilestoneDate)
        .FirstOrDefault()
}

【讨论】:

  • 谢谢,第一个效果很好。我将 PastMilestone 字段更改为几个单独的字段,但以其他方式逐字使用。
【解决方案2】:

在我的头顶:

var q = from t in Context.Tasks
        let mostRecentDate = t.Milestones.Where(m => m.MilestoneDate < DateTime.Now)
                                         .Max(m => m.MilestoneDate)
        select new 
        {
            TaskId = t.Id,
            TaskName = t.TaskName,
            RecentPastMilestone = t.Milestones.Where(m => m.MilestoneDate == mostRecentDate)
                                              .FirstOrDefault()
        };

【讨论】:

  • 我刚才试过了,但是它会导致Linq生成多个SQL查询。每个任务之一:SELECT TOP (1) [t0].[Id], [t0].[NeedDate], [t0].[Duration], [t0].[IsShown], [t0].[ProgramId] , [t0].[MilestonesId], [t0].[StatusId], [t0].[CreatedOn], [t0].[ModifiedOn], [t0].[CreatedBy], [t0].[ModifiedBy], [ t0].[ActualCompletion] FROM [ProgramsMilestones] AS [t0] WHERE ([t0].[NeedDate] = @x2) AND ([t0].[ProgramId] = @x1) GO
  • 奇数。它不在 L2E 中。虽然它仍然会比你拥有的快一点,但它并不理想。我会考虑的。
  • 您可以尝试将let 合并到select 部分。我不太了解 L2S 生成 SQL 的确切规则,但值得一试。
  • 仅供参考:我的测试是将您拥有的内容粘贴到 LinqPad 中并执行。感谢您的帮助!
  • 多个查询显然来自 LinqPad,用每条记录填充里程碑。当我将其更改为 .FirstOrDefault().Id 时,它停止了多查询...我想我可以将其重新加入主表...
猜你喜欢
  • 2010-12-20
  • 2018-06-13
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多