How to use LINQ to get multiple totals - c#

Let's say I have the following data in a database.
class Data
{
public int Category { get; set; }
public int ValueA { get; set; }
public int ValueB { get; set; }
}
How can I write a LINQ query to get the sum of ValueA and also the sum of ValueB for all rows with Category == 1?
I know I could load all the data and then use Sum on the loaded data but would prefer to total them in the database.
I know I can use group by but I'm not grouping by anything. I just want these two totals from the data.

If you are using EF, you can try this:
var result= context.Data.Where(d=>d.Category == 1)
.GroupBy(d=>d.Category)
.Select(g=>new {
SumA=g.Sum(d=>d.ValueA),
SumB=g.Sum(d=>d.ValueB)
}
);

You can group by a constant
var result = from d in context.Data
where d.Category == 1
group d by 1 into g
select
{
ASum = g.Sum(d => d.ValueA),
BSum = g.Sum(d => d.ValueB)
};
Or as octavioccl pointed out you can also group by Category since it will be a constant value because of the where clause. But using a constant is how you can achieve what you want in the general case.

Related

How to calculate sum for specific property without grouping main list data

I'm fetching Invoices from database and I want to return all invoices without grouping them!
I don't want to group them since If there are 100 invoices I want to return all of them 100, considering that I want to get Sum of Amount.
So it is perfectly fine to repeat same Total for multiple invoices if their sum is the same, so basically I want to calculate sum of Amount of each invoice item and group by CompanyId, PackageId, BankId, PayMethod only if it's possible?
-- Read code comments --
var result = await _dbContext.Invoices
.Where(p => p.CheckDate >= startDate && p.CheckDate <= endDate)
.Select(p => new DemoDto()
{
CompanyId = p.CompanyId,
Title = p.Title,
Price = p.Price
Total = p.Sum(p => p.Amount).ToString(), // Can I sum here and group by fields I mentioned above? without grouping all data set because I want to keep all 100 records if I received all 100 from database
})
.ToListAsync();
This query obliviously doesn't work because it says
Invoice does not contain definition for Sum and no accessible method..
DemoDto looks like this:
public class DemoDto
{
public string CompanyId {get;set;}
public string Title {get;set;}
public decimal Price {get;set;}
public decimal Amount {get;set;}
}
Invoice class looks like this:
public class Invoice
{
public string CompanyId { get; set; }
public int PackageId {get; set;}
public int BankId {get;set;}
public int PayMethod {get;set;}
public string Title { get; set; }
public decimal Price { get; set; }
public decimal Amount { get; set; }
}
what I'm missing here?
How can I achieve this?
Thanks guys
Cheers
Fetch all the invoices from the database:
var invoices = await _dbContext.Invoices
.Where(p => p.CheckDate >= startDate && p.CheckDate <= endDate)
.ToListAsync();
Group the in-memory results using Linq-To-Object:
var result = invoices?
.GroupBy(p => new { p.CompanyId, p.PackageId, p.BankId, p.PayMethod })
.SelectMany(x => x.Select(y =>
new DemoDto
{
CompanyId = y.CompanyId,
Title = y.Title,
Price = y.Price,
Total = x.Sum(z => z.Price)
}))
.ToList();
If you want to perform the grouping in the database for some reason, you should execute a raw SQL query or a stored procedure rather than relying on the ORM to generate some magic (and most probably inefficient) query for you.

Create Poll in C# (windows form)

I want create a poll. i have a table for questions(Fields: ID,Question) and another for answers(Fields: ID,QuestionID,Answer). There is a table for the results(fields: QuestionID,AnswerID,UserID).i want to show the Percent of responding to any item in datagridview.
for example when i enter question id, datagridview show:
Choose question ID:1
Option...........Percent
1 ------------------------- 30
2 -------------------------- 20
3---------------------------50
4-------------------------- 10
this is my code But the result does not show it:
int a = Convert.ToInt32(textBox1.Text);
var q = (from s in Session.DB.PoolUsers
where s.PoolQID == a
select s);
var qq = (from c in q
group c by c.PoolAID into agroups
select agroups.Key);
var qqq = (from c in qq
select c).Count();
MessageBox.Show(qqq.ToString());
And there is my classes:
public partial class PoolA //For answers
{
public int Id { get; set; }
public string Answer { get; set; }
public string QuestionID { get; set; }
}
public partial class PoolQ //Questions
{
public int Id { get; set; }
public string Question { get; set; }
public string AnswerID { get; set; }
public string Status { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
}
public partial class PoolUser //resaults
{
public int Id { get; set; }
public int PoolQID { get; set; }
public int PoolAID { get; set; }
public int UserID { get; set; }
}
This is not the optimal way I would do it, I would highly consider you, to change your entities.
Here is the solution:
int questionId = 1;
var questionAnswers = list.Where(elem => elem.PoolQID == questionId);
int questionAnswersCount = questionAnswers.Count();
var answersPrecentage = questionAnswers
.GroupBy(elem => elem.PoolAID)
.ToDictionary(grp => grp.Key, grp => (grp.Count() * 100.0 / questionAnswersCount));
.NET Fiddle Link
I don't know your intentions but I think you should redesign the schema (not that it not doable as it is) for maximum efficiency. You call your PoolUser table your results table but it is your answers log at best. you need a table that has the results processed already such as would be done in a DWH kind of situation.
If you have a results summary table you could work out vertical sums groups all day without the extra performance of joining tables etc. for ad hoc data. I reckon you'd be running this a lot to go over the results, so it sounds like it is a better idea to store the summarized information for reporting, performance etc.
as for the current state of the problem:
var qq = (from c in q
group c by c.PoolAID into agroups
select agroups.Key);
agroups has the answerIds grouped up for the question number you typed in.
then you are just showing the row count of the grouped up answers which shows you 2:
var qqq = (from c in qq
select c).Count();
you are telling me that your messagebox shows the number "2"
this means that people only selected 2 options to answer whatever question id you put in. you don't know whether the answer is correct or not. the last query "qqq" you have, you have the count outside so it only returns the row count. you don't want the row count. you want the answerID count after the grouping.
from c in qq
select count(c)
this is the total answers for the questionID you typed in.
now you need the correct answers in order to calculate the percentage. this is the query missing. you have gone 75% of the way. just finish it off. hopefully this is sufficient detail and not too much text to read.
The problem was solved and I got the following code. I also draw a graph based on
private void button1_Click(object sender, EventArgs e)
{
var list = (from c in Session.DB.PoolUsers
select c).ToList();
int questionId = Convert.ToInt32(textBox1.Text);
var questionAnswers = list.Where(elem => elem.PoolQID == questionId);
int questionAnswersCount = questionAnswers.Count();
var answersPrecentage = questionAnswers
.GroupBy(elem => elem.PoolAID)
.ToDictionary(grp => grp.Key, grp => (grp.Count() * 100.0 / questionAnswersCount));
dataGridView1.DataSource = answersPrecentage.ToArray();
//Draw Chart From DataGridview:
chart1.DataBindTable(answersPrecentage);
}
And works properly
Thanks all :)

Count the items of a List according to their status

My question is simple, but I can't find a solution in my search.
Well, I have here these Classes and Lists:
public class Table
{
public string TableName { get; set; }
public string Status { get; set; }
public List<Request> Requests { get; set; }
}
public class Request
{
public string ProductName { get; set; }
public double ProductPrice { get; set; }
public int Quantity { get; set; }
}
public List<Table> Tables = new List<Table>();
The tables can have two status: Unoccupied and Occupied. What I need is to count all the tables presents in the Table List and separate them according to Status, but I don't know how to do this. For example, I have two tables with their status occupied and three tables with their status unoccupied. I need the output like: You have 2 tables occupied and 3 tables unoccupied. I think I need to use a while loop, but I don't know how to count separate.
Just use GroupBy
var results = Tables.GroupBy(t => t.Status)
.Select(g => new
{
Status = g.Key,
Count = g.Count()
});
foreach(var item in results)
{
Console.WriteLine("You have {0} tables of status {1}", item.Count, item.Status);
}
Note that this will only give you statuses that have at least one table, so if a status has none and you need that as well you can adjust to the following.
var results = Tables.GroupBy(t => t.Status)
.ToDictionary(g => g.Key, g => g.Count());
foreach(var status in new[] {"Unoccupied", "Occupied"})
{
int count;
results.TryGetValue(status, out count);
Console.WriteLine("You have {0} tables of status {1}", count, status);
}
Also you might want to consider creating an enum for the Status if it should only be one of two values.
public enum TableStatus
{
Unoccupied,
Occupied
}
You can use LINQ like:
List<Table> UnoccupiedTables = Tables.Where(r=> r.Status == "Unoccupied")
.ToList();
List<Table> OccupiedTables = Tables.Where(r=> r.Status == "Occupied")
.ToList();
If you want case insensitive comparison then you can replace you can use String.Equals like:
String.Equals(r.Status, "Occupied", StringComparison.InvariantCultureIgnoreCase)
I missed the part of getting count. You can use GroupBy as mentioned in the other answer, or you can get count for each item like:
int CountOfUnoccupiedTables = Tables.Count(r=> r.Status == "Unoccupied");

Another Q about Linq grouping

I using Linq (together with EF) in order to access my database. I have object "Job", which contains several properties, some of them are "complex". My goal is to group jobs by these properties, and to get a count for each group.
Here my objects (simplified):
public class Job
{
[Key]
public int Id
{
get;
set;
}
[Required]
public Salary Salary
{
get;
set;
}
[Required]
public ICollection<Category> Categories
{
get;
set;
}
}
"Category" is a complex class, and looks like this:
public class Category
{
[Key]
public int Id
{
get;
set;
}
public Industry Industry //Example: Software
{
get;
set;
}
public Field Field //Example: .NET
{
get;
set;
}
public Position Position //Example: Developer
{
get;
set;
}
}
Industry, Field, Position and Salary classes contains just "int" id and "string" name.
I need to group list of Jobs by Industry, Field, Position and Salary and to get a count of each group. This is how I doing it right now:
var IndustryGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Industry} into g
select new
{
Tag = g.Key.Industry,
Count = g.Count()
};
var FieldsGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Field} into g
select new
{
Tag = g.Key.Field,
Count = g.Count()
};
var PositionsGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Position} into g
select new
{
Tag = g.Key.Position,
Count = g.Count()
};
Jobs.GroupBy(job => job.Salary)
.Select(group => new
{
Tag = group.Key,
Count = group.Count()
}))
This is works fine, but I wondering is it possible to improve somehow its performance.
Q1: I think, that probably one single query will perform better that four. Is it possible to combine these queries into one single query?
Q2: When I asking Linq to group by "Industry", how exactly it able to distinguish between one Industry to another? Is it implicitly comparing records' keys? Is it will be faster if I explicitly tell to linq which property to group by (e.g. "id") ?
Thanks!
Answer in reverse order:
Q2:
When you group by an object instead of a base type, it uses the standard equality comparer (obj x == obj y) which does a simple reference comparison (http://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx). If that suits, it works, otherwise you can implement a custom equality comparer (How to implement IEqualityComparer to return distinct values?)
Q1:
If you wanted sub-groups of the groups, then you can do it in a single query. If you just want the counts for each, then you are doing it exactly the right way.
You can user conditional GROUP BY.
You can define a variable to tell the query which column to use for grouping. You can define an ENUM for GROUP BY columns.
int groupByCol = 1; //Change the value of this field according to the field you want to group by
var GenericGroupsQuery = from t in Jobs
group t by new { GroupCol = ( groupByCol == 1 ? t.Industry:(groupByCol == 2 ? t.Field:(groupByCol == 3 ? t.Position : t.Job)))} into g
select new
{
Tag = g.Key,
Count = g.Count()
};

Need help with LINQ query

I have the following class:
internal class ModuleScrap
{
public System.DateTime ReadTime { get; set; }
public string ScrapReason { get; set; }
public Int16 NetScrap { get; set; }
}
I could use some help with a LINQ query that retrieves all rows between a range of dates (that's the easy part), groups on ScrapReason and determines the sum of NetScrap for each group. I don't have much experience with grouping in LINQ.
Something like this, I suspect:
var query = from scrap in scraps
where scrap.ReadTime >= minDate && scrap.ReadTime <= maxDate
group scrap by scrap.ScrapReason into g
select new { Reason = g.Key, Total = g.Sum(x => (int) x.NetScrap) };
Note that the int cast is because Sum isn't defined for Int16 values. An alternative is to change the grouping:
var query = from scrap in scraps
where scrap.ReadTime >= minDate && scrap.ReadTime <= maxDate
group (int) scrap.NetScrap by scrap.ScrapReason into g
select new { Reason = g.Key, Total = g.Sum() };
They'll give the same results - it's just a matter of which you prefer.

Categories

Resources