Linq to Entity, selecting group without value

烂漫一生 提交于 2019-12-13 02:49:22

问题


I would like to be able to group all my data by months even if some months doesn't contain any data. At this moment, I can group but the data returned contain only months with data.

Here is how my code look like:

var twelveMonthAgo = date.AddMonths(-12).Date;
var twelveMonthAgoFirstOfMonth = new DateTime(twelveMonthAgo.Year, twelveMonthAgo.Month, 1, 0, 0, 0, 0);

var data = (from i in Database.Users
            where i.RegisterDate >= twelveMonthAgoFirstOfMonth
            group i by new {y=i.RegisterDate.Year,m = i.RegisterDate.Month} into g
            select new UserStatistic{ Date = EntityFunctions.CreateDateTime(g.Key.y, g.Key.m, 1, 0, 0, 0)
                                       , UserCount = g.Count(o => o.Id)
                                     });

//The code below is what I would like to remove                                         
var toReturn = new List<UserStatistic>();
var allData = data.ToList();
DateTime datei = twelveMonthAgoFirstOfMonth;
while (datei.Year<= date.Year && datei.Month<=date.Month){
    var info = allData.SingleOrDefault(x => x.Date.HasValue && x.Date.Value.Year == datei.Year && x.Date.Value.Month == datei.Month);
    toReturn.Add(info ?? new UserStatistic { Date = datei, UserCount = 0, PaymentPaid = 0 });
    datei = datei.AddMonths(1);
}
return toReturn.AsQueryable();   

As you can see, the code under the comments build a collection of the last 12 months and check if some data has been out of the database and fill up the collection if some exist, otherwise put 0.

How can I do all that without having to do the code below the comment?


回答1:


Here's some code that uses a group join to a subquery get your desired results:

DateTime date = DateTime.Today;
DateTime firstOfMonth = new DateTime(date.Year, date.Month, 1);
DateTime twelveMonthAgoFirstOfMonth = firstOfMonth.AddMonths(-12);

// Generate a collection of the months and years for the last 12 months
var monthYears = Enumerable.Range(-12, 12).Select(monthOffset => { DateTime monthDate = firstOfMonth.AddMonths(monthOffset); return new { y = monthDate.Year, m = monthDate.Month }; });

// Go through the list of months and years and join them to the users retrieved from the database in the subquery.
var data = from monthYear in monthYears
           join i in (from i in Database.Users
                      where i.RegisterDate >= twelveMonthAgoFirstOfMonth && i.RegisterDate < firstOfMonth
                      select i) on monthYear equals new { y = i.RegisterDate.Year, m = i.RegisterDate.Month } into gj
           select new UserStatistic() { Date = new DateTime(monthYear.y, monthYear.m, 1), UserCount = gj.Count() });

This can also be expressed as a group in the subquery with a left outer join:

    DateTime date = DateTime.Today;
    DateTime firstOfMonth = new DateTime(date.Year, date.Month, 1);
    DateTime twelveMonthAgoFirstOfMonth = firstOfMonth.AddMonths(-12);
    var monthYears = Enumerable.Range(-12, 12).Select(monthOffset => { DateTime monthDate = firstOfMonth.AddMonths(monthOffset); return new { y = monthDate.Year, m = monthDate.Month }; });

    var data = (from monthYear in monthYears
                join i in (from i in Database.Users
                           where i.RegisterDate >= twelveMonthAgoFirstOfMonth && i.RegisterDate < firstOfMonth
                           group i by new {y = i.RegisterDate.Year, m = i.RegisterDate.Month} into g
                           select new { Key = g.Key, UserCount = g.Count() }) on monthYear equals i.Key into j
                 from k in j.DefaultIfEmpty()
                 select new UserStatistic() { Date = new DateTime(monthYear.y, monthYear.m, 1), UserCount = k != null ? k.UserCount : 0 });

Since I don't have your EF model, you'll have to try it and see if you need to replace new DateTime with EntityFunctions.CreateDateTime.




回答2:


I haven't compiled and debugged this but It should give an idea of how I think you could fix yor problem. I use Enumerable.Range to generate a set of place holders for the months you require. I then had to choose between left joining the placeHolders to the data and merging or, just concatenating the ommissions.

I also reformated the code from the question a bit to help me understand it.

IQueryable<UserStatistic> GetPastMonths(int months)
{    
    var limitDay = date.AddMonths(-months).Date;
    var limit = new DateTime(limitDay.Year, limitDay.Month, 1, 0, 0, 0, 0);

    var placeHolders = Enumerable.Range(0, months + 1)
        .Select(m =>
            new UserStatistic
                {
                    Date = limit.AddMonths(-m),
                    UserCount = 0
                });

    var data = Database.Users
        .Where(i => i.RegisterDate >= limit)
        .GroupBy(i => new {y=i.RegisterDate.Year, m = i.RegisterDate.Month})
        .Select(g => 
            new UserStatistic
                { 
                    Date = EntityFunctions.CreateDateTime(
                            g.Key.y,
                            g.Key.m, 
                            1, 
                            0, 
                            0, 
                            0),
                    UserCount = g.Count(o => o.Id)
                });

    return data.Concat(placeHolders
        .Where(p => !data.Any(d => d.Date == p.Date)))
        .AsQueryable();
}

The cocatenating the ommissions approach is almost garaunteed to give the data back in an illogical order but, you have no garauntees anyway unless you use an order by clause.



来源:https://stackoverflow.com/questions/11653636/linq-to-entity-selecting-group-without-value

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