Linq: group by + count for one column - c#

I have this query in SQL
select FileName, UploadDate, Status
from MyTable group by FileName, UploadDate, Status
this give me the correct output
FileName UploadDate Status
fuel 1.xls 2020-04-10 17:43:04.857 1
fuel 1.xls 2020-04-10 17:43:04.857 4
fuel 2.xls 2020-04-10 17:43:17.193 4
I can translate this query to LINQ
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new { x.Key.FileName, x.Key.UploadDate, x.Key.Status });
Now i wanna the same query but an additional column with the count of the 'Status' column
i Accomplish this in SQL with this query
select FileName, UploadDate, Status, count(Status) as 'StatusCount'
from MyTable group by FileName, UploadDate, Status
This give me the correct output
FileName UploadDate Status StatusCount
fuel 1.xls 2020-04-10 17:43:04.857 1 19
fuel 1.xls 2020-04-10 17:43:04.857 4 1
fuel 2.xls 2020-04-10 17:43:17.193 4 20
How to translate this additional "column count" into LINQ?i've tried several times different solutions but without success. Can someone help me please?

If you really mean count:
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new { x.Key.FileName, x.Key.UploadDate, x.Key.Status,
Count = x.Count() });
If you actually meant Sum:
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new { x.Key.FileName, x.Key.UploadDate, x.Key.Status,
Sum = x.Sum(e => e.Status) });

You just need to call the Count method on each IGrouping<> object in the Select method :
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new
{
FileName = x.Key.FileName,
UploadDate = x.Key.UploadDate,
Status = x.Key.Status,
Count = x.Count()
});

Related

Transform sql query to linq with groupBy and months

I have following query:
select concat(Left(DateName(month,[date]),3), ' ', Year([date])),
sum(TotalAttendants) as Total,
Sum(FemaleAttendants) as Women,
Sum(MaleAttendants) as Men
from dbo.Events
where IsDeleted=0 and EventTypeId = 1
group by concat(Left(DateName(month,[date]),3), ' ', Year([date]))
and I want to transform it to c# linq lambda expression.
I tried something like this:
var response = await _context.Events
.Where(x => !x.IsDeleted && x.EventTypeId == Domain.Enums.EventTypes.DirectBeneficiaries)
.GroupBy(x => x.Date)
.Select(x => new EventViewData
{
MaleAttendants = x.Sum(u => u.MaleAttendants),
FemaleAttendants = x.Sum(u => u.FemaleAttendants),
TotalAttendants = x.Sum(u => u.TotalAttendants),
MonthName = x.Key.ToString("00")
}).ToListAsync();
Im not getting same result as Im getting in my mssql management studio.
If you need more information about data structure and table Events here is the my another stackoverflow topic: link
I think you should group by month and year and do the formatting (concat, etc.) later (if needed at all).
select
...
from dbo.Events
..
group by Month([date]), Year([date]))
Then in linq you can:
...
.GroupBy(x => new { Year = x.Date.Year, Month = x.Date.Month } )
.Select(x => new // Note no type name
{
MaleAttendants = x.Sum(u => u.MaleAttendants),
FemaleAttendants = x.Sum(u => u.FemaleAttendants),
TotalAttendants = x.Sum(u => u.TotalAttendants),
Month = x.Key.Month,
Year = x.Key.Year
})
.ToListAsync() // Hit the db
.Select( x => new EventViewData
{
x.MaleAttendants
x.FemaleAttendants
x.TotalAttendants
MonthName = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(x.Month)
...
}
I don't think GetAbbreviatedMonthName is supported by EF so we need to do it after ToListAsync.

Joining and selecting only the first item based on clause

I have a Customers and an Orders database.
I need to make some statistics for the first order of all new customers and count the number of first orders from new clients by month.`
var date = new DateTime(now.Year - 1, now.Month, 1);
db.Orders
.Where(o => o.Customer.IsNew && o.OrderDate > date)
.GroupBy(o => new { o.OrderDate.Year, o.OrderDate.Month })
.Select(g => new NewCustomerStatsModel {
Month = g.Key.Month,
Year = g.Key.Year,
Count = g.Count()
})
.OrderBy(cs => cs.Year)
.ThenBy(cs => cs.Month)
.ToList();
This query provide me the number of orders for all new client but I need to get only the sum of the first order for each new Customer if the first order date is greater than the provided date.
Is it possible to do it with a query (and how) or am I forced to use AsEnumerable and do it in memory?
I need to make some statistics for the first order of all new customers
var clientFirstOrders = db.Customers.Where(c => c.IsNew)
.Select(c => new{
Customer = c,
FirstOrder = c.Orders.OrderBy(c => c.OrderDate).FirstOrDefault()
})
// might have to do (int?)FirstOrder.Id != null or something like that.
.Where(e => e.FirstOrder != null);
and count the number of first orders from new clients by month.
var clientCountByFirstOrderMonth = clientFirstOrders
.GroupBy(e => new { e.FirstOrder.OrderDate.Year, e.FirstOrder.OrderDate.Month })
.Select(g => new{g.Key.Year, g.Key.Month, Count = g.Count()});
I could find the solution.
With some appropriate index, the performances are pretty good.
It's probably not a perfect solution, but I couldn't update the entities because it's not my Library.
var date = new DateTime(now.Year - 1, now.Month, 1);
var result = db.Orders
.Where(o => o.Customer.IsNew && o.State != OrderState.Cancelled) // get all orders where the Customer is a new one.
.GroupBy(o => o.Customer.Id) // group by customer
.Select(g => g.OrderBy(o => o.OrderDate).FirstOrDefault()) // get the first order for every customer
.Where(o => o.OrderDate > date) // restrict to the given date
.GroupBy(o => new { o.OrderDate.Year, o.OrderDate.Month) }) // then group by month
.Select(g => new NewCustomerStatsModel {
Month = g.Key.Month,
Year = g.Key.Year,
Count = g.Count()
})
.OrderBy(g => g.Year)
.ThenBy(g => g.Month)
.ToList();

Count number of Mondays, Tuesdays etc from table using Linq

I'm trying to figure out how to count the number of Mondays, Tuesdays etc in a table using Linq and C#
Here is my sample data:
Status StatusDate
DELIVRD 2015-04-16 11:57:47.000
DELIVRD 2015-04-16 13:02:57.000
I know I need to use Group by to group the same Mondays, Tuesdays etc as 1.
My attempt:
var mondays = rad.SMSSentItems
.Where(x => (x.StatusDate.Value.DayOfWeek == DayOfWeek.Monday)
&& (x.Status == "DELIVRD"))
.ToList()
.Count;
You need to filter by the desired Status (DELIVRD) then group them by DayOfWeek of the status date
var weekDays = rad.SMSSentItems
.Where(x => x.Status == "DELIVRD")
.AsEnumerable()
.GroupBy(x => x.StatusDate.Value.DayOfWeek)
.Select(g => {
//Total items sent on this day of the week
var totalItemCount = g.Count();
//Total number if this distinct day of the week
var totalNumberOfDays = g.Select(x => x.StatusDate.Value.Date).Distinct().Count();
return new {
DayOfWeek = g.Key,
TotalItemCount = totalItemCount,
TotalNumberOfDays = totalNumberOfDays,
AverageItemPerDay = totalItemCount / totalNumberOfDays
};
})
.ToList();

Group By Sum Case subquery unnecessary linq

I have the following expression in linq
db.Produto.Where(x => db.ProdutoGroup
.GroupBy(y => new { y.idProduto })
.Select(y => y.Key.idProduto).Contains(x.id))
.GroupBy(x => new { x.composicao, x.atributos, x.idTipoProduto })
.Select(x => new {
qtdeProdutos = x.Count(),
idProduto = x.Max(y => y.id),
liberadosVenda = x.Sum(y => y.flagLiberadoVenda == true ? 1 : 0),
valoresValidados = x.Sum(y => y.flagValoresValidados == true ? 1 : 0)
}).ToList();
And it generates the following code sql
But that way, it gets 3 times slower than this sql statement that returns me the same thing, how to make linq generate this query?

Union of ordered sets in entity framework and then skip

I have a table of lessons, and I want to perform a text search over several fields of it. However the search should be ordered: for example lesson have a Keywords field and Description field. The search should give a priority over values found by Keywords. Everything should be also ordered by date but only after the priority is considered.
I'm also using ToPagedList() in the end from https://github.com/troygoode/PagedList (I think it just uses Skip() and Top() to manage pages)
This is what I have so far:
string[] word = /*Search words*/
var data = db.LessonsLearneds.Where(dbRecord => words.Any(word =>
dbRecord.SearchKeywords.StartsWith(word + ",") ||
dbRecord.SearchKeywords.Contains("," + word + ",") ||
dbRecord.SearchKeywords.EndsWith("," + word)))
.Select(x => new { Record = x, Order = 1 });
data = data.Union(
db.LessonsLearneds
.Where(dbRecord => words.Any(word => dbRecord.Title.Contains(word)))
.Select(x => new { Record = x, Order = 2 }));
data = data.Union(
db.LessonsLearneds
.Where(dbRecord => words.Any(word => dbRecord.Description.Contains(word)))
.Select(x => new { Record = x, Order = 3}));
data = data.Union(
db.LessonsLearneds
.Where(dbRecord => words.Any(word => dbRecord.Lesson.Contains(word)))
.Select(x => new { Record = x, Order = 4 }));
return data
.Distinct()
.OrderBy(x => x.Order)
.ThenByDescending(x => x.Record.Date)
.Select(x => x.Record)
.ToPagedList(pageNumber, pageSize);
Overall this code does almost what I want, except of Distinct(). Each union here can retrieve the same record, so I may receive it several times, and Distinct() does not forces the uniqueness because of virtual Order field. I cannot put Distinct after Select(x => x.Record) because of ToPagedList(..) which requires the set to be ordered (results in: The method 'Skip' is only supported for sorted input in LINQ to Entities. exception)
Any ideas?
I have one so far: to add Order field after I Distinct, but this means that I will have to write those Contains checks twice which I think is very ugly solution.
First, since you are projecting unique records due to the different Order value, replace the Union operator with Concat (which is the LINQ equivalent of the SQL UNION ALL).
string[] word = /*Search words*/
var data = db.LessonsLearneds.Where(dbRecord => words.Any(word =>
dbRecord.SearchKeywords.StartsWith(word + ",") ||
dbRecord.SearchKeywords.Contains("," + word + ",") ||
dbRecord.SearchKeywords.EndsWith("," + word)))
.Select(x => new { Record = x, Order = 1 });
data = data.Concat(
db.LessonsLearneds
.Where(dbRecord => words.Any(word => dbRecord.Title.Contains(word)))
.Select(x => new { Record = x, Order = 2 }));
data = data.Concat(
db.LessonsLearneds
.Where(dbRecord => words.Any(word => dbRecord.Description.Contains(word)))
.Select(x => new { Record = x, Order = 3}));
data = data.Concat(
db.LessonsLearneds
.Where(dbRecord => words.Any(word => dbRecord.Lesson.Contains(word)))
.Select(x => new { Record = x, Order = 4 }));
Then replace the Distinct with GroupBy using x.Record as a key and taking min Order for each grouping, and do the rest as in your current query:
return data
.GroupBy(x => x.Record)
.Select(g => new { Record = g.Key, Order = g.Min(x => x.Order) })
.OrderBy(x => x.Order)
.ThenByDescending(x => x.Record.Date)
.Select(x => x.Record)
.ToPagedList(pageNumber, pageSize);
You can replace Distinct with GroupBy and Select, like this:
return data
.GroupBy(x => x.Record)
.Select(g => g.OrderBy(x => x.Order).ThenByDescending(x => x.Record.Date).First())
.OrderBy(x => x.Order)
.ThenByDescending(x => x.Record.Date)
.Select(x => x.Record)
.ToPagedList(pageNumber, pageSize);
The unfortunate side effect of this approach is that you need to repeat OrderBy inside the first Select, but it should produce the results that you are looking for.

Categories

Resources