代码之家  ›  专栏  ›  技术社区  ›  luke

LINQ查询按月份活动聚合

  •  1
  • luke  · 技术社区  · 14 年前

    我有一个看起来很简单的需求,但是我不知道如何把它写成一个只有一次往返服务器的查询。

    Basically i have a simple table

    CREATE TABLE Item
    (
        id int not null identity(1,1),
        create datetime not null,
        close datetime --null means not closed yet
    );
    

    我想做的是在一段时间内(比如2010年1月1日到2010年6月1日),每个月我都需要那个月活跃的项目数量。如果某个项目是在该月期间或之前创建的,并且未关闭(即关闭为空),或者在该月之后关闭,则该项目处于活动状态。因此,我使用helper方法将其转换为linq表达式:

    //just returns the first day of every month inbetween min and max (inclusive)
    private IEnumerable<DateTime> EnumerateMonths(DateTime Min, DateTime Max)
    {
        var curr = new DateTime(Min.Year, Min.Month, 1);
        var Stop = new DateTime(Max.Year, Max.Month, 1).AddMonths(Max.Day == 1 ? 0 : 1);
        while(curr < Stop)
        {
            yield return curr;
            curr = curr.AddMonths(1);
        }
    }
    
    public List<DataPoint> GetBacklogByMonth(DateTime min, DateTime max)
    {
        return EnumerateMonths(min, max)
            .Select(m => new DataPoint
                            {
                                Date = m,
                                Count = DB.Items.Where(s => s.Create <= m.AddMonths(1) && (!s.Close.HasValue || s.Close.Value >= m.AddMonths(1)))
                                        .Count()
                                }
                ).ToList();
    }
    

    除了每个 Count

    有什么建议吗?

    4 回复  |  直到 14 年前
        1
  •  0
  •   Jay    14 年前

    只需先把你的物品拿出来,然后用内存中的集合来滚动你的月份。我不确定您的条件是否适合DB查询,但基本上是:

    var items = Db.Items.Where(s => s.Create <= min 
        && (!s.Close.HasValue || s.Close.Value >= max)).ToList();
    
    return EnumerateMonths(min, max).Select(m => new DataPoint
        {
            Date = m,
            Count = items.Where(s => s.Create <= m.AddMonths(1) && (!s.Close.HasValue || s.Close.Value >= m.AddMonths(1))).Count()
        }).ToList();
    
        2
  •  0
  •   James Manning    14 年前

    101个LINQ样本中的一个是嵌套的一年和一个月。

    http://msdn.microsoft.com/en-us/vcsharp/aa336754.aspx#nested

        3
  •  0
  •   ram    14 年前

    我会同意杰伊的话。我也有类似的情况。在内存中进行排序/查询比多次命中数据库更快。

    如果你提前知道你只会阅读,设置你的 objectContext.Table MergeOption.NoTracking 并使用 foreach 循环。

    如果仍然需要跟踪,则在使用该对象后将其从DATACONTRON文本中分离出来。

    var results = from t in DBContext.table select t where t.criteria=your select criteria
    foreach (var result in results)
    {
      DoSomething(result);
      DbContext.Detach(result);
    }
    

    或者,如果不使用跟踪,则不需要分离对象。

        4
  •  0
  •   luke    14 年前

    我不愿意回答我自己的问题,但这就是我所做的。

    一直以来我真正需要做的是一个带有月表的左联接,然后做一个组,并对每个月的项目数进行计数。一个月的正常分组是不起作用的,因为只有一个月才会计算项目,而不是所有项目的活动时间。所以我添加了一个表months,只包含第一个月的日期,并对其进行了左联接。这个操作需要经常进行,我认为它值得为它添加一个表。

    最后一个疑问:

            var joins = from m in DB.Months
                        from s in DB.Items
                        let nm = m.month.AddMonths(1)
                        where s.Create < nm && (!s.Close.HasValue || s.Close.Value >= nm) && m.month >= min && m.month <= max
                        select new { d = m.month, id = s.ID };
            var counts = from j in joins
                         group j by j.d into g
                         select new DataPoint { Date = g.Key, Count = g.Key > DateTime.Now ? 0 : g.Count() };
    

    我还添加了一些代码,以确保月份在查询中有正确的行。