LINQ group by date - include empty days WITHOUT using join

后端 未结 3 741
天涯浪人
天涯浪人 2020-12-22 02:47

Using C#, NHibernate, NHibernate to LINQ. Using NHibernate to LINQ, I do not have the JOIN functionality. I can not use QueryOver either.

I have a LINQ query that co

相关标签:
3条回答
  • 2020-12-22 03:13

    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);
    
    0 讨论(0)
  • 2020-12-22 03:26

    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)

    0 讨论(0)
  • 2020-12-22 03:31

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

    0 讨论(0)
提交回复
热议问题