2010-06-26 4 views
1

У меня есть, казалось бы, простое требование, но я не могу понять, как записать его в качестве запроса, который имеет только один обратный рейс на сервер.Агрегация запросов Linq по месяцам активна

В принципе у меня есть простая таблица

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

и то, что я хочу сделать, это в некотором диапазоне времени (скажем, 1/1/2010 до 6/1/2010), за каждый месяц я нужен количество элементов, которые были активны в этом месяце. элемент активен, если он был создан либо во время, либо до этого месяца и либо не закрыт (т. е. закрыт недействителен), либо закрыт после этого месяца. Поэтому я перевел это в выражение 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 представляет собой отдельный запрос для его супер медленно (туда и обратно за каждый месяц), так что мой вопрос, как мог Я реструктурирую этот запрос, чтобы сделать это в одном раунде на сервер.

Первоначально я думал о том, чтобы делать какую-то группу по сумме по месяцам, но поскольку каждый элемент может быть «активным» в разные месяцы, я не думаю, что это сработает.

Любые предложения?

ответ

0

Я ненавижу чтобы ответить на мой собственный вопрос, но вот что я сделал.

То, что мне действительно нужно было делать, - это левое соединение с таблицей месяцев, а затем группа и подсчет количества предметов за каждый месяц. нормальная группировка в месяц не будет работать, потому что тогда предметы будут подсчитываться за один месяц, а не только те, для которых они были активны. Поэтому я добавил таблицу Months, содержащую только даты первого месяца, и сделал левое соединение на нем. Эта операция должна выполняться достаточно часто, и я решил, что стоит добавить таблицу для нее.

Heres окончательный запрос:

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

Я также добавил код, чтобы убедиться, что месяцы имеют правильные строки в нем для моего запроса.

0

Просто потяните ваши предметы первым, а затем переверните свои месяцы, используя коллекцию в памяти. Я не уверен, что я получил ваши критерии право для запроса БД, но это будет в основном быть:

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

это должно работать, но возможно, что он может вывести несколько тысяч строк, это может быть слишком много сетевого трафика. плохо попробуйте и посмотрите, приемлемо ли это. – luke

+0

Если строки большие, вы можете 'Select()' просто столбцы «Создать» и «Закрыть». Другой альтернативой, если вы используете MS SQL Sever, является создание функции CLR, которая будет выполнять работу на стороне сервера. Затем вы можете добавить эту функцию в качестве метода в вашем сопоставлении LINQ-to-SQL. http://msdn.microsoft.com/en-us/library/ms189876.aspx – Jay

0

Я бы пошел с тем, что говорит Джей. У меня была аналогичная ситуация. Выполнение вашей сортировки/запроса в памяти будет работать быстрее, чем ударить БД несколько раз.

Если вы знаете заранее, что только собираетесь прочитать, установите 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); 
} 

В качестве альтернативы, если вы используете не слежения, вам не нужно, чтобы отделить свои объекты

Смежные вопросы