LINQ group by date - включайте пустые дни БЕЗ использования join
Использование C#, NHibernate, NHibernate для LINQ. Используя NHibernate для LINQ, у меня нет функциональности JOIN. Я не могу использовать QueryOver либо.
У меня есть запрос LINQ, который подсчитывает количество потенциальных клиентов и продаж. Эта таблица создается только тогда, когда сделано новое предложение или продажа, поэтому в некоторые дни строка не вставляется. Выполнение следующего запроса работает
var query3 = query2.AsEnumerable()
.Where(x => x.Created <= toDate.Value && x.Created >= fromDate.Value)
.GroupBy(x => x.Created.Date)
.Select(g => new ReferrerChart {
Date = g.Key.Date,
LeadsCount = g.Count(x => !x.IsSale),
SalesCount = g.Count(x => x.IsSale)
});
но некоторых дат там нет (даты были 0 лидов и продаж).
Как я могу включить эти даты и позволить им установить LeadsCount = 0 и SalesCount = 0 БЕЗ использования join?
РЕДАКТИРОВАТЬ:
Конечный результат, который работает:
var selectedFromDate = fromDate.Value;
var selectedToDate = toDate.Value;
var selectedDates = Enumerable.Range(0, 1 + selectedToDate.Subtract(selectedFromDate).Days)
.Select(offset => new ReferrerChart {
Date = selectedFromDate.AddDays(offset),
LeadsCount = 0,
SalesCount = 0
})
.ToList();
var query3 = Find(x => x.Source == "UserRef").AsEnumerable()
.Where(x => x.Created <= toDate.Value && x.Created >= fromDate.Value)
.GroupBy(x => x.Created.Date)
.Select(g => new ReferrerChart {
Date = g.Key.Date,
LeadsCount = g.Count(x => !x.IsSale),
SalesCount = g.Count(x => x.IsSale)
}).ToList();
var result = query3.Union(
selectedDates
.Where(e => !query3.Select(x => x.Date).Contains(e.Date)))
.OrderBy(x => x.Date);
3 ответа
Вы можете Union()
данные из вашего запроса с фиктивными элементами, которые вы создаете в памяти. Например:
var query4 = query3.ToList(); // Prevent multiple execution.
var startDate = DateTime.Today.AddDays(-99);
var emptyData = Enumerable.Range(1,100).Select (i =>
new ReferrerChart
{
Date = startDate.AddDays(i),
LeadsCount = 0,
SalesCount = 0
});
var result = query4 .Union(
emptyData
.Where(e => !query4.Select(x => x.Date).Contains(e.Date)))
.OrderBy(x => x.Date);
Есть две вещи, которые я бы изменил в вашем запросе LINQ, я бы включил нулевые даты
.Where(x => x.Created <= toDate.Value && x.Created >= fromDate.Value)
становится чем-то вроде
.Where(x => x.Created.Date == null || (x.Created <= toDate.Value && x.Created >= fromDate.Value))
Другим моим указателем будет то, что вашему.GroupBy требуется x.Created.Date, использование IsNull или аналогичной функции для установки значения здесь в значение независимо от того, является ли запись пустой
Например (x.Created == null ? "" : x.Created.ToString("yyyyMMdd"))
(Я извиняюсь, но в данный момент я не на своем компьютере для разработки, поэтому не могу точно указать правильный код)
Есть отличный способ сделать это путем реализации метода расширения для IEnumerable<IGrouping<TKey, TElement>>
(тип, который возвращается из GroupBy
). По моему скромному мнению, это лучший подход по ряду причин:
- Не пересчитывает заполненный результирующий набор (
.ToList()
также выполняет это, но ценой нетерпеливой оценки), - Сохраняет ленивую оценку (отложенное исполнение)
GroupBy
, - Дружественный к одной строке (может быть объединен в цепочку внутри быстрого запроса LINQ),
- Универсальный, многоразовый,
- Элегантный, легко читаемый.
Реализация
public static IEnumerable<IGrouping<TKey, TElement>> Fill<TKey, TElement>(this IEnumerable<IGrouping<TKey, TElement>> groups, IEnumerable<TKey> filling)
{
List<TKey> keys = filling.ToList();
foreach (var g in groups)
{
if(keys.Contains(g.Key))
keys.Remove(g.Key);
yield return g;
}
foreach(var k in keys)
yield return new EmptyGrouping<TKey, TElement>(k);
}
class EmptyGrouping<TKey, TElement> : List<TElement>, IGrouping<TKey, TElement>
{
public TKey Key { get; set; }
public EmptyGrouping(TKey key)
{
this.Key = key;
}
}
Пример использования
Random rand = new Random();
var results = Enumerable.Repeat(0, 5) // Give us five
.Select(i => rand.Next(100)) // Random numbers 0 - 99
.GroupBy(r => r.Dump("Calculating group for:") / 10) // Group by tens (0, 10, 20, 30, 40...)
.Fill(Enumerable.Range(0, 10)) // Fill aby missing tens
.OrderBy(g => g.Key); // Sort
@"Anything above = eager evaluations.
Anything below = lazy evaluations.".Dump();
results.Dump();
Образец вывода
(Пять целых чисел сверху печатаются изнутри запроса, когда он оценивается. Как видите, был только один проход вычисления).