I need to write a SQL query in Entity Framework so as to get the total expense amount in array format (i.e I need to group the amounts month-wise).
I have tried this Entity Framework query but it didn't work out:
double[] Amountdate;
using (IncomeExpenseManagementDBEntities incomeExpenseManagementDB = new IncomeExpenseManagementDBEntities())
{
Amountdate = incomeExpenseManagementDB
.Expenses
.Select(e => e.Amount)
.Where(e => e.userID == IncomeExpenseTool.user.Id)
.GroupBy(e => e.Date.Month);
}
Appreciate any input into this
You need to do the Select last and then you'll need to aggregate the value by taking the Sum.
var amountsByMonth = incomeExpenseManagementDB.Expenses
.Where(e => e.userID == IncomeExpenseTool.user.Id)
.GroupBy(e => e.Date.Month)
.Select(grp => new
{
Month = grp.Key,
TotalAmount = grp.Sum(x => x.Amount)
})
.ToList();
That will result in the sum of the amount for each month that is represented in your DB (in other words if you have no data for the month of March you will not get a result for March).
Related
What I am trying to do is get the top 10 most sold Vegetables by grouping them by an Id passed by parameter in a function and ordering them by the sum of their Quantity. I don't know how to use SUM or (total) quite yet but I thought I'd post it here seeking help. If you need me offering you anything else I will be ready.
This is my code:
TheVegLinQDataContext db = new TheVegLinQDataContext();
var query =db.OrderDetails.GroupBy(p => p.VegID)
.Select(g => g.OrderByDescending(p => p.Quantity)
.FirstOrDefault()).Take(10);
And this is an image of my database diagram
Group orders by Vegetable ID, then from each group select data you want and total quantity:
var query = db.OrderDetails
.GroupBy(od => od.VegID)
.Select(g => new {
VegID = g.Key,
Vegetable = g.First().Vegetable, // if you have navigation property
Total = g.Sum(od => od.Quantity)
})
.OrderByDescending(x => x.Total)
.Select(x => x.Vegetable) // remove if you want totals
.Take(10);
Since this is not clear that you are passing what type of id as function parameter, I'm assuming you are passing orderId as parameter.
First apply where conditions then group the result set after that order by Total sold Quantity then apply Take
LINQ query
var result = (from a in orderdetails
where a.OrderId == orderId //apply where condition as per your needs
group a by new { a.VegId } into group1
select new
{
group1.Key.VegId,
TotalQuantity = group1.Sum(x => x.Quantity),
group1.FirstOrDefault().Vegitable
}).OrderByDescending(a => a.TotalQuantity).Take(10);
Lamda (Method) Syntax
var result1 = orderdetails
//.Where(a => a.OrderId == 1) or just remove where if you don't need to filter
.GroupBy(x => x.VegId)
.Select(x => new
{
VegId = x.Key,
x.FirstOrDefault().Vegitable,
TotalQuantity = x.Sum(a => a.Quantity)
}).OrderByDescending(x => x.TotalQuantity).Take(10);
I am working on an application in which I have to store play history of a song in the data table. I have a table named PlayHistory which has four columns.
Id | SoundRecordingId(FK) | UserId(FK) | DateTime
Now i have to implement a query that will return the songs that are in trending phase i.e. being mostly played. I have written the following query in sql server that returns me data somehow closer to what I want.
select COUNT(*) as High,SoundRecordingId
from PlayHistory
where DateTime >= GETDATE()-30
group by SoundRecordingId
Having COUNT(*) > 1
order by SoundRecordingId desc
It returned me following data:
High SoundRecordingId
2 5
2 3
Which means Song with Ids 5 and 3 were played the most number of times i.e.2
How can I implement this through Linq in c#.
I have done this so far:
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId)
.Take(20)
.ToList();
It returns me list of whole table with the count of SoundRecording objects but i want just count of the most repeated records.
Thanks
There is an overload of the .GroupBy method which will solve your problem.
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId, (key,values) => new {SoundRecordingID=key, High=values.count()})
.Take(20)
.ToList();
I have simply added the result selector to the GroupBy method call here which does the same transformation you have written in your SQL.
The method overload in question is documented here
To go further into your problem, you will probably want to do another OrderByDescending to get your results in popularity order. To match the SQL statement you also have to filter for only counts > 1.
DateTime d = DateTime.Now;
var monthBefore = d.AddMonths(-1);
var list =
_db.PlayHistories
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId, (key,values) => new {SoundRecordingID=key, High=values.count()})
.Where(x=>x.High>1)
.OrderByDescending(x=>x.High)
.ToList();
I like the 'linq' syntax it's similar to SQL
var query = from history in _db.PlayHistories
where history.DateTime >= monthBefore
group history by history.SoundRecordingId into historyGroup
where historyGroup.Count() > 1
orderby historyGroup.Key
select new { High = historyGroup.Count(), SoundRecordingId = historyGroup.Key };
var data = query.Take(20).ToList();
You´re allmost done. Just order your list by the count and take the first:
var max =
_db.PlayHistories
.OrderByDescending(x=>x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x=>x.SoundRecordingId)
.OrderByDescending(x => x.Count())
.First();
This gives you a single key-value-pair where the Key is your SoundRecordingId and the value is the number of its occurences in your input-list.
EDIT: To get all records with that amount chose this instead:
var grouped =
_db.PlayHistories
.OrderByDescending(x => x.SoundRecordingId)
.Where(t => t.DateTime >= monthBefore)
.GroupBy(x => x.SoundRecordingId)
.Select(x => new { Id = x.Key, Count = x.Count() }
.OrderByDescending(x => x.Count)
.ToList();
var maxCount = grouped.First().Count;
var result = grouped.Where(x => x.Count == maxCount);
This solves the problem by giving you what you asked for. Your query in LINQ, returning just the play counts.
var list = _db.PlayHistories.Where(x => x.DateTimeProp > (DateTime.Now).AddMonths(-1))
.OrderByDescending(y => y.SoundRecordingId.Count())
.ThenBy(z => z.SoundRecordingId)
.Select(xx => xx.SoundRecordingId).Take(20).ToList();
I have Mysql database with ~1 500 000 entities. When I try to execute below statement using EF Core 1.1 and Mysql.Data.EntityFrameworkCore 7.0.7-m61 it takes about 40minutes to finish:
var results = db.Posts
.Include(u => u.User)
.GroupBy(g => g.User)
.Select(g => new { Nick = g.Key.Name, Count = g.Count() })
.OrderByDescending(e => e.Count)
.ToList();
On the other hand using local mysql-cli and below statement, takes around 16 seconds to complete.
SELECT user.Name, count(*) c
FROM post
JOIN user ON post.UserId = user.Id
GROUP BY user.Name
ORDER BY c DESC
Am i doing something wrong, or EF Core performance of MySql is so terrible?
Your queries are doing different things. Some issues in your LINQ-to-Entities query:
You call Include(...) which will eagerly load the User for every item in db.Posts.
You call Count() for each record in each group. This could be rewritten to count the records only once per group.
The biggest issue is that you're only using the Name property of the User object. You could select just this field and find the same result. Selecting, grouping, and returning 1.5 million strings should be a fast operation in EF.
Original:
var results =
db.Posts
.Include(u => u.User)
.GroupBy(g => g.User)
.Select(g => new { Nick = g.Key.Name, Count = g.Count() })
.OrderByDescending(e => e.Count)
.ToList();
Suggestion:
var results =
db.Posts
.Select(x => x.User.Name)
.GroupBy(x => x)
.Select(x => new { Name = x.Key, Count = x.Count() })
.OrderByDescending(x => x.Count)
.ToList();
If EF core still has restrictions on the types of grouping statements it allows, you could call ToList after the first Select(...) statement.
I am calculating totals per month for each country. I have managed to group data by country, but I get error
An item with the same key has already been added.
when trying to put monthly totals into inner dictionary:
var totalPerMonth = data.AsEnumerable()
.Select(x => new
{
Date = Convert.ToDateTime(x.ItemArray[0]).ToString("yyyy-MM"),
Country = x.ItemArray[1],
Revenue = x.ItemArray[2]
})
.GroupBy(x => x.Country)
.ToDictionary(x => x.Key, x => x.ToDictionary(p => p.Date,////this is not unique/// p => Convert.ToDouble(p.Revenue)));
how to group it to make Date key unique?
You can either use ToLookup instead of ToDictionary to allow several values for same date.
Or you can use grouping to get unique dates only (assume you want to calculate totals for each month, so use Sum of revenue for each date group dg ):
var totalPerMonth = data.AsEnumerable()
.Select(x => new {
Date = Convert.ToDateTime(x.ItemArray[0]).ToString("yyyy-MM"),
Country = x.ItemArray[1],
Revenue = Convert.ToDouble(x.ItemArray[2]) // convert here
})
.GroupBy(x => x.Country)
.ToDictionary(
g => g.Key,
g => g.GroupBy(x => x.Date).ToDictionary(dg => dg.Key, dg => dg.Sum(x => x.Revenue))
);
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().