Linq查询按月累计活动

时间:2010-06-26 01:49:25

标签: c# linq-to-sql

我有一个看似简单的要求,但我无法弄清楚如何将其编写为只有一次往返服务器的查询。

基本上我有一个简单的表

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日),每个月我都需要那个月活跃的项目数。如果某个项目是在该月期间或之前创建的,并且未关闭(即已关闭为空)或在该月之后关闭,则该项目处于活动状态。所以我使用辅助方法将其转换为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 个答案:

答案 0 :(得分:0)

首先拉动您的物品,然后使用内存中的集合滚动您的月份。我不确定我的数据是否适合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();

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

我会跟Jay说的那样。我有类似的情况。在内存中进行排序/查询比多次按DB更快。

如果您提前知道自己只是要阅读,请将objectContext.Table设置为MergeOption.NoTracking并使用foreach循环进行迭代。

如果仍需要跟踪,请在使用后将对象从dataContext中分离

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

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

答案 3 :(得分:0)

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

我真正需要做的是左手加入一个月的表,然后做一组并计算每个月的项目数。一个月的正常分组是行不通的,因为这样的项目只能在一个月内计算,而不是所有它们都是活跃的。所以我添加了一个表月份,其中只包含了当月第一天的日期,并在其上进行了左连接。这个操作需要经常进行,我认为值得为它添加一个表。

继承人最后的询问:

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

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