let's say I have a class:
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public double Price { get; set; }
public int Number { get; set; }
}
and list of class objects:
List<Product> productList = new()
{
new Product { Id = 1, Name = "Pasta", Category = "Beverages", Price = 18.0, Number = 39 },
new Product { Id = 2, Name = "Anchius", Category = "Beverages", Price = 19.0, Number = 0 },
new Product { Id = 3, Name = "Syrup", Category = "Condiments", Price = 10.0, Number = 13 },
new Product { Id = 4, Name = "Seasoning", Category = "Condiments", Price = 22.0, Number = 53 },
new Product { Id = 5, Name = "Gumbo", Category = "Condiments", Price = 21.35, Number = 41 },
new Product { Id = 6, Name = "Spread", Category = "Condiments", Price = 25.0, Number = 120 },
new Product { Id = 7, Name = "Dried", Category = "Confections", Price = 30.0, Number = 0 },
new Product { Id = 8, Name = "Bread", Category = "Confections", Price = 16.0, Number = 14 },
new Product { Id = 9, Name = "Sauce", Category = "Condiments", Price = 40.0, Number = 20 },
new Product { Id = 10, Name = "Niku", Category = "Meat", Price = 97.0, Number = 29 },
new Product { Id = 11, Name = "Niku", Category = "Meat", Price = 34.0, Number = 61 },
new Product { Id = 12, Name = "Ragoo", Category = "Seafood", Price = 31.0, Number = 31 }
};
TODO 1: create new tuple collection if 'Number' property equals 0. The first element of tuple should be "Category" and second - array of Products.
return type:
IEnumerable<(string category, IEnumerable<Product> products)> result1
result should be:
("Beverages",
new Product[]
{
new Product { Id = 1, Name = "Pasta", Category = "Beverages", Price = 18.0, Number = 39 },
new Product { Id = 2, Name = "Anchius", Category = "Beverages", Price = 19.0, Number = 0 },
}
),
("Confections",
new Product[]
{
new Product { Id = 7, Name = "Dried", Category = "Confections", Price = 30.0, Number = 0 },
new Product { Id = 8, Name = "Bread", Category = "Confections", Price = 16.0, Number = 14 },
}
)
TODO 2: create new tuple collection if 'Number' property doesn't equals 0. The first element of tuple should be "Category" and second - array of Products.
return type:
IEnumerable<(string category, IEnumerable<Product> products)> result2
result should be:
("Condiments",
new Product[]
{
new Product { Id = 3, Name = "Syrup", Category = "Condiments", Price = 10.0, Number = 13 },
new Product { Id = 4, Name = "Seasoning", Category = "Condiments", Price = 22.0, Number = 53 },
new Product { Id = 5, Name = "Gumbo", Category = "Condiments", Price = 21.35, Number = 41 },
new Product { Id = 6, Name = "Spread", Category = "Condiments", Price = 25.0, Number = 120 },
}
),
("Meat",
new Product[]
{
new Product { Id = 10, Name = "Niku", Category = "Meat", Price = 97.0, Number = 29 },
new Product { Id = 11, Name = "Niku", Category = "Meat", Price = 34.0, Number = 61 },
}
),
("Seafood",
new Product[]
{
new Product { Id = 12, Name = "Ragoo", Category = "Seafood", Price = 31.0, Number = 31 },
}
)
Please help.
Please give both Query syntax and Method syntax solutions.
You can simply use this linq:
var result1 = productList
.GroupBy(x => x.Category, (category, products) => new List<(string Category, IEnumerable<Product> Products)>
{ (category, products) }
).Where(x => x[0].Products.Any(s => s.Number == 0)).Select(x => x[0]);
var result2 = productList
.GroupBy(x => x.Category, (category, products) => new List<(string Category, IEnumerable<Product> Products)>
{ (category, products) }
).Where(x => x[0].Products.All(s => s.Number != 0)).Select(x => x[0]);
The T-Sql query is a little tricky and remember you must turn t-sql result to your tuple model, this is the query:
CREATE TABLE #product(Id INT, Name NVARCHAR(50), Category NVARCHAR(50),Price DECIMAL(10,1), Number INT)
INSERT INTO #product
(
Id,
Name,
Category,
Price,
Number
)
VALUES
( 1,N'Pasta',N'Beverages',18.0,39),
( 2,N'Anchius',N'Beverages',19.0,0),
( 3,N'Syrup',N'Condiments',10,13),
( 4,N'Seasoning',N'Condiments',22,53),
( 5,N'Gumbo',N'Condiments',18.0,41),
( 6,N'Spread',N'Condiments',18.0,120),
( 7,N'Dried',N'Confections',18.0,0),
( 8,N'Bread',N'Confections',18.0,14),
( 9,N'Sauce',N'Condiments',18.0,20),
( 10,N'Niku',N'Meat',18.0,29),
( 11,N'Niku',N'Meat',18.0,61),
( 12,N'Ragoo',N'Seafood',18.0,31)
--get product with 0 nubmers
SELECT * FROM #product WHERE Category IN (
SELECT x.Category FROM (
SELECT *,CASE Number WHEN
0 THEN 1 ELSE 0 END NewNumber FROM #product) x
GROUP BY x.Category
HAVING SUM(x.NewNumber)>0
)
--get product without 0 nubmers
SELECT * FROM #product WHERE Category IN (
SELECT x.Category FROM (
SELECT *,CASE Number WHEN
0 THEN -1 ELSE 0 END NewNumber FROM #product) x
GROUP BY x.Category
HAVING SUM(x.NewNumber)=0
)
(Method syntax only)
This is perhaps a somewhat overkill solution, but it lets you traverse productList only once by creating a dictionary where the keys are true (for result1) and false (for result2). Since first posting this answer, I have supplemented it with a second implementation version.
The first version groups products by category according to each Product's Number value (Number == 0 and Number != 0); adhering to your written requirements:
TODO 1: create new tuple collection if 'Number' property equals 0
TODO 2: create new tuple collection if 'Number' property doesn't equals 0
IDictionary<bool, IEnumerable<( string category, IEnumerable<Product> products)>> results = productList
.GroupBy(
product => product.Number == 0,
( isZero, products ) => (
NumberIsZero: isZero,
ProductsByCategory: products.GroupBy(
product => product.Category,
( category, products ) => ( category, products ))))
.ToDictionary(item => item.NumberIsZero, item => item.ProductsByCategory);
var result1 = results[true];
var result2 = results[false];
Example fiddle here.
The second version groups products by category based on whether each category contains any Product with Number == 0, or not; adhering to your requested example outputs:
IDictionary<bool, IEnumerable<( string Category, IEnumerable<Product> Products )>> results = productList
.GroupBy(product => product.Category,
( category, products ) => ( Category: category, Products: products ))
.GroupBy(productsByCategory => productsByCategory.Products.Any(p => p.Number == 0),
( containsZero, productsByCategory ) => ( ContainsZero: containsZero, ProductsByCategory: productsByCategory ))
.ToDictionary(
gr => gr.ContainsZero,
gr => gr.ProductsByCategory);
var result1 = results[true];
var result2 = results[false];
Example fiddle here.
i have 2 table ,
produk table
id produk batch qty
1 AAA ADADAD 2
2 BBB ADADAD 2
3 BBB AAAAAA 2
...............
and move table,
id produk batch qty
1 BBB ADADAD 1
and i want showing table after qty from stok table minus qty from move table, what i want table
PRODUK BATCH QTY
AAA ADADAD 2
BBB ADADAD 1
BBB AAAAAA 2
and this my query
var obj = _db.produk
.Groupby(a=> new {a.code,a.batch})
.Select(a=> new {
produk = a.key.code,
batch = a.Key.batch,
qty = _db.move.Where(c => a.Any(p => p.code == a.code && p.batch == a.batch)).Sum(a=>a.qty)
}).tolist();
but not working
You have to do LEFT JOIN to grouped move table.
var moves =
from m in _db.move
group m by { m.code, m.batch } into g
select
{
g.Key.code,
g.Key.batch,
qty = g.Sum(x => x.qty)
};
var query =
from p in _db.produk
join m in moves on new { p.code, p.batch } equals { m.code, m.batch } into j
from m in j.DefaultIfEmpty()
select new
{
produk = p.code,
batch = p.batch.
qty = p.qty - (int?)m.qty ?? 0
};
If you prefer method syntax over query syntax then you can write your query as this:
var availableItems = repository
.GroupJoin(purchases,
stock => new { stock.Product, stock.Batch },
move => new { move.Product, move.Batch },
(stock, moves) => new { Stock = stock, Moves = moves })
.SelectMany(
stockAndRelatedMoves => stockAndRelatedMoves.Moves.DefaultIfEmpty(),
(stockAndRelatedMoves, relatedMove) => new
{
stockAndRelatedMoves.Stock.Product,
stockAndRelatedMoves.Stock.Batch,
Quantity = stockAndRelatedMoves.Stock.Quantity - (relatedMove?.Quantity ?? 0)
})
.ToList();
As you can see instead of GroupBy you need to use GroupJoin and instead of simple Select you need SelectMany to retrieve items from the joined records.
Some explanation:
stock => new { stock.Product, stock.Batch }: Anonymous type is used here because multiple fields are used in the join
stockAndRelatedMoves => stockAndRelatedMoves.Moves.DefaultIfEmpty(): it is needed because of left outer join
(relatedMove?.Quantity ?? 0): relatedMove can be null that's why we substitute it with 0
In the above code I've used the following collections:
var repository = new List<Stock>
{
new Stock { Id = 1, Product = "AAA", Batch = "ADADAD", Quantity = 2 },
new Stock { Id = 2, Product = "BBB", Batch = "ADADAD", Quantity = 2 },
new Stock { Id = 3, Product = "BBB", Batch = "AAAAAA", Quantity = 2 },
};
var purchases = new List<Move>
{
new Move { Id = 1, Product = "BBB", Batch = "ADADAD", Quantity = 1 }
};
You could also query the produck table, then, in the Select statement, filter the move table based on the produck's batch and produck properties, then calculate the qty.
Code as below:
List<Produk> produks = new List<Produk>()
{
new Produk(){ id = 1, produk= "AAA", batch="ADADAD", qty = 2},
new Produk(){ id = 2, produk= "BBB", batch="ADADAD", qty = 2},
new Produk(){ id = 3, produk= "BBB", batch="AAAAAA", qty = 2},
};
List<Remove> removes = new List<Remove>()
{
new Remove(){ id=1, produk="BBB", batch="ADADAD", qty=1}
};
var result = (from p in produks
select new Produk
{
id = p.id,
produk = p.produk,
batch = p.batch,
qty = p.qty - removes.Where(c => c.produk == p.produk && c.batch == p.batch).Sum(c => c.qty)
}).ToList();
The result like this:
Lets say that i have a table with multiple columns (student, teacher, subject, marks) and I want to compare each of these column to another table with same columns (with sum(marks)).
I have the following method with column name as an argument, which is then used in group by clause.
public List<AllMarks> RIMarks(string filter)
{
var MarkTable = from p in MainDB.classTable
let fil = filter
group p by new { fil } into g
select new AllMarks
{
Column = g.Key.fil,
Marks = g.Sum(f => f.Mark)
};
List<AllMarks> lstRI = MarkTable.ToList();
return lstRI;
}
public void Test()
{
var filter = new string[] {"Student", "Teacher", "Subject"}
foreach (f in filter)
{
// Call RIMarks(f) and do something
}
}
I have number of distinct students in my table, however with this method, what I get is just a single row with sum(marks(all students) for the first filter criteria (which is Student) and its not actually grouping by the student.
How do I use local variable in linq groupby clause ?
Update:
Sample DB:
Student Teacher Subject Marks
stu1 teac1 sub1 23
stu1 teac1 sub1 45
stu2 teac2 sub2 34
You're trying to group by the name of a particular property of some data object you have.
You want to reflect the property of the field you want then group by that field's values.
You can't just provide the name because the filter wont work and it'll just group the whole collection.
Test this out in LinqPad.
What you have are the marks being summed on the grouping, not sure if you wanted the average but
what you have here should lead you to getting the average.
void Main()
{
var mySchool = new List<School>{
new School{Student = "Student A", Teacher = "Teacher A", Subject = "Math", Marks = 80},
new School{Student = "Student B", Teacher = "Teacher A", Subject = "Math", Marks = 65},
new School{Student = "Student C", Teacher = "Teacher A", Subject = "Math", Marks = 95},
new School{Student = "Student A", Teacher = "Teacher B", Subject = "History", Marks = 80},
new School{Student = "Student B", Teacher = "Teacher B", Subject = "History", Marks = 100},
};
GroupByFilter("Student", mySchool);
GroupByFilter("Teacher", mySchool);
GroupByFilter("Subject", mySchool);
}
public void GroupByFilter(string filter, List<School> school)
{
PropertyInfo prop = typeof(School).GetProperties()
.Where(x => x.Name == filter)
.First();
var grouping = from s in school
group s by new {filter = prop.GetValue(s)} into gr
select new {
Filter = gr.Key.filter,
Marks = gr.Sum(x => x.Marks)
};
grouping.Dump(); // this is linqpad specific
}
// Define other methods and classes here
public class School{
public string Student {get;set;}
public string Teacher {get;set;}
public string Subject {get;set;}
public int Marks {get;set;}
}
Results
Group By Student
Filter Marks
Student A 160
Student B 165
Student C 95
Group By Teacher
Filter Marks
Teacher A 240
Teacher B 180
Group By Subject
Filter Marks
Math 240
History 180
please try below code.
public List<AllMarks> RIMarks(string filter)
{
if (filter == "Student") {
var MarkTable = from p in MainDB.classTable
group p by new { p.Student} into g
select new AllMarks
{
Column = g.Key.fil,
Marks = g.Sum(f => f.Mark)
};
}
else if (filter == "Teacher") {
var MarkTable = from p in MainDB.classTable
group p by new { p.Teacher} into g
select new AllMarks
{
Column = g.Key.fil,
Marks = g.Sum(f => f.Mark)
};
}
else if (filter == "Subject") {
var MarkTable = from p in MainDB.classTable
group p by new { p.Subject} into g
select new AllMarks
{
Column = g.Key.fil,
Marks = g.Sum(f => f.Mark)
};
}
}
I created this query, and I don't know how find the last date_attribution
var usertmp = from user in entity.inv_utilisateur
join userbadgetmp in entity.inv_utilisateur_badge on user.u_id equals userbadgetmp.u_id into gj
from userbadge in gj.DefaultIfEmpty()
select new UserEJ
{
u_id = (int) user.u_id,
name = user.name,
date_attrib = userbadge.date_attribution // here I want find the last date
};
for exemple if I have this 2 tables:
inv_utilisateur inv_utilisateur_badge
u_id name u_id date_attribution
1 name1 1 20130911
1 20130807
2 name2 2 20120608
3 name3
I need the result
u_id name date_attribution
1 name1 20130911
2 name2 20120608
3 name3
Edit 1
the type of the field are :
u_id : int,
name : string,
date_attribution : datetime
var usertmp = from user in entity.inv_utilisateur
join userbadgetmp in entity.inv_utilisateur_badge on user.u_id equals userbadgetmp.u_id into gj
from userbadge in gj.DefaultIfEmpty()
group userbadge by new{user.u_id, user.name} into g
select new UserEJ {
u_id = (int)g.Key.u_id,
name = g.Key.name,
date_attrib = g.Max(x => x!= null ? x.date_attribution : <a defaultvalue>)
}
you could also do
var usertmp = from user in entity.inv_utilisateur
let max_dt = (from userbadge in entity.inv_utilisateur_badge
where user.u_id == userbadge.u_id
select userbadge.date_attribution).Max()//??someDefaultvalue if you don't want null
select new UserEJ {
u_id = user.u_id,
name = g.name,
date_attrib = max_dt
}
Sorry for my answer but i am not used in the from clause:
var usertmp = entity.inv_utilisateur
.Join
(
entity.inv_utilisateur_badge,
x=>x.u_id,
x=>x.u_id,
(user,userbadge) => new
{
u_id = user.u_id,
name = user.name,
date_attrib = userbadge.date_attribution
}
)
.GroupBy(x=>new {x.u_id,x.name})
.Select
(
x => new
{
x.Key.u_id,
x.Key.name,
date_attrib = x.Max(z=>z.date_attrib)
}
);