Group by multiple values and break down to weekly periods - c#

I'm still learning LINQ and have a task where I need to group Booking objects by four properties and then by weekly intervals depending on the input timerange.
public class Booking
{
public string Group { get; set; }
public BookingType Type { get; set; }
public BookingStatus Status { get; set; }
public decimal Price { get; set; }
public bool Notification { get; set; }
public DateTime Date { get; set; }
}
Let's say we have the following Bookings:
IList<Booking> Bookings = new List<Booking>
{
new Booking{Group = "Group1", Type = BookingType.Online, Status = BookingStatus.New, Price = 150, Date = new DateTime(2012,06,01)},
new Booking{Group = "Group1", Type = BookingType.Online, Status = BookingStatus.New, Price = 100, Date = new DateTime(2012,06,02)},
new Booking{Group = "Group1", Type = BookingType.Online, Status = BookingStatus.New, Price = 200, Date = new DateTime(2012,06,03)},
new Booking{Group = "Group2", Type = BookingType.Phone, Status = BookingStatus.Accepted, Price = 80, Date = new DateTime(2012,06,10)},
new Booking{Group = "Group2", Type = BookingType.Phone, Status = BookingStatus.Accepted, Price = 110, Date = new DateTime(2012,06,12)},
new Booking{Group = "Group3", Type = BookingType.Store, Status = BookingStatus.Accepted, Price = 225, Date = new DateTime(2012,06,20)},
new Booking{Group = "Group3", Type = BookingType.Store, Status = BookingStatus.Invoiced, Price = 300, Date = new DateTime(2012,06,21)},
new Booking{Group = "Group3", Type = BookingType.Store, Status = BookingStatus.Invoiced, Price = 140, Date = new DateTime(2012,06,22)},
};
That would result in the following lines on the final printout:
Week 22 Week 23 Week 24 Week 25 Week 26
May28-Jun3 Jun4-10 Jun11-17 Jun18-24 Jun25-Jul1
Group Type Status Cnt. Price Cnt. Price Cnt. Price Cnt. Price Cnt. Price
----------------------------------------------------------------------------------------------
Group1 Online New 3 450 0 0 0 0 0 0 0 0
Group2 Phone Accepted 0 0 1 80 1 110 0 0 0 0
Group3 Store Accepted 0 0 0 0 0 0 1 225 0 0
Group3 Store Invoiced 0 0 0 0 0 0 2 440 0 0
I have created 2 additional classes to represent a line with the possibility to include any number of weeks:
public class GroupedLine
{
public string Group { get; set; }
public BookingType Type { get; set; }
public BookingStatus Status { get; set; }
public List<WeeklyStat> WeeklyStats { get; set; }
}
public class WeeklyStat
{
public DateTime WeekStart { get; set; }
public decimal Sum { get; set; }
public int Count { get; set; }
}
If I have the following time period:
var DateFrom = new DateTime(2012, 05, 28);
var DateTo = new DateTime(2012, 7, 01);
Firstly, I need to identify what weeks are necessary in the statistics: in this case week 22-26.
For that I have the following code:
var DateFrom = new DateTime(2012, 05, 28);
var DateTo = new DateTime(2012, 7, 01);
var firstWeek = GetFirstDateOfWeek(DateFrom, DayOfWeek.Monday);
IList<DateTime> weeks = new List<DateTime> { firstWeek };
while(weeks.OrderByDescending(w => w).FirstOrDefault().AddDays(7) <= DateTo)
{
weeks.Add(weeks.OrderByDescending(w => w).FirstOrDefault().AddDays(7));
}
And now, I'd need some LINQ magic to do the grouping both by the 4 properties and the aggregation (count of bookings and sum of prices) for the weeks.
I can attach code sample of the LINQ I got so far tomorrow, as I don't have access to it now.
Sorry for the long post, hope it's clear what I mean.
Edit: 2012-11-07
I have to modify the question a bit, so that the grouped weeks only include those weeks, that actually have data.
Updated example output:
May28-Jun3 Jun4-10 Jun18-24
Group Type Status Cnt. Price Cnt. Price Cnt. Price
---------------------------------------------------------------
Group1 Online New 3 450 0 0 0 0
Group2 Phone Accepted 0 0 1 80 0 0
Group3 Store Accepted 0 0 0 0 1 225
Group3 Store Invoiced 0 0 0 0 2 440
In this case there were no Bookings in period Jun 11-17 and Jun25-Jul1 so they are omitted from the results.

This query will get all data
var query = from b in Bookings
where b.Date >= dateFrom && b.Date <= dateTo
group b by new { b.Group, b.Type, b.Status } into g
select new GroupedLine()
{
Group = g.Key.Group,
Type = g.Key.Type,
Status = g.Key.Status,
WeeklyStats = (from b in g
let startOfWeek = GetFirstDateOfWeek(b.Date)
group b by startOfWeek into weekGroup
orderby weekGroup.Key
select new WeeklyStat()
{
WeekStart = weekGroup.Key,
Count = weekGroup.Count(),
Sum = weekGroup.Sum(x => x.Price)
}).ToList()
};
I leave UI output to you :)
This will also return WeekStats for all weeks (with 0 values, if we do not have booking groups on some week):
// sequence contains start dates of all weeks
var weeks = Bookings.Select(b => GetFirstDateOfWeek(b.Date))
.Distinct().OrderBy(date => date);
var query = from b in Bookings
group b by new { b.Group, b.Type, b.Status } into bookingGroup
select new GroupedLine()
{
Group = bookingGroup.Key.Group,
Type = bookingGroup.Key.Type,
Status = bookingGroup.Key.Status,
WeeklyStats = (from w in weeks
join bg in bookingGroup
on w equals GetFirstDateOfWeek(bg.Date) into weekGroup
orderby w
select new WeeklyStat()
{
WeekStart = w,
Count = weekGroup.Count(),
Sum = weekGroup.Sum(b => b.Price)
}).ToList()
};
Keep in mind, that if you need date filter (from, to), then you need to apply it both to weeks query and bookings query.

var query = Bookings.GroupBy(book => new GroupedLine()
{
Group = book.Group,
Type = book.Type,
Status = book.Status
})
.Select(group => new
{
Line = group.Key,
Dates = group.GroupBy(book => GetWeekOfYear(book.Date))
.Select(innerGroup => new
{
Week = innerGroup.Key,
Count = innerGroup.Count(),
TotalPrice = innerGroup.Sum(book => book.Price)
})
});
public static int GetWeekOfYear(DateTime date)
{
return date.DayOfYear % 7;
}

This variant generates "empty" spots for weeks with no data:
// I group by week number, it seemed clearer to me , but you can change it
var firstWeek = cal.GetWeekOfYear(DateFrom, dfi.CalendarWeekRule, dfi.FirstDayOfWeek);
var lastWeek = cal.GetWeekOfYear(DateTo, dfi.CalendarWeekRule, dfi.FirstDayOfWeek);
var q = from b in Bookings
group b by new { b.Group, b.Type, b.Status }
into g
select new
{
Group = g.Key,
Weeks =
from x in g
select new
{
Week = cal.GetWeekOfYear(x.Date,
dfi.CalendarWeekRule,
dfi.FirstDayOfWeek),
Item = x
}
} into gw
from w in Enumerable.Range(firstWeek, lastWeek-firstWeek+1)
select new
{
gw.Group,
Week = w,
Item = from we in gw.Weeks
where we.Week == w
group we by we.Item.Group into p
select new
{
p.Key,
Sum = p.Sum (x => x.Item.Price),
Count = p.Count()
}
};

Related

Left outer join to include null values linq lambda

I have one table in database named Balance and a list of dates as follows:
List<string> allDates = { "2021-01-02", "2021-01-03", "2021-01-04" }
Balance table:
Id, Amount, BalanceDate
1, 233, "2021-01-02"
2, 442, "2021-01-03
I need to fetch the records in Balance table with amount 0 for the missing dates. For example:
233, "2021-01-02"
442, "2021-01-03"
0, "2021-01-04"
I have tried the following:
balnces.GroupJoin(allDates,
balance => balance.Date,
d => d,
(balance, d) => balance);
But the records are still the same (only the ones in the balance table)
Given a data structure from database:
private class balance
{
public int id { get; set; }
public double amount { get; set; }
public string date { get; set; }
}
You get your data as you want (this is only a mock-up)
List<string> allDates = new List<string> { "2021-01-02", "2021-01-03", "2021-01-04" };
List<balance> balances = new List<balance>();
balances.Add(new balance { id = 1, amount = 233 , date = "2021-01-02" });
balances.Add(new balance { id = 2, amount = 442, date = "2021-01-03" });
you can get your desired result this way:
List<balance> result = allDates.Select(d=>
new balance {
amount =
balances.Any(s=> s.date == d)?
balances.FirstOrDefault(s => s.date == d).amount:0,
date = d
}).ToList();
If your default contains a 0 in amount instead a null, you can skip the .Any check
Assumption
Balance query had been materialized and data are returned from the database.
Solution 1: With .DefaultIfEmpty()
using System.Linq;
var result = (from a in allDates
join b in balances on a equals b.Date.ToString("yyyy-MM-dd") into ab
from b in ab.DefaultIfEmpty()
select new { Date = a, Amount = b != null ? b.Amount : 0 }
).ToList();
Sample Program for Solution 1
Solution 2: With .ToLookup()
var lookup = balances.ToLookup(x => x.Date.ToString("yyyy-MM-dd"));
var result = (from a in allDates
select new
{
Date = a,
Amount = lookup[a] != null && lookup[a].Count() > 0 ? lookup[a].First().Amount : 0
}
).ToList();
Sample Program for Solution 2

How to group by date time in c# for a specific range?

In my aspnet core 3.1 application I am using CQRS pattern. I have bulling summary table which is calculating in every hour (Hangifre background job) some price
Example data looks like:
Price - 10, StartDate - 2020/08/02 10:00 , EndDate - 2020/08/03 11:00
Price - 10, StartDate - 2020/08/02 11:00 , EndDate - 2020/08/03 12:00
Price - 10, StartDate - 2020/08/02 13:00 , EndDate - 2020/08/03 14:00
Price - 10, StartDate - 2020/08/02 14:00 , EndDate - 2020/08/03 15:00
Price - 10, StartDate - 2020/08/02 15:00 , EndDate - 2020/08/03 16:00
Price - 10, StartDate - 2020/08/02 16:00 , EndDate - 2020/08/03 17:00
I would like achive something like:
if I specify periodDuration=3h
Price - 30, StartDate - 2020/08/02 10:00 , EndDate - 2020/08/03 13:00
Price - 30, StartDate - 2020/08/02 13:00 , EndDate - 2020/08/03 16:00
Price - 30, StartDate - 2020/08/02 19:00 , EndDate - 2020/08/03 22:00
My method looks like:
var billing = _context.BillingSummaries.AsQueryable();
switch (request.SortBy)
{
case "createdAt" when request.SortDirection == "asc":
billing = billing.OrderBy(x => x.BeginApply);
break;
case "createdAt" when request.SortDirection == "desc":
billing = billing.OrderByDescending(x => x.BeginApply);
break;
}
if (request.StartDate.HasValue)
{
billing = billing.Where(x =>
x.BeginApply >= request.StartDate);
}
if (request.EndDate.HasValue)
{
billing = billing.Where(x =>
x.EndApply <= request.EndDate);
}
// Want to achieve this
billing = request.PeriodDuration switch
{
"3h" => "calculate 3 hours range",
"6h" => "calculate 6 hours range",
"12h" => "calculate 12 hours range",
"d" => "calculate daily range",
"w" => "calculate weekly range",
"m" => "calculate monthly range",
_ => billing
};
var billings = await billing.Skip(request.Offset ?? 0).Take(request.Limit ?? 50)
.ToListAsync(cancellationToken: cancellationToken);
if (billings == null)
throw new NotFoundException("Not found");
return new BillingInfo
{
Data = _mapper.Map<List<BillingSummary>, List<BillingSummaryDto>>(billings),
TotalCount = await billing.CountAsync(cancellationToken: cancellationToken),
AllPrice = await billing.SumAsync(x => x.Price, cancellationToken:
cancellationToken),
Currency = currency.ToString("G")
};
Suppose raw data always span one hour each.
billing = request.PeriodDuration switch
{
"3h" => (
from b in billing
group b by new Hourly { b.StartDate.Year, b.StartDate.Month, b.StartDate.Day, Hour = b.StartDate.Hour / 3 } into groups
from bs in groups
select new
{
Price = bs.Sum(b => b.Price),
StartDate = bs.Min(p => p.StartDate),
EndDate = bs.Max(p => p.EndDate)
}
),
"6h" => (
from b in billing
group b by new Hourly { b.StartDate.Year, b.StartDate.Month, b.StartDate.Day, Hour = b.StartDate.Hour / 6 } into groups
from bs in groups
select new
{
Price = bs.Sum(b => b.Price),
StartDate = bs.Min(p => p.StartDate),
EndDate = bs.Max(p => p.EndDate)
}
),
"12h" => (
from b in billing
group b by new Hourly { b.StartDate.Year, b.StartDate.Month, b.StartDate.Day, Hour = b.StartDate.Hour / 12 } into groups
from bs in groups
select new
{
Price = bs.Sum(b => b.Price),
StartDate = bs.Min(p => p.StartDate),
EndDate = bs.Max(p => p.EndDate)
}
),
"d" => (
from b in billing
group b by new Daily { b.StartDate.Year, b.StartDate.Month, b.StartDate.Day } into groups
from bs in groups
select new
{
Price = bs.Sum(b => b.Price),
StartDate = bs.Min(p => p.StartDate),
EndDate = bs.Max(p => p.EndDate)
}
),
"w" => (
from b in billing
group b by new { b.StartDate.Year, Week = SqlFunctions.DatePart("week", b.StartDate) } into groups
from bs in groups
select new
{
Price = bs.Sum(b => b.Price),
StartDate = bs.Min(p => p.StartDate),
EndDate = bs.Max(p => p.EndDate)
}
),
"m" => (
from b in billing
group b by new { b.StartDate.Year, b.StartDate.Month } into groups
from bs in groups
select new
{
Price = bs.Sum(b => b.Price),
StartDate = bs.Min(p => p.StartDate),
EndDate = bs.Max(p => p.EndDate)
}
),
_ => billing
};
first group your data by date and after try it
public class Billingdata
{
public int Price { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
public class GroupingData {
public int Start { get; set; }
public int End { get; set; }
}
var timerangelist = new List<GroupingData> {
new GroupingData{ Start = 10, End=13 },
new GroupingData{ Start = 13, End=16 },
new GroupingData{ Start = 16, End=19 },
new GroupingData{ Start = 19, End=22 },
};
var result = new List<Billingdata>();
var billings = new List<Billingdata> {
new Billingdata{Price = 10, Start = new DateTime(2020,08,02,10,00,00), End = new DateTime(2020,08,02,11,00,00) },
new Billingdata{Price = 10, Start = new DateTime(2020,08,02,11,00,00), End = new DateTime(2020,08,02,12,00,00) },
new Billingdata{Price = 10, Start = new DateTime(2020,08,02,12,00,00), End = new DateTime(2020,08,02,13,00,00) },
new Billingdata{Price = 10, Start = new DateTime(2020,08,02,13,00,00), End = new DateTime(2020,08,02,14,00,00) },
new Billingdata{Price = 10, Start = new DateTime(2020,08,02,14,00,00), End = new DateTime(2020,08,02,15,00,00) },
new Billingdata{Price = 10, Start = new DateTime(2020,08,02,15,00,00), End = new DateTime(2020,08,02,16,00,00) },
new Billingdata{Price = 10, Start = new DateTime(2020,08,02,16,00,00), End = new DateTime(2020,08,02,17,00,00) }
};
foreach (var item in timerangelist)
{
var data = billings.Where(w => item.End >= w.End.Hour && w.Start.Hour >= item.Start).ToList();
var p = data.Sum(s => s.Price);
var ss = data.FirstOrDefault(f => f.Start.Hour == item.Start)?.Start;
var e = data.FirstOrDefault(f => f.End.Hour == item.End)?.End;
if (ss == null || e == null) { continue; }
result.Add(
new Billingdata { Price = p,
Start = (DateTime)ss,
End = (DateTime)e,
}
);
}
enter code here

LINQ Join usage data from two data sets into one

What I have:
Two lists of the following model:
int SubscriptionId
int ItemId
double Usage
double EffectiveRate
string ResourceName
string UnitOfMeasure
The first contains usage data of the last month like this:
SubscriptionId ItemId Usage EffectiveRate ResourceName UnitOfMesaure
_________________________________________________________________________
1 1 2 2,75 R1 U1
1 2 3 1,50 R2 U2
The seconds contains usage data of the current month like this:
SubscriptionId ItemId Usage EffectiveRate ResourceName UnitOfMesaure
_________________________________________________________________________
1 1 5 2,75 R1 U1
1 3 2 1,50 R3 U3
What I want:
This should be merge in a list like this:
SubscriptionId ItemId UsageThis UsageLast EffRate ResName UOM
_________________________________________________________________________
1 1 5 2 2,75 R1 U1
1 2 0 3 1,50 R2 U2
1 3 2 0 1,50 R3 U3
What I have:
//data for both months available
if (resourcesThisMonth.Any() && resourcesLastMonth.Any())
{
//join both months
resources = from resourceLastMonth in resourcesLastMonth
join resourceThisMonth in resourcesThisMonth
on new { resourceLastMonth.SubscriptionId, resourceLastMonth.ItemId } equals new { resourceThisMonth.SubscriptionId, resourceThisMonth.ItemId }
select new Resource
{
SubscriptionId = resourceThisMonth.SubscriptionId,
ItemId = resourceThisMonth.ItemId,
UsageThisMonth = resourceThisMonth.Usage,
UsageLastMonth = resourceLastMonth.Usage,
EffectiveRate = resourceThisMonth.EffectiveRate,
ResourceName = resourceThisMonth.ResourceName,
UnitOfMeasure = resourceThisMonth.UnitOfMeasure
};
//resources only last month available
var resourcesOnlyLastMonth = resourcesLastMonth.Where(r => !resourcesThisMonth.Where(s => s.ItemId == r.ItemId && s.SubscriptionId == r.SubscriptionId).Any())
.Select(r => new Resource
{
SubscriptionId = r.SubscriptionId,
ItemId = r.ItemId,
UsageThisMonth = 0.0,
UsageLastMonth = r.Units,
EffectiveRate = r.EffectiveRate,
ResourceName = r.ResourceName,
UnitOfMeasure = r.UnitOfMeasure
});
//resources only this month available
var resourcesOnlyThisMonth = resourcesThisMonth.Where(r => !resourcesLastMonth.Where(s => s.ItemId == r.ItemId && s.SubscriptionId == r.SubscriptionId).Any())
.Select(r => new Resource
{
SubscriptionId = r.SubscriptionId,
ItemId = r.ItemId,
UsageThisMonth = r.Usage,
UsageLastMonth = 0.0,
EffectiveRate = r.EffectiveRate,
ResourceName = r.ResourceName,
UnitOfMeasure = r.UnitOfMeasure
});
//union data
resources = resources.Union(resourcesOnlyLastMonth);
resources = resources.Union(resourcesOnlyThisMonth);
}
//data for last month available
else if (resourcesLastMonth.Any())
{
resources = from resource in resourcesLastMonth
select new Resource
{
SubscriptionId = resource.SubscriptionId,
ItemId = resource.ItemId,
UsageThisMonth = 0.0,
UsageLastMonth = resource.Usage,
EffectiveRate = resource.EffectiveRate,
ResourceName = resource.ResourceName,
UnitOfMeasure = resource.UnitOfMeasure
};
}
//data for this month available
else if (resourcesThisMonth.Any())
{
resources = from resource in resourcesThisMonth
select new Resource
{
SubscriptionId = resource.SubscriptionId,
ItemId = resource.ItemId,
UsageThisMonth = resource.Usage,
UsageLastMonth = 0.0,
EffectiveRate = resource.EffectiveRate,
ResourceName = resource.ResourceName,
UnitOfMeasure = resource.UnitOfMeasure
};
}
//no data available
else
{
resources = new List<Resource>();
}
Problem:
This is very much code - should be less, any possible solutions failed so far
Thanks for helping!
public class ExampleClass
{
public int Id1 { get; set; }
public int Id2 { get; set; }
public int Usage { get; set; }
public int UsageThis { get; set; }
public int UsageLast { get; set; }
}
List<ExampleClass> listThisMonth = new List<ExampleClass>
{
new ExampleClass{Id1=1, Id2=1,Usage=7, UsageThis=1, UsageLast=0},
new ExampleClass{Id1=2, Id2=2,Usage=4, UsageThis=2, UsageLast=0},
new ExampleClass{Id1=3, Id2=3,Usage=1, UsageThis=3, UsageLast=0},
};
List<ExampleClass> listLastMonth = new List<ExampleClass>
{
new ExampleClass{Id1=1, Id2=1,Usage=3, UsageThis=1, UsageLast=1},
new ExampleClass{Id1=4, Id2=4,Usage=3, UsageThis=4, UsageLast=3},
new ExampleClass{Id1=2, Id2=2,Usage=1, UsageThis=8, UsageLast=6},
};
var result = listThisMonth.Select(a=>new {value=a, list=1})
.Union(listLastMonth.Select(a => new { value = a, list = 2 }))
.GroupBy(a => new { Id1 = a.value.Id1, Id2 = a.value.Id2 })
.Select(x => new ExampleClass
{
Id1 = x.Key.Id1,
Id2 = x.Key.Id2,
UsageThis = x.Any(o => o.list == 1) ? x.First(o => o.list == 1).value.Usage : 0,
UsageLast = x.Any(o => o.list == 2) ? x.First(o => o.list == 2).value.Usage : 0,
Usage = x.Sum(o=>o.value.Usage)
}).ToList();
//id1 id2 current last sum
//1 1 7 3 10
//2 2 4 1 5
//3 3 1 0 1
//4 4 0 3 3
It looks to me that what you're looking for is a full outer join. Unfortunately, it looks like LINQ doesn't have a construct for that. So, there are a few options: LINQ - Full Outer Join
For your scenario, it looks like you have some redundant code. You should be able to do the union using two outer joins to get the correct result set. For example:
// Left join the current month with the last month
var currentMonth =
from current in resourcesThisMonth
join last in resourcesLastMonth on new { current.SubscriptionId, current.ItemId } equals new { last.SubscriptionId, last.ItemId } into outer
from o in outer.DefaultIfEmpty()
select new Resource
{
SubscriptionId = current.SubscriptionId,
ItemId = current.ItemId,
UnitsThisMonth = current.Units,
UnitsLastMonth = o?.Units ?? 0, // Replace NULL with 0
EffectiveRate = current.EffectiveRate,
ResourceName = current.ResourceName,
UnitOfMeasure = current.UnitOfMeasure
};
// Reverse of the first join. Last month LEFT JOIN Current month
var lastMonth =
from last in resourcesLastMonth
join current in resourcesThisMonth on new { last.SubscriptionId, last.ItemId } equals new { current.SubscriptionId, current.ItemId } into outer
from o in outer.DefaultIfEmpty()
select new Resource
{
SubscriptionId = last.SubscriptionId,
ItemId = last.ItemId,
UnitsThisMonth = o?.Units ?? 0, // Replace NULL with 0
UnitsLastMonth = last.Units,
EffectiveRate = o?.EffectiveRate ?? last.EffectiveRate,
ResourceName = o?.ResourceName ?? last.ResourceName,
UnitOfMeasure = o?.UnitOfMeasure ?? last.UnitOfMeasure
};
// Union them together to get a full join
var resources = currentMonth.Union(lastMonth);

Getting wrong sum and count in linq

This is my table structure
ID A B C D
1 null 10 5 null
2 3 5 null D2
3 8 null 2 D2
4 null 4 3 D1
5 4 6 1 D2
This is c# class and its property to store query result.
public class GrillTotals
{
public int? SumOfA {get; set;}
public int? SumOfB{get; set;}
public int? SumOfC{get; set;}
public int? CountOfD1{get; set;}
public int? CountOfD2{get; set;}
}
What I expect is:
SumOfA = 15
SumOfB = 20
SumOfC = 11
CountOfD1 = 1
CountOfD2 = 3
What I am getting is :
SumOfA = null,
SumOfB = null,
SumOfC = null,
CountOfD1 = 0,
CountOfD2 = 0
Here is a code what I have tried.
var _FinalResult = from s in dbContext.tblSchedules
group s by new
{
s.A,
s.B,
s.C,
s.D,
} into gt
select new GrillTotals
{
SumOfA = gt.Sum(g => g.A),
SumOfB = gt.Sum(g => g.B),
SumOfC = gt.Sum(g => g.C),
CountOfD1 = gt.Count(g => g.D == "D1"),
CountOfD2 = gt.Count(g => g.D == "D2"),
};
Try to correct me if I am doing something wrong or incorrectly.Any help will be appreciated.
You should not be grouping by the fields you want to calculate aggregates. When you group by them, every aggregate (Sum, Min, Max etc) will return the value itself (and Count 1 or 0 depending of the condition).
From what I see you are trying to return several aggregates with single SQL query. If that's correct, it's possible by using group by constant technique.
Just replace
group s by new
{
s.A,
s.B,
s.C,
s.D,
} into gt
with
group s by 1 // any constant
into gt
Try this:
var _FinalResult = from s in dbContext.tblSchedules
group s by new
{
s.A,
s.B,
s.C,
s.D,
} into gt
select new GrillTotals
{
SumOfA = gt.Sum(g => g.A ?? 0),
SumOfB = gt.Sum(g => g.B ?? 0),
SumOfC = gt.Sum(g => g.C ?? 0),
CountOfD1 = gt.Count(g => g.D == "D1"),
CountOfD2 = gt.Count(g => g.D == "D2"),
};

LINQ: display results from empty lists

I've created two entities (simplified) in C#:
class Log {
entries = new List<Entry>();
DateTime Date { get; set; }
IList<Entry> entries { get; set; }
}
class Entry {
DateTime ClockIn { get; set; }
DateTime ClockOut { get; set; }
}
I am using the following code to initialize the objects:
Log log1 = new Log() {
Date = new DateTime(2010, 1, 1),
};
log1.Entries.Add(new Entry() {
ClockIn = new DateTime(0001, 1, 1, 9, 0, 0),
ClockOut = new DateTime(0001, 1, 1, 12, 0, 0)
});
Log log2 = new Log()
{
Date = new DateTime(2010, 2, 1),
};
The method below is used to get the date logs:
var query =
from l in DB.GetLogs()
from e in l.Entries
orderby l.Date ascending
select new
{
Date = l.Date,
ClockIn = e.ClockIn,
ClockOut = e.ClockOut,
};
The result of the above LINQ query is:
/*
Date | Clock In | Clock Out
01/01/2010 | 09:00 | 12:00
*/
My question is, what is the best way to rewrite the LINQ query above to include the results from the second object I created (Log2), since it has an empty list. In the other words, I would like to display all dates even if they don't have time values.
The expected result would be:
/*
Date | Clock In | Clock Out
01/01/2010 | 09:00 | 12:00
02/01/2010 | |
*/
Try this:
var query =
from l in DB.GetLogs()
from e in l.Entries.DefaultIfEmpty()
orderby l.Date ascending
select new
{
Date = l.Date,
ClockIn = e == null ? null : e.ClockIn,
ClockOut = e == null ? null : e.ClockOut,
};
See the docs for DefaultIfEmpty for more information on it.
EDIT: You might want to just change it to perform the final part in memory:
var dbQuery =
from l in DB.GetLogs()
from e in l.Entries.DefaultIfEmpty()
orderby l.Date ascending
select new { Date = l.Date, Entry = e };
var query = dbQuery.AsEnumerable()
.Select(x => new {
Date = x.Date,
ClockIn = x.Entry == null ? null : x.Entry.CLockIn,
ClockOut = x.Entry == null ? null : x.Entry.CLockOut
});
This is building on top of Jon's solution. Using it I get the following error:
Cannot assign to anonymous type
property
I have updated Jon's example to the following and it appears to give the desired result:
var logs = new []{log1,log2};
var query =
from l in logs.DefaultIfEmpty()
from e in l.entries.DefaultIfEmpty()
orderby l.Date ascending
select new
{
Date = l.Date,
ClockIn = e == null ? (DateTime?)null : e.ClockIn,
ClockOut = e == null ? (DateTime?)null : e.ClockOut,
};
query.Dump();
Andrew
P.S. the .Dump() is due to me using LINQ Pad.
You should have a look at the LINQ Union Operator.
http://srtsolutions.com/public/blog/251070

Categories

Resources