LINQ group by date - include empty days WITHOUT using join

女生的网名这么多〃 提交于 2019-11-29 16:51:28

You can Union() the data from your query with dummy items that you create in memory. For example:

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

There are two things I would change in your LINQ Query, I'd include the null dates

.Where(x => x.Created <= toDate.Value && x.Created >= fromDate.Value)

becomes something like

.Where(x => x.Created.Date == null || (x.Created <= toDate.Value && x.Created >= fromDate.Value))

My other pointer would be that your .GroupBy requires a x.Created.Date, use of IsNull or similar function to set the value here to a value regardless of whether the entry is Null

E.g. (x.Created == null ? "" : x.Created.ToString("yyyyMMdd"))

(I apologize but I'm not on my development PC at this moment in time so can't definitively state the correct code)

There's a great way to do it by implementing an extension method for IEnumerable<IGrouping<TKey, TElement>> (the type that is returned from GroupBy). In my humble opinion it's the best approach for a number of reasons:

  • Doesn't recalculate the result set being filled (.ToList() also accomplishes that, but at the price of eager evaluation),
  • Keeps lazy evaluation (deferred execution) of GroupBy,
  • One-liners friendly (can be chained inside a fluent LINQ query),
  • Generic, reusable,
  • Elegant, easy to read.

Implementation

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

Sample usage

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

Sample output

(Five integers on top are printed from inside the query when it's being evaluated. As you can see, there was only one calculation pass).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!