I have a collection of flyers with a property FlyerDate as datetime and I want to create a dropdown list with month and year, such "nov 2015, dec 2015, jan 2016"...
This is my code:
var monthList = flyers.Where(i => i.FlyerDate != DateTime.MinValue && i.FlyerDate.Year >= 2013)
.GroupBy(i => i.FlyerDate.Month)
.Select(g => new {
Month = g.Key,
Year = g.First(i => i.FlyerDate != DateTime.MinValue).FlyerDate.Year,
FullDate = String.Concat(DateTimeFormatInfo.CurrentInfo.GetMonthName(g.Key), " ", g.First(i => i.FlyerDate != DateTime.MinValue).FlyerDate.Year),
Total = g.Count(i => i.FlyerID > 0)
}
);
I would that the GroupBy works both on month and year as in my case the list contains only the first occurrence of each months. Any hints?
You need to group by an anonymous type containing both, the year and the month:
var monthList = flyers.Where(i => i.FlyerDate.Year >= 2013)
.GroupBy(i => new { i.FlyerDate.Year, i.FlyerDate.Month })
.Select(g => new {
Year = g.Key.Year,
Month = g.Key.Month,
FullDate = DateTimeFormatInfo.CurrentInfo.GetMonthName(g.Key.Month) + " " + g.Key.Year
});
Btw, if you want the abbreviated month-name as your desired result suggests you need to use DateTimeFormatInfo.CurrentInfo.GetAbbreviatedMonthName instead of GetMonthName.
I suspect your issue may be the GroupBy(i => i.FlyerDate.Month) clause. That grouping doesn't appear to respect year, so you'd be left with only 12 groups by the time you make it to your Select.
Incorporating the year into that GroupBy lambda could create a unique group for each month. Assuming your Month and Year are ints:
.GroupBy(i => (i.FlyerDate.Year * 12) + i.FlyerDate.Month)
might be a good place to start.
var query = flyers.GroupBy(f => f.FlyerDate.ToString("MMM yyyy"))
foreach (var group in query)
{
Console.WriteLine(group.Key);
foreach (Flyer f in group)
Console.WriteLine(f.FlyerDate);
}
Related
I'm working on an ASP.NET MVC app written in C#, and I need to display sum of how many times people have viewed videos captured in a SQL database. I'm able to do it in SQL:
SELECT
CAST([Date] AS DATE),
SUM([Views])
FROM
db
WHERE
[DATE] >= '2022-01-28'
AND [DATE] <= '2022-07-28'
GROUP BY
CAST([Date] AS DATE)
But, now the problem is to translate it to Linq in my controller, this is what I have so far (where start and end date are variables):
var data = (Context.Where(w => w.Date >= startDate && w.Date <= endDate)
.GroupBy(g => new { date = g.Date })
.Select(s => new { date = s.Key, sum = s.Sum(c => c.Views) })).ToList();
This will work but will group by date and time which has more rows.
For example, what my code returns:
date = 3/3/2022 3:00:00 AM, sum = 1
date = 3/3/2022 4:00:00 AM, sum = 2
date = 3/3/2022 5:00:00 AM, sum = 3
What I want:
date = 3/3/2022, sum = 6
You can simply use EntityFunctions.TruncateTime. I had in the past tried to put in a PR to allow using DateTime.Date, not sure if it ever landed.
var data = (Context
.Where(w => w.Date >= startDate && w.Date <= endDate)
.GroupBy(g => EntityFunctions.TruncateTime(g.Date))
.Select(s => new { date = s.Key, sum = s.Sum(c => c.Views) })
).ToList()
I have a customers table and I want to return count of rows inserted each month or Customers registered each month. The following code returns only month and record count but I want to record 0 if say for Jan no customers where registered. Thank you.
The following code returns:
Month 2 Count 15
Month 5 Count 11
Month 9 Count 82
I also want to return
Month 1 count 0
Month 3 count 0
so on..
My code:
var query = (from customers in context.customers
group customers by customers.RegisterDateTime.Month into g
select new
{ Month = g.Key, Count = g.Count() }
).ToList();
foreach (var data in query)
{
Console.WriteLine( "Month "+ data.Month +" Count "+ data.Count);
}
var query = (from m in Enumerable.Range(1, 12)
join c in context.customers on m equals c.RegisteredDateTime.Month into monthGroup
select new { Month = m, Count = monthGroup.Count() }
).ToList();
I assume that you want the range from the lowest month and the highest month.
I see no possiblity (but maybe there is?) to do it inside your query directly. I would add the "0" after the query to fill the gaps between the range with zeros.
So I would add the following code line after your query:
var lowestKey = result.Min(x => x.Month);
var highestKey = result.Max(x => x.Month);
query = query.Union(
Enumerable.Range(lowestKey, highestKey - lowestKey)
.Where(e => !result.Any(r => r.Month == e))
.Select(s => new { Month = s, Count = 0 })
).OrderBy(o => o.Month).ToList();
Since I don't have your complete code, this query maybe need some adjustment.
If you need another range, than you can simple change it.
My complete example look like this:
static void Main(string[] args)
{
// Initialize the list
var result = new []
{
new { Month = 2, Count = 15 },
new { Month = 5, Count = 11 },
new { Month = 9, Count = 82 }
}.ToList();
// Generate a List with 0 in Range
var lowestKey = result.Min(x => x.Month);
var highestKey = result.Max(x => x.Month);
result = result.Union(
Enumerable.Range(lowestKey, highestKey - lowestKey)
.Where(e => !result.Any(r => r.Month == e))
.Select(s => new { Month = s, Count = 0 })
).OrderBy(o => o.Month).ToList();
foreach (var data in result)
{
Console.WriteLine("Month " + data.Month + " Count " + data.Count);
}
Console.ReadKey();
}
Hope it helps,
var query = (from customers in context.customers
group customers by customers.RegisterDateTime.Month into g
select new
{ Month = g.Key, Count = g.Count(x=>x!=null) }
).ToList();
I have written a solution which basically adds missing date and sets the sales property for that date in my collection to 0 where it's missing like this:
int range = Convert.ToInt32(drange);
var groupedByDate = tr.Union(Enumerable.Range(1, Convert.ToInt32(range))
.Select(offset => new MyClassObject
{
Date = DateTime.Now.AddDays(-(range)).AddDays(offset),
Sales = 0
})).GroupBy(x => x.Date)
.Select(item => new MyClassObject
{
Sales = item.Sum(x => x.Sales),
Date = item.Key
})
.OrderBy(x => x.Date)
.ToList();
The first solution where the dates from DB were grouped by and they were missing looked like this:
var groupedByDate = tr
.GroupBy(x => x.TransactionDate.Date)
.Select(item => new MyClassObject
{
Sales = item.Sum(x => x.QuantityPurchased),
Date = item.Key.ToString("yyyy-MM-dd")
})
.OrderBy(x => x.Date)
.ToList();
I don't really like the way I did it in first solution, the code looks very messy and I honestly believe it can be written in a better manner..
Can someone help me out with this?
P.S. The first solution above that I've shown works just fine, but I would like to write something better which is more prettier to the eyes, and it looks quite messy (the first solution I wrote)...
How about generate the date range and then left join that with the result from your original query. And than set Sales to 0 when there is no match.
int range = 2;
var startDate = DateTime.Now;
var dates = Enumerable.Range(1, range)
.Select(offset => startDate.AddDays(-offset).Date);
var groupedByDate = from date in dates
join tmp in groupedByDate on date equals tmp.Date into g
from gr in g.DefaultIfEmpty()
select new MyClassObject
{
Sales = gr == null ? 0 : gr.Sales,
Date = date
};
Here is the easy way to do this:
var lookup = tr.ToLookup(x => x.TransactionDate.Date, x => x.QuantityPurchased);
var quantity = lookup[new DateTime(2017, 6, 29)].Sum();
If you want a range of dates then it's just this:
var startDate = new DateTime(2017, 6, 1)
var query =
from n in Enumerable.Range(0, 30)
let TransactionDate = startDate.AddDays(n)
select new
{
TransactionDate,
QuantityPurchases = lookup[TransactionDate].Sum(),
};
Simple.
I am basically trying to an "Archives" of dates grouped by Month and Year. However my problem is the dates in the database are strings...not my choice.
Here is my code to get that list of date groups
var dates = dbBlog.Data
.GroupBy(o => new
{
Month = Convert.ToDateTime(o.date).Month,
Year = Convert.ToDateTime(o.date).Year
})
.Select(g => new BlogArchiveClass
{
Month = g.Key.Month,
Year = g.Key.Year,
Total = g.Count()
})
.OrderByDescending(a => a.Year)
.ThenByDescending(a => a.Month)
.ToList();
But when I use it, I get this error:
LINQ to Entities does not recognize the method 'System.DateTime ToDateTime(System.String)
How would I be able to accomplish what I am doing with string dates from the database?
If you know the format of your string then you can probably do something that looks like this :
var dates = dbBlog.Data
Select(d => new
{
Month = d.Month.Substring(....),
Year = d.Year.Substring(....)
})
.GroupBy(o => new { o.Month,o.Year})
.Select(g => new BlogArchiveClass
{
Month = g.Key.Month,
Year = g.Key.Year,
Total = g.Count()
})
.OrderByDescending(a => a.Year)
.ThenByDescending(a => a.Month)
.ToList();
I have created a linq statement which seems to be working ok. I may or maynot have written it correctly however its returning my expected results.
var grouped = RewardTransctions.GroupBy(t => new
{
t.PurchaseDate.Value.Month
}).Select(g => new TransactionDetail()
{
Month =
g.Where(w=>w.EntryType==1).Select(
(n =>
n.PurchaseDate.Value.Month))
.First(),
TransactionAmount = g.Count()
});
Now the results are returning 5 values grouped by months. Is it possible to add the 7 other missing months with a TransactionAmount = 0 to them?
The reason for my madness is I am trying to bind these values to a chart and having my x axis based on months. Currently its only showing the 5 months of records. If my data doesnt return any value for a month I some how want to add in the 0 value.
Any suggestions?
It's very simple if you use .ToLookup(...).
var lookup =
(from w in RewardTransctions
where w.EntryType == 1
select w).ToLookup(w => w.PurchaseDate.Value.Month);
var grouped =
from m in Enumerable.Range(1, 12)
select new TransactionDetail()
{
Month = m,
TransactionAmount = lookup[m].Count(),
};
How's that for a couple of simple LINQ queries?
When you're using LINQ to Objects, this query should do the trick:
var grouped =
from month in Enumerable.Range(1, 12)
select new TransactionDetail()
{
Month = month,
TransactionAmount = RewardTransactions
.Where(t => t.PurchaseDate.Value.Month == month).Count()
};
When RewardTransactions however is an IQueryable, you should first call AsEnumerable() on it.
Why not do it just like this:
var grouped =
RewardTransctions.GroupBy(t => t.PurchaseDate.Value.Month).Select(
g => new TransactionDetail { Month = g.Key, TransactionAmount = g.Count() }).ToList();
for (var i = 1; i <= 12; ++i)
{
if (grouped.Count(x => x.Month == i) == 0)
{
grouped.Add(new TransactionDetail { Month = i, TransactionAmount = 0 });
}
}
It's not entirely LINQ, but straight forward. I also simplified your LINQ query a bit ;-)
I guess If you do not use an anonymoustype(var), but create a custom type and do a .ToList() on your query that you can use .Add() on your list and bind the chart to the list.