linq query sum products across multiple collections - c#

I have two datasets that look like this:
+------------------------------------+
| Products |
+------------------------------------+
| Id | Name | Price |
+------------------------------------+
| 1 | apples | 1.00 |
| 2 | oranges | 2.00 |
| 3 | pomengrate | 3.00 |
+------------------------------------+
+-------------------------------+
| Sales |
+-------------------------------+
| CustId | ProductId | Quantity |
+-------------------------------+
| 1 | 1 | 5 |
| 1 | 2 | 4 |
| 1 | 3 | 2 |
| 2 | 1 | 8 |
| 2 | 3 | 7 |
+-------------------------------+
I want to get the amount that each customer is spending, essentially ending up with a result like this:
+----------------+
| CustId | Total |
+----------------+
| 1 | 19.00 |
| 2 | 29.00 |
+----------------+
I can do a sum across a single table but the Sum method in LINQ takes a lambda with only one argument: the reference to the table that the values being summed are in. These values are in different tables. How do I sum these together?

var totals =
from sale in Sales
group sale by sale.CustId into custSales
select new
{
CustId = custSales.Key,
Total = (
from custSale in custSales
select custSale.Product.Price *
custSale.Quantity)
.Sum()
};

Give this a shot. It gives the results you were looking for:
var results = sales.Join(products,
sale => sale.ProductID,
product => product.ID,
(sale, product) => new { CustID = sale.CustID, Total = sale.Quantity * product.Price })
.GroupBy(r => r.CustID)
.Select(g => new { CustID = g.Key, Total = g.Sum(gt => gt.Total) });

For completeness sake, here's the query syntax version (utilizing joins as opposed to a subselect):
var totals =
from sale in sales
join prod in product on sale.ProductId equals prod.Id
let saleProds = new { sale.CustId, Total = prod.Price * sale.Quantity }
group saleProds by saleProds.CustId into custSale
select new { Customer = custSale.Key, Total = custSale.Sum(tr => tr.Total) };
The key part is that you need to somehow transform the joined collection results (sale and prod) into a single entity that can then be grouped by.

Related

LINQ: equivalent for string_agg from PostgreSql

I need to concatenate multiple columns values to single value (separated with coma for example). I can do it by string_agg(u.name, ',') as Users in PostgreSql. I tried do it in linq query syntax but I failed - all time I just get list with split values instead of one row with values concatenated in one field.
Let's assume that I have only three tables:
Doc User Doc_User
+----+--------------------+ +----+-----------+ +----+-----------+
| Id | Path | | Id | Name | | DocId | UserId |
+----+--------------------+ +----+-----------+ +----+-----------+
| 1 | "C:\\Files\\A.txt" | | 1 | "Adam" | | 1 | 1 |
| 2 | "C:\\Files\\B.txt" | | 2 | "Benny" | | 1 | 2 |
| 3 | "C:\\Files\\C.txt" | | 3 | "Charlie" | | 2 | 1 |
+----+--------------------+ +----+-----------+ | 2 | 2 |
| 2 | 3 |
+-------+--------+
At the start I was trying simple join:
var model = (from d in db.Docs
join du in db.DU on d.Id equals du.DocId
join u in db.Users on du.UserId equals u.Id
select new DataModel() { DocPath = d.Path, UserName = u.Name }).ToList();
As I suspected I got list with separated rows:
C:\Files\A.txt | Adam
C:\Files\A.txt | Benny
C:\Files\B.txt | Adam
C:\Files\B.txt | Benny
C:\Files\B.txt | Charlie
But what I want to obtain is:
C:\Files\A.txt | Adam, Benny
C:\Files\B.txt | Adam, Benny, Charlie
string.Join() is not recognized by EF and I can't use it in linq queries, Aggregate() not working too. How can I achieve this?
I've prepared example for play with it: .Net Fiddle.
The code below groups the common documents by path using LINQ and then joins the grouped document's UserNames, using string.Join. I also think you don't need ToList() in this line select new DataModel() { DocPath = d.Path, UserName = u.Name }).ToList(); if you are going to use this solution because we are going to use LINQ again.
var grouped = model.GroupBy(x => x.DocPath);
foreach (var iGrouped in grouped){
string.Join(",",iGrouped.Select(x => x.UserName));
Console.WriteLine(iGrouped.Key + " | " + string.Join(",",iGrouped.Select(x => x.UserName)));
}

Linq syntax to find subtotal of grouped rows in a DataTable and update the DataTable

I have been scratching my head about this one... I have a table in memory, a DataTable structured like this:
Input:
ID | Invoice | Account | Payment | Subtotal
-----------------------------------------------------------------
0 | 09310 | 123 | 6.0 | ?
-----------------------------------------------------------------
And I wish to use Linq to perform the following (I am pretty new to Linq!)
For each row that is for the same account, add together all the payments and write or update the subtotal field(s)
I do not want to collapse the table, the invoice numbers are going to be different. My thought is that there are two ways that this can be done
(A) start with a BLANK subtotal column for all records... the values from payment would be added together and then written into the subtotal column
(B) when table created, I DUPLICATE the payment values into the subtotal fields. Later, the linq would only have to add/replace the values in the same column
So we would ignore the ID and Invoice fields; its ACCOUNT and SUBTOTAL (and PAYMENT as well if using style (A))
(A) Input: *(note that there are two records for 123)*
ID | Invoice | Account | Payment | Subtotal
-----------------------------------------------------------------
0 | 03310 | 123 | 6.0 |
-----------------------------------------------------------------
1 | 09728 | 123 | 4.0 |
-----------------------------------------------------------------
2 | 07731 | 559 | 18.0 |
-----------------------------------------------------------------
 
(B) Input:
ID | Invoice | Account | Payment | Subtotal
-----------------------------------------------------------------
0 | 03310 | 123 | 6.0 | 6.0
-----------------------------------------------------------------
1 | 09728 | 123 | 4.0 | 4.0
-----------------------------------------------------------------
2 | 07731 | 559 | 18.0 | 18.0
-----------------------------------------------------------------
 
Result:
ID | Invoice | Account | Payment | Subtotal
-----------------------------------------------------------------
0 | 03310 | 123 | 6.0 | 10.0
-----------------------------------------------------------------
1 | 09728 | 123 | 4.0 | 10.0
-----------------------------------------------------------------
2 | 07731 | 559 | 18.0 | 18.0
-----------------------------------------------------------------
And so, each Subtotal cell would have the total of all PAYMENTS for each unique ACCOUNT
I am thinking that style (B) would be easier because we only have to deal with those two columns
For style (B), I have tried something like
rpTable.AsEnumerable().GroupBy(g => int.Parse(g.Field<string>("Account"))).Select(g => g.Sum(p => p.Field<decimal>("SubTotal")));
but I can tell its missing something.....hmmmm
By using Select, you will not update the table. This just returns an IEnumerable of the selected values.
What you want to do is add the column to the table and then fill it:
var subTotalByAccount = table.AsEnumerable()
.GroupBy(g => g.Field<string>("Account"))
.Select(g => new { Account = g.Key, SubTotal = g.Sum(p => p.Field<decimal>("Payment")) })
.ToDictionary(t => t.Account, t => t.SubTotal);
table.Columns.Add("SubTotal", typeof(decimal));
foreach (var row in table.AsEnumerable())
{
row.SetField(columnName: "SubTotal", value: subTotalByAccount[row.Field<string>("Account")]);
}
Thank you very much timcbaoth, yes I agree with what you said. I tried to upvote your post but the system says I may not :-(
I had figured it out (below) using brute force, but I will evaluate your solution as well! Thanks again!!
var query = from row in rpTable.AsEnumerable()
group row by int.Parse(row.Field<string>("Account")) into grp
orderby grp.Key
select new
{
Id = grp.Key,
Sum = grp.Sum(r => r.Field<decimal>("Payment"))
};
foreach (var grp in query)
{
rpTable.Select("Account ="+grp.Id).ToList<DataRow>().ForEach(r=>r["Payment"] = grp.Sum);
}
I found an insidious little bug, so FYI:
OLD: rpTable.Select("Account ="+grp.Id).ToList<DataRow>().ForEach(r=>r["Payment"] = grp.Sum);
NEW: rpTable.Select("Account ='"+ grp.Id +"'").ToList<DataRow>().ForEach(r => r["Payment"] = grp.Sum);

Convert SQL containing IN statement to chainable LINQ

I am trying to convert an MSSQL statement into LINQ. The overall functionality is broken up into many LINQ statements which are combined together, so the result must return type IQueryable.
Included in this question is an example of the schema and data. I am trying to see if the most recent (by DateCreated) value of Number is equal to 400, if so return the Table1Id.
The following is the MSSQL statement.
select * from Table1 t1
where 400 in (
select top 1 t2.Number
from Table2 t2
where t2.Table1Id = t1.id
order by t2.DateCreated desc
)
The LINQ I have come up with so far is:
//initial query
var query1 = _table1Repository.Table
//chain our query
var query2 = from t1 in query1
where ((from t2 in _table2Repository.Table
where t2.Table1Id == t1.Id
orderby t2.DateCreated descending
select t2.Number)
.Take(1)).Contains(400)
select t1;
//execute the query (may be more queries between last query and this)
var queryResult = query2.ToList();
Though upon executing the following error is thrown:
Unable to create a constant value of type 'Project.Domain.Table2'. Only primitive types or enumeration types are supported in this context.
Table 1 data
| Id |
|----|
| 1 |
| 2 |
| 3 |
Table 2 data
| Id | DateCreated | Table1Id | Number |
|----|------------ |-----------|---------|
| 1 | 1/1/2014 | 1 | 100 |
| 2 | 2/1/2014 | 1 | 200 |
| 3 | 3/1/2014 | 1 | 300 |
| 4 | 1/1/2014 | 2 | 200 |
| 5 | 2/1/2014 | 2 | 300 |
| 6 | 3/1/2014 | 2 | 400 |
| 7 | 1/1/2014 | 3 | 400 |
| 8 | 2/1/2014 | 3 | 300 |
| 9 | 3/1/2014 | 3 | 200 |
Expected result
| Id |
|----|
| 2 |
Assuming you still want to use the In keyword, the Lambda version would look like:
var intList = new List<int>(1) { 400 };
var test = _table1Repository.Table
.Where(t1 => intList.Contains(_table2Repository.Table
.Where(t2 => t2.Table1Id == t1.id)
.OrderByDescending(t2 => t2.Number)
.First()));
The only Caveat is that if there are no values in Table2, First() with throw an exception.
An example of mix'd Linq and Lambda:
var query1 = _table1Repository.Table
.OrderBy(t1 => t1.id);
var query2 = (from t1 in query1
select new { Id = t1.Id, Number = number } );
var query3 = query2.ToList();

Querying highest value by keys from database

With the following table:
+-------------+-----------+---------+
| Parent_ID | Item_ID | Count |
+-------------+-----------+---------+
| 1 | 1 | 1 |
| 1 | 1 | 5 |
| 1 | 1 | 4 |
| 1 | 2 | 7 |
| 1 | 2 | 2 |
| 2 | 1 | 2 |
| 2 | 1 | 3 |
| 2 | 2 | 2 |
| 2 | 2 | 4 |
+-------------+-----------+---------+
I would like to get the highest available count for given Parent_ID and Item_ID, like the following:
+-------------+-----------+---------+
| Parent_ID | Item_ID | Count |
+-------------+-----------+---------+
| 1 | 1 | 5 |
| 1 | 2 | 7 |
| 2 | 1 | 3 |
| 2 | 2 | 4 |
+-------------+-----------+---------+
How would I go about doing this using LINQ/SQL in C#? For example, I would like the highest item counts for parent id 1. This would be something like:
int parentId = 1;
var counts = from c in database.Items where parentId == c.parentId
//this gets all counts from items for parent id 1, but I am just looking
//for the highest count per item for parent id 1
SELECT Parent_ID, Item_ID, MAX(Count)
FROM ...
GROUP BY Parent_Id, Item_ID
select parent_id,
item_id,
max(count) as count
from your_table
group by parent_id, item_id
To group by multiple fields, you can group using an anonymous object:
var counts = from item in database.Items
group item by new { item.ParentId, item.ItemId } into itemGroup
select new Item()
{
ParentId = itemGroup.Key.ParentId,
ItemId = itemGroup.Key.ItemId,
Count = itemGroup.Max(x => x.Count)
};
Using LINQ2SQL I think you want something like:
var counts = database.Items.GroupBy(p => p.parentId).Max(p => p.Key);

Query tabels to sort sums across rows and count based on a value in LINQ

Here is my problem. i have 3-5 persons that is going to set a grade on one person and they use their own individual row to do so, and what I'm having trouble to do is to sum and average the grade from individual data across multiple rows on the same table.
in the select new statement i have made a pseudo answer of what i want
var users = from workRew in db.Reviews
select new
{
UserID = workRew.UserID.DistinctOfSomeSort
AvgGrade = workRew.Grade.Sum/CountOfSomeSort
};
Here i a illustration.
So if i have this table
| SomeID | UserID | Grade |
| 1 | 2 | 3 |
| 2 | 3 | 1 |
| 3 | 2 | 1 |
And this is the output i want from the LINQ query on the above (In theory ateast)
| UserID | AvgGrade |
| 2 | 2 |
| 3 | 1 |
EDIT: Simplified the whole case, to a great extent.
It should look something like this fragment:
group by user.UserID
select new
{
User = user.UserID
TotGradeCount = workRew.Grade.Sum()
Graders = workRew.Grade.Count()
}

Categories

Resources