grouping data by distinct dates using lambda - c#

I have a Message table that has fields namely Creation_Date, Message_Count and Message_Cost. I'm trying to group data by the dates but I want the dates to not repeat and then sum the total number of messages and message cost on each date row.
I've tried using the below expression but that doesn't resolve the issue.
var query = db.Messages
.GroupBy(d => d.Creation_Date)
.OrderBy(d => d.Key)
.Select(g =>
new Report
{
TotalMessagesSent = g.Select(t=>t.Message_Count).Distinct().Sum(),
TotalCost = g.Select(p=>p.Customer_Price).Distinct().Sum(),
DateTime = (DateTime)g.Key
});
What am I doing wrong here?

I'm guessing your Creation_Date includes a timestamp? If so, use d.Creation_Date.Date instead to group by the date component only.

Related

Get the earliest date with maximum occurrences from a list

I have a list which contains dates and the same date can be repeated multiple times.
List<DateTime> dates = new List<DateTime>();
foreach (List<string> l in reservations)
{
var start = Convert.ToDateTime(l.First());
var end = Convert.ToDateTime(l.Last());
for (var date = start.Date; date.Date <= end.Date; date = date.AddDays(1))
dates.Add(date);
}
I want to find the date with maximum occurrences. If there are multiple dates with the maximum occurrence, I need to get the earliest date. Here's the LINQ query I wrote:
DateTime max = dates.GroupBy(s => s)
.OrderByDescending(s => s.Count())
.ThenBy(s => s)
.First().Key;
Here I'm getting the error:
At least one object must implement IComparable.'
How do I do this?
#Servy is correct.
I would suggest:
DateTime max = dates.GroupBy(s => s)
.OrderByDescending(sg => sg.Count())
.ThenBy(sg => sg.Key)
.First().Key;
You're trying to order by a group of dates. An entire group of dates can't be compared to another group of dates (unless you supply a custom comparer).
You probably want to order on the group's key, which is a single date, not on the entire group.

Order by user and then select max date

I have this LINQ query:
ArrayList arr = new ArrayList();
var data = conn.SCOT_DADOS.OrderByDescending(x => x.DATE)
.GroupBy(r => r.USER)
.ToList();
foreach (var item in data)
{
var itemdata = item.Where(r => r.DATE == item.Max(s => s.DATE));
var name = svc.GetUserName(itemdata.Select(r => r.USER).First().ToString());
var value = itemdata.Select(r => r.VALUE).First();
var date = itemdata.Select(r => r.DATE).First().ToString("dd/MM/yyyy HH:mm:ss");
arr.Add( new{ NAME = name, DATE = date, VALUE = value} );
}
This code will give me the latest result by DATE for each USER.
But the LINQ query is selecting all data from the user and then I'm getting the latest one in the foreach loop.
Is there any way to get only the last data in the LINQ query, so I don't have to take all the user data every time?
I have tried this:
var data = conn.SCOT_DADOS.OrderByDescending(x => x.DATE)
.GroupBy(r => r.USER)
.First()
.ToList();
And then treated item as an object, instead of running selects on it.
It gave me all the data for an individual user, which isn't what I want.
What can be done?
Edit 1:
I get this error if I try to swap OrderByDescending and GroupBy:
Error CS1061 'IGrouping' does not contain a
definition for 'DATE' and no extension method 'DATE' accepting a first
argument of type 'IGrouping' could be found (are
you missing a using directive or an assembly reference?)
Edit 2:
This is some sample data (the column names are not the same because I translated them for the question):
From the data presented, I'd have the results:
If the combination of the (USER, DATE) pair is unique (which seems to be the case when looking at the sample data), the requirement can be trimmed down to
return each record if there is no other record with the same USER and later DATE
which could be translated to the following LINQ query:
var result = conn.SCOT_DADOS
.Where(r => !conn.SCOT_DADOS.Any(r2 => r2.USER == r.USER && r2.Date > r.Date))
// end of Db Query
.AsEnumerable()
.Select(r => new
{
Name = svc.GetUserName(r.User),
Value = r.Value,
Date = r.Date.ToString("dd/MM/yyyy HH:mm:ss")
}).ToList();
I'm a bit confused but from your attempts with First() think you mean this:
conn.SCOT_DADOS.GroupBy(item => item.User)
.Select(grp => grp.OrderByDescending(i => t.Date).First());
This will retrieve for each User only the latest record of it
The reason only swapping the GroupBy and OrderByDescending isn't enough and that you need the Select is that once you grouped that data your enumerable is IEnumerable<IGrouping<User,YourType>>. Each IGrouping is actually a collection by itself so you need to Select only the 1 item you want from it.
Another way is to replace the Select with:
.SelectMany(grp => grp.OrderByDescending(i => t.Date).Take(1))
IMO the first is cleaner, but the second is in the case you need for each user N first items
On the query above you can also add what you have in the foreach loop:
conn.SCOT_DADOS.GroupBy(item => item.User)
.Select(grp => grp.OrderByDescending(i => t.Date).First())
.AsEnumerable()
.Select(item => new {
Name = svc.GetUserName(item.User),
Value = item.Value,
Date = item.Date.ToString("dd/MM/yyyy HH:mm:ss")
}).ToList();
The use of the AsEnumerable() is to invoke the query to be executed to the database before the last Select() which uses the GetUserName method that will not be known to the Oracle database
IMO representing the DateTime as string is not a good way..
Update - The error you get:
Oracle 11.2.0.3.0 does not support apply
It seems that as for this version of Oracle it does not support GroupBy with Select via linq. See Linq to Entities Group By (OUTER APPLY) “oracle 11.2.0.3.0 does not support apply”.
One answer there recommended to create a view in the database for this and then use linq to select over that view. That is what I'd go for
Try this
conn.SCOT_DADOS.GroupBy(x => x.User).Select(x => new
{
User = x.Key,
Date = list.Where(y => y.User == x.Key).Max(y => y.Date)
});

.NET when grouping records by hour impossible to use datetime in the select

I'm trying to group a list of records by hour and store the number of record for each hour. Here is my code :
DateTime firstTimeStamp = myRecords.DataBaseRecords.First().TimeStamp;
Statistics = myRecords.DataBaseRecords
.GroupBy(x => x.TimeStamp.Hour)
.Select(group => new GraphModel() { Date =firstTimeStamp.AddHours(group.Key), Value = group.Count() })
.ToList();
The problem is that when I'm on the select fuction, I cannot acces to the DateTime anymore so the field group.key contains a value between 0 and 24. I just need to group all the records by hour and foreach hour, I need to have the number of records in the Value parameter.
You have to group the data by absolute hours as of the first timestamp, i.e. the differences in hours calculated for each TimeStamp value:
Statistics = myRecords.DataBaseRecords
.GroupBy(x => DbFunctions.DiffHours(firstTimeStamp, x.TimeStamp) into g
.Select(g => new GraphModel
{
Date = g.FirstOrDefault().TimeStamp,
Value = g.Count()
};
If this is plain LINQ to objects (not Entity Framework) you can replace ...
DbFunctions.DiffHours(firstTimeStamp, x.TimeStamp)
... by
(x.TimeStamp - firstTimeStamp).TotalHours
If it's LINQ to SQL, use
SqlMethods.DateDiffHour(firstTimeStamp, x.TimeStamp)
Perhaps something like this may work out for you:
DateTime myDateTime = new DateTime(DateTime.Parse(firstTimeStamp).AddHours(group.Key).Ticks);
Question specific to answer above:
...Date = new DateTime(DateTime.Parse(firstTimeStamp).AddHours(group.Key))...

Different dates in directory

If have a directory with files that are created on different dates. I want to get the dates.
Is it possible to do this with a linq query, or must I first read all the files and use a foreach loop to get the dates.
Example:
List =
File_1 6/03/2016
File_2 6/03/2016
File_3 6/03/2016
File_4 6/03/2016
File_5 15/04/2016
File_6 21/04/2016
File_7 21/04/2016
File_8 21/04/2016
Result =
6/03/2016
15/04/2016
21/04/2016
Thanks
Based on the comment from #MatthewWatson.
I have make following linq statement
var dateInfo = Directory.EnumerateFiles(Dir).Select(filename => new FileInfo(filename)).Select(i => new { i.LastWriteTime }).GroupBy(g => g.LastWriteTime.Date);
That way I get all the different dates used in my directory.
Clearly you can't process the list of dates without iterating it, but you can use Linq to produce the sequence in the first place, like so:
var dateInfo =
Directory.EnumerateFiles(directoryName)
.Select(filename => new FileInfo(filename))
.Select(info => new {info.Name, info.CreationTime});
That'll give you a list of FullName/CreationTime pairs, where FullName is the full path of the file, and CreationTime is the creation time of the file.
You can process it like so:
foreach (var item in dateInfo)
Console.WriteLine($"{item.FullName} created on {item.CreationTime}");
If you just want the (unique) dates that the files were created on:
var uniqueDates = dateInfo.GroupBy(x => x.CreationTime.Date).Select(y => y.Key);
foreach (var date in uniqueDates)
Console.WriteLine(date);
Finally, if you need the dates to be ordered:
var uniqueDates =
dateInfo.GroupBy(x => x.CreationTime.Date)
.Select(y => y.Key)
.OrderBy(z => z);
(And use .OrderByDescending() for the reverse order, of course.)
If you prefer Linq query syntax:
var uniqueDates =
from date in dateInfo
group date by date.CreationTime.Date into g
orderby g.Key
select g.Key;
Or putting the entire thing in one Linq query (maybe getting a bit unreadble here, so you might want keep it as separate queries, but this is for completeness):
var uniqueDates =
from date in
from file in Directory.EnumerateFiles(directoryName)
select new FileInfo(file).CreationTime
group date by date.Date into g
orderby g.Key
select g.Key;

Linq to Entity Group by Date

Done a lot of research but still having a tough one with this. Consider a database which has a transactions table of "CreatedOn", "Amount", "Type". I need to be able to do an entity query to get transactions of a certain type grouped together by different date granularities (month / day etc).
So imagine a table with:
2011/1/22 $300 Deposit
2011/2/18 $340 Deposit
2011/3/6 $200 Other
2011/3/6 $100 Deposit
2011/3/7 $50 Deposit
I could have a query which would pull all deposits grouped by month so it could yield:
2011-1 $300 1deposit
2011-2 $340 1deposit
2011-3 $150 2deposits
How would I then adapt this query to be by day rather than month?
Here's my current block of code but I get an inner linq exception
Can't group on A1
var result = context.TransactionEntities.Where(d => d.Type == "Deposit")
.GroupBy(g => new { g.CreatedOn.Year, g.CreatedOn.Month })
.Select(g => new
{
DateKey = g.Key,
TotalDepositAmount = g.Sum(d => d.Amount),
DepositCount = g.Count()
}).ToList();
Note: I am currently using the MySql connector and I've read possibly this is a bug?
Func<DateTime, object> groupByClause;
if (groupByDay) groupByClause = date => date.Date;
else if (groupByMonth) groupByClause = date => new { date.Year, date.Month};
else throw new NotSupportedException("Some option should be chosen");
var result = data.Where(d => d.Type == "Deposit")
.GroupBy(groupByClause)
.Select(g => new { DateKey = g.Key,
TotalDepositAmount = g.Sum(d => d.Amount),
DepositCount = g.Count(),
});
Of course this should be checked whether linq-2-entities will accept it.
Check the code mentioned in my question: Group by Weeks in LINQ to Entities. It shows how you can group your data by days and months. Let me know if you have any other questions.
This is probably a bug in MySQL connector. My solution for that was to place .ToList() just before .GroupBy().

Categories