having trouble with groupby and sum - c#

Good evening guys. I'm currently developing a web application with the use of asp.net mvc and C# and I'm having trouble with using the groupby() and sum(). I'm attaching a screenshot of the output to better understand the situation i'm in.
what I want to do here is to group all with the same description and the total is to be summed. I have tried groupby and sum but i cant seem to arrive at the desired output. Can anyone please give me some good points on how to do this?
here is the codes i have tried in the manager:
public IEnumerable<TuitionFeeRevenuePerProgramVM_L> getDetails(string orgUnit, short? academicYear, byte? academicPeriod)
{
var depts = getAllOrgUnitDepByOrgUnitSchID(orgUnit);
var revenue = tuitionFee.SearchFor(x => x.AcademicPeriod == academicPeriod && x.AcademicYear == academicYear && depts.Contains(x.Course.OrgUnitCode) && x.IsLatest == true, "AssessmentItem.ChartOfAccount,AssessmentItem.ChartOfAccount.CostCenter,Course,Course.CourseSchedule,Course.OrgUnit,Course.OrgUnit.OrgUnitHierarchy1,Course.OrgUnit.OrgUnitHierarchy1.OrgUnit").ToList();
var r = revenue.GroupBy(x => x.Course.OrgUnitCode).Select(x => x.First()).ToList(); //this line seems to group them but the output is the first total amount not the overall total
var rev = revenue.Select(x => new TuitionFeeRevenuePerProgramVM_L
{
CostCenterCode = x.AssessmentItem.ChartOfAccount.CostCenterID,
CostCenterDesc = x.Course.OrgUnit.OrgUnitDesc,
Subjects = getNumberOfSubjects(x.Course.OrgUnitCode, x.AcademicYear, x.AcademicPeriod),
Enrolees = revenue.Where(y => y.Course.OrgUnitCode == x.Course.OrgUnitCode).Select(z => z.Course.CourseSchedule.Sum(a => a.EnrolledStudents)).Sum(b => b.Value),//(int)x.Course.CourseSchedule.Sum(y => y.EnrolledStudents),
Total = (decimal)((x.Course.CourseSchedule.Sum(y => y.EnrolledStudents) * x.Amount) * x.Course.ContactHrs) /*(revenue.Where(y => y.Course.OrgUnitCode == x.Course.OrgUnitCode).Select(z => z.Course.CourseSchedule.Sum(a => a.EnrolledStudents)).Sum(b => b.Value) * revenue.Where(d => d.Course.OrgUnitCode == x.Course.OrgUnitCode).Sum(e=>e.Amount)) * revenue.Where(f => f.Course.OrgUnitCode == x.Course.OrgUnitCode).Sum(g=>g.Course.ContactHrs) //*/
});
return rev.ToList();
}
the controller,model and view is as is since all the logic involved will be placed in the manager. I have tried to use Sum() when returning but it gets an error since the controller receives a list and not a decimal.

from d in data
group d by d.Description into g
from gg in g
select new {Description = gg.Key, Sum = gg.Sum(ggg=>ggg.TotalCost)}
Something like that without seeing the data class

Related

Using GroupBy to compute average or count based on the whole data until the corresponding date

I have the AssessmentItems DB object which contains the items about: Which user evaluated (EvaluatorId), which submission (SubmissionId), based on which rubric item (or criteria)(RubricItemId) and when (DateCreated).
I group by this object by RubricItemId and DateCreated to get compute some daily statistics based on each assessment criteria (or rubric item).
For example, I compute the AverageScore, which works fine and returns an output like: RubricItem: 1, Day: 15/01/2019, AverageScore: 3.2.
_context.AssessmentItems
.Include(ai => ai.RubricItem)
.Include(ai => ai.Assessment)
.Where(ai => ai.RubricItem.RubricId == rubricId && ai.Assessment.Submission.ReviewRoundId == reviewRoundId)
.Select(ai => new
{
ai.Id,
DateCreated = ai.DateCreated.ToShortDateString(),//.ToString(#"yyyy-MM-dd"),
ai.CurrentScore,
ai.RubricItemId,
ai.Assessment.SubmissionId,
ai.Assessment.EvaluatorId
})
.GroupBy(ai => new { ai.RubricItemId, ai.DateCreated })
.Select(g => new
{
g.Key.RubricItemId,
g.Key.DateCreated,
AverageScore = g.Average(ai => ai.CurrentScore),
NumberOfStudentsEvaluating = g.Select(ai => ai.EvaluatorId).Distinct().Count(),
}).ToList();
What I want to do is to compute the average until that day. I mean instead of calculating the average for the day, I want to get the average until that day (that is, I want to consider the assessment scores of the preceding days). The same why, when I compute NumberOfStudentsEvaluating, I want to indicate the total number of students participated in the evaluation until that day.
One approach to achieve this could be to iterate through the result object and compute these properties again:
foreach (var i in result)
{
i.AverageScore = result.Where(r => r.DateCreated <= i.DateCreated).Select(r => r.AverageScore).Average(),
}
But, this is quite costly. I wonder if it is possible to tweak the code a bit to achieve this, or should I start from scratch with another approach.
If you split the query into two halves, you can compute the average as you would like (I also computed the NumberOfStudentsEvaluating on the same criteria) but I am not sure if EF/EF Core will be able to translate to SQL:
var base1 = _context.AssessmentItems
.Include(ai => ai.RubricItem)
.Include(ai => ai.Assessment)
.Where(ai => ai.RubricItem.RubricId == rubricId && ai.Assessment.Submission.ReviewRoundId == reviewRoundId)
.Select(ai => new {
ai.Id,
ai.DateCreated,
ai.CurrentScore,
ai.RubricItemId,
ai.Assessment.SubmissionId,
ai.Assessment.EvaluatorId
})
.GroupBy(ai => ai.RubricItemId);
var ans1 = base1
.SelectMany(rig => rig.Select(ai => ai.DateCreated).Distinct().Select(DateCreated => new { RubricItemId = rig.Key, DateCreated, Items = rig.Where(b => b.DateCreated <= DateCreated) }))
.Select(g => new {
g.RubricItemId,
DateCreated = g.DateCreated.ToShortDateString(), //.ToString(#"yyyy-MM-dd"),
AverageScore = g.Items.Average(ai => ai.CurrentScore),
NumberOfStudentsEvaluating = g.Items.Select(ai => ai.EvaluatorId).Distinct().Count(),
}).ToList();

Refactor linq query and return a single after grouping

I have the following linq query:
var q = Queryable.First(Queryable.Select(r.Find((Expression<Func<Application, bool>>)((Application x) =>
x.IsValid && x.CreatedOn < startDate && x.CreatedOn >= endDate))
.GroupBy((Expression<Func<Application, int>>)((Application x) => (int)1))
, (IGrouping<int, Application> g) => new
{
AvgApplicationTime = (int)Math.Round(g.Average((Application i) => i.ApplicationTime)),
AvgDecisionTime = (int)Math.Round(g.Average((Application i) => i.DecisionTime)),
ApprovalRate = 100.0 * g.Count<Application>((Application i) => i.IsAccepted) / g.Count<Application>()
}));
return new ApplicationStats((int)q.AvgApplicationTime, (int)q.AvgDecisionTime, q.ApprovalRate);
I'm pretty sure the query can be simplified and have set out to do so.
My approach is always to try and understand a query I'm working with, rather than just hacking bits about and hoping it works.
In doing so, I like to break it down into parts before reassembling, which I've tried to do here:
var q1 = r.Find(x => x.CreatedOn < startDate && x.CreatedOn >= endDate);
var q2 = q1.Select(x => x.Applicant);
var q3 = q2.GroupBy(x => 1, (g) => new ApplicationStats(1, 1, 0.75)); // I've omitted some bits for brevity
// q4 = ??? // Needs to return a single Applicationstats()
Unfortunately, I haven't managed to figure out the final piece of the puzzle, despite using a combination of .Select(), .SelectMany(), .First().
I know the answer will prove simple but any help is appreciated.
There are numerous issues with the new query.
bommelding was correct - the final bit of the puzzle was q4 = q3.First(),
However, this will not work without other changes to this statement:
var q3 = q2.GroupBy(x => 1, (key, g) => new
{
AvgFormCompletionTime = 360,
AvgProcessingTime = 90,
ApprovalRate = .75
});
Firstly 'key' was added making (g), (key, g).
Secondly, linq only accepts parameterless constructors, which the one on my question wasn't.
To convert the final anonymous property, I added this statement back in at the end:
return new ApplicationStats(q4.AvgFormCompletionTime, q4.AvgProcessingTime, q4.ApprovalRate);

Get product of items in list C# linq

I have a list with people with integerIds who share Tokens I am trying to get the products of each pair of friends id numbers. so friend id 2 and 4 would result in 8 etc.
var FriendsWhoShareTheMostTokens = FriendsWithTheirSharedTokensAmount.Select(x => new
{
FriendIdA = x.FriendTo.FriendID,
FriendIdB = x.FriendFor.FriendID,
SharedCountNumber = x.SharedCount
})
.Where(x => x.SharedCountNumber == FriendsWithTheirSharedTokensAmount.Max(y => y.SharedCount))
.ToList();
// need some code here**
foreach (var pairOffriendsWhoSharetheMostTokens in FriendsWhoShareTheMostTokens)
{
}
Can I accomplish this with Linq or what is the best way to accomplish this?
Edit
The answer is simple
var ProductofGreatestTokenSharers = FriendsWhoShareTheMostTokens.Select(x => new
{
ProductOfIds = x.FriendIdA * x.FriendIdB
}
);
It's really hard to understand what you are trying to achieve with your example.
However, if you want to have the id's and the product, and also obtain the highest SharedCountNumber.
You can probably just do the following
var someResult = someList.Select(x => new
{
FriendIdA = x.FriendTo.FriendID,
FriendIdB = x.FriendFor.FriendID,
SharedCountNumber = x.FriendTo.FriendID * x.FriendFor.FriendID
}) // Projection
.OrderByDescending(x => x.SharedCountNumber) // order the list
.FirstOrDefault(); // get the first
if (someResult != null)
{
Debug.WriteLine($"A : {someResult.FriendIdA}, B : {someResult.FriendIdB}, Product : {someResult.SharedCountNumber}");
}

Order by Descending with multiple comparisons

I want to order the result with multiple comparisons like (by paid users, updated date, created date).
var order = _vrepository.Details()
.Where(od => od.Order.Id != null
&& od.PlanName.ToLower().Contains("DP".ToLower())
&& od.Order.Status == true)
.OrderByDescending(od => od.ValidityTill)
.Select(ord => ord.Order.Id.Value);
The above one I am getting the paid users.
var updatedList = _repository.GetUsers()
.Where(c => c.UpdatedDate != null
&& fresh <= c.UpdatedDate)
.Select(c => c.Id);
This second query I am getting updated users.
var UsersListByCreatedDate = _repository.GetUsers()
.Where(c => fresh <= c.CreatedDate)
.Select(c => c.Id);
The third one is for get the users by created date. Then I am selecting the Id (an Integer)
lstId = order.ToList();
lstUpdatedUsersId = updatedList ToList();
lstCreatedDatewiseUsers = UsersListByCreatedDate.ToList();
To show the ordered list I did the following code:
Func<IQueryable<User>, IOrderedQueryable<User>> orderingFunc = query =>
{
if (order.Count() > 0)
return query.OrderByDescending(rslt => lstId .Contains(rslt.Id))
.ThenByDescending(rslt => lstUpdatedUsersId .Contains(rslt.Id))
.ThenByDescending(rslt => lstCreatedDatewiseUsers.Contains(rslt.Id));
else
return query.OrderByDescending(rslt => lstUpdatedUsersId .Contains(rslt.Id))
.ThenByDescending(rslt => rslt.UpdatedDate);
};
But I am getting result as One Paid user, some Created user and the date is before 3 years like wise. I want to get the exact order is as follows
Ordered User
Updated User
Created User
Please help me to solve this issue. Hope I will get correct result.
I have tried this snipepd of code according to your
public class Program
{
public static void Main()
{
var l1= new List<int>{1,2,3};
var l2= new List<int>{4,5,6};
var l3= new List<int>{7,8,9};
var testList = new List<Test>{new Test{Id=9,Name="test1"},new Test{Id=11,Name="test1"},new Test{Id=5,Name="test1"},new Test{Id=7,Name="test1"},new Test{Id=2,Name="test1"}};
var orderedList= testList.OrderByDescending(e=> l1.Contains(e.Id)).ThenByDescending(e=> l2.Contains(e.Id)).ThenByDescending(e=> l3.Contains(e.Id));
Console.WriteLine(string.Join("\t", orderedList.Select(e=> e.Id).Cast<int>().ToArray()));
// it prompts 2 5 9 7 11
}
}
public class Test{
public int Id{get; set;}
public string Name{get; set;}
}
and it works as you expected. Maybe I haven't understood what you are trying to achieve.
Maybe the function that you are using to sort items is in the wrong branch of the if statment, check for if (order.Count() > 0)

C# Retrieve Common Records by LINQ

I have a database with the table that keeps user_ids and tag_ids. I want to write a function which takes two user_ids and returns the tag_ids that both users have in common.
These are the sample rows from the database:
User_id Tag_id
1 100
1 101
2 100
3 100
3 101
3 102
What I want from my function is that when I call my function like getCommonTagIDs(1, 3), it should return (100,101). What I did so far is that I keep the rows which are related to user_id in two different lists and then using for loops, return the common tag_ids.
using (TwitterDataContext database = TwitterDataContext.CreateTwitterDataContextWithNoLock())
{
IEnumerable<Usr_Tag> tags_1 = database.Usr_Tags.Where(u => u.User_id == userID1).ToList();
IEnumerable<Usr_Tag> tags_2 = database.Usr_Tags.Where(u => u.User_id == userID2).ToList();
foreach (var x in tags_1)
{
foreach (var y in tags_2) {
if (x.Tag_id == y.Tag_id) {
var a =database.Hashtags.Where(u => u.Tag_id==x.Tag_id).SingleOrDefault();
Console.WriteLine(a.Tag_val);
}
}
}
}
What I want to ask is that, instead of getting all rows from database and searching for the common tag_ids in the function, I want to get the common tag_ids directly from database with LINQ by making the calculations on the database side. I would be grateful if you could help me.
This is the SQL that I wrote:
SELECT [Tag_id]
FROM [BitirME].[dbo].[User_Tag]
WHERE USER_ID = '1' AND Tag_id IN (
SELECT [Tag_id]
FROM [BitirME].[dbo].[User_Tag]
where USER_ID = '3')
What you want is the "Intersection" of those two sets:
var commonTags = database.Usr_Tags.Where(u => u.User_id == userID1).Select(u => u.Tag_id)
.Intersect(database.Usr_Tags.Where(u => u.User_id == userID2).Select(u => u.Tag_id));
And voila, you're done.
Or, to clean it up a bit:
public static IQueryable<int> GetUserTags(int userId)
{
return database.Usr_Tags
.Where(u => u.User_id == userId)
.Select(u => u.Tag_id);
}
var commonTags = GetUserTags(userID1).Intersect(GetUserTags(userID2));
Here's one way to do it:
int[] users = new int[] {1,3}; // for testing
database.Ustr_Tags.Where(t => users.Contains(t.User_id))
.GroupBy(t => t.Tag_id)
.Where(g => users.All(u => g.Any(gg=>gg.User_id == u))) // all tags where all required users are tagged
.Select(g => g.Key);
One benefit of this one is it can be used for any number of users (not just 2).
If i got it right, query like this is maybe what you need
var q = from t in database.Usr_Tags
//all Usr_Tags for UserID1
where t.User_Id == userID1 &&
//and there is a User_tag for User_ID2 with same Tag_ID
database.User_Tags.Any(t2=>t2.User_ID==userID2 && t2.Tag_ID==t.Tag_ID)
select t.Tag_Id;
var commonTags = q.ToList();

Categories