I am really new to using LINQ and I was wondering what I need to do to the below expression to grab extra fields
public class Foo
{
public string Name {get;set;}
public string Manufacturer {get;set;}
public float Price {get;set;}
}
var result= (
from row in dt.AsEnumerable()
group row by row.Field<string>("NAME") into g
select new Foo
{
Name = g.Key,
Price=g.Min (x =>x.Field<float>("PRICE"))
//Manufacturer = ????
}
).ToList();
I basically need to get the Manufacturer from the MANUFACTURER field and set it's value in the object. I've tried:
row.Field<string>("MANUFACTURER")
//and
g.Field<string>("MANUFACTURER")
But I am having no luck accessing the field in the DataTable. Can anyone advise what I'm doing wrong please?
So you want to group by name. But how do you want to aggregate the manufacturers for each name-group?
Presuming that you just want to take the first manufacturer:
var result= (
from row in dt.AsEnumerable()
group row by row.Field<string>("NAME") into g
select new Foo
{
Name = g.Key,
Price=g.Min (x =>x.Field<float>("PRICE")),
Manufacturer = g.First().Field<string>("MANUFACTURER")
}
).ToList();
Maybe you instead want to concatenate all with a separator:
// ...
Manufacturer = string.Join(",", g.Select(r=> r.Field<string>("MANUFACTURER")))
As your logic stands you may have more than one Manufacturer if you only group by Name.
To illustrate this consider the following data, which is supported by your data structure.
Example
ProductA, ManufacturerA
ProductA, ManufacturerB
If you group by just "ProductA" then Manufacturer is a collection of ["ManufacturerA", "ManufacturerB"]
Potential Solution
You could group by Name and Manufacturer then access both Name and Manufacturer
var result= (
from row in dt.AsEnumerable()
group row by new
{
row.Field<string>("NAME"),
row.Field<string>("MANUFACTURER")
} into g
select new Foo
{
Name = g.Key.Name,
Manufacturer = g.Key.Manufacturer,
Price=g.Min (x =>x.Field<float>("PRICE"))
}
).ToList();
EDIT
Based on comment "I am trying to pull the name with the cheapest price and the manufacturer along with it."
var result= (
from row in dt.AsEnumerable()
group row by row.Field<string>("NAME") into g
let x = new
{
Name = g.Key.Name,
Price=g.Min (x =>x.Field<float>("PRICE"))
}
where (row.Name == x.Name && row.Price == x.Price)
select new Foo
{
Name = row.Name,
Manufacturer = row.Manufacturer,
Price= row.Price
}
).ToList();
Related
I'm struggling with linq (left join - group - count). Please help me.
Below is my code and it gives me this result.
Geography 2
Economy 1
Biology 1
I'm expecting this...
Geography 2
Economy 1
Biology 0
How can I fix it?
class Department
{
public int DNO { get; set; }
public string DeptName { get; set; }
}
class Student
{
public string Name { get; set; }
public int DNO { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Department> departments = new List<Department>
{
new Department {DNO=1, DeptName="Geography"},
new Department {DNO=2, DeptName="Economy"},
new Department {DNO=3, DeptName="Biology"}
};
List<Student> students = new List<Student>
{
new Student {Name="Peter", DNO=2},
new Student {Name="Paul", DNO=1},
new Student {Name="Mary", DNO=1},
};
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count()
};
foreach (var st in query)
{
Console.WriteLine("{0} \t{1}", st.DName, st.Count);
}
}
}
var query =
from department in departments
join student in students on department.DNO equals student.DNO into gst
select new
{
DepartmentName = department.DeptName,
Count = gst.Count()
};
I don't think any grouping is required for answering your question.
You only want to know 2 things:
- name of department
- number of students per department
By using the 'join' and 'into' you're putting the results of the join in the temp identifier gst. You only have to count the number of results in the gst.
var query = from dp in departments
from st in students.Where(stud => stud.DNO == dp.DNO).DefaultIfEmpty()
group st by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(x => x!=null)
};
You want to group the students by the department name but you want the count to filter out null students. I did change the join syntax slightly although that really does not matter to much.
Here is a working fiddle
Well, see what #Danny said in his answer, it's the best and cleanest fix for this case. By the way, you could also rewrite it to the lambda syntax:
var query = departments.GroupJoin(students,
dp => dp.DNO, st => st.DNO,
(dept,studs) => new
{
DName = dept.DNO,
Count = studs.Count()
});
I find this syntax much more predictable in results, and often, shorter.
BTW: .GroupJoin is effectively a "left join", and .Join is "inner join". Be careful to not mistake one for another.
And my answer is similar to #Igor
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(std => std != null)
};
g.Count(std => std != null) is only one change you should take.
I have three levels of master detail relation, One purchase can contain multiple challan, one challan can contain multiple items. each item has some quantity. I need to calculate total item quantity for every purchase. I've done that by the following, but it takes a lot of time just for a handful of data. I'm worried what will happen when the amount of data becomes large. Is there a way to do this in a single query using join or anything else? Thanks in advance.
var allData = (from p in _context.Prq_Purchase.AsEnumerable()
//where p.RecordStatus == "NCF"
from s in _context.Sys_Supplier
where s.SupplierID == p.SupplierID
from sa in _context.Sys_SupplierAddress
where sa.SupplierAddressID == p.SupplierAddressID
orderby p.PurchaseID descending
select new PurchaseReceive
{
PurchaseID = (p.PurchaseID).ToString(),
PurchaseNo= p.PurchaseNo,
SupplierID = (p.SupplierID).ToString(),
SupplierName = s.SupplierName,
Address = sa.Address,
SupplierAddressID = (p.SupplierAddressID).ToString(),
PurchaseCategory = p.PurchaseCategory,
PurchaseType = p.PurchaseType,
PurchaseYear = p.PurchaseYear,
PurchaseDate = (p.PurchaseDate).ToString("dd'/'MM'/'yyyy"),
RecordStatus= DalCommon.ReturnRecordStatus(p.RecordStatus)
}).ToList();
foreach(var Purchase in allData)
{
decimal TotalQty = 0;
var ChallanList= (from c in _context.Prq_PurchaseChallan.AsEnumerable()
where (c.PurchaseID).ToString()==Purchase.PurchaseID
select c).ToList();
foreach(var Challan in ChallanList)
{
var ItemList = (from i in _context.Prq_PurchaseChallanItem.AsEnumerable()
where i.ChallanID == Challan.ChallanID
select i).ToList();
foreach(var Item in ItemList )
{
TotalQty = TotalQty + Item.ReceiveQty;
}
}
Purchase.TotalItem = TotalQty;
}
The following data is returned from an SQL View:
Name CandidateID Filled
Tom Jones 1003436 2014-05-09 07:13:53.087
Tom Jones 1003436 2014-05-09 07:13:18.957
Ed Harris 1421522 2014-05-09 08:17:20.234
I only want the one Tom Jones record with the latest Filled time. How can I achive this in C#/LINQ while getting or after getting data from server?
Maybe something like this:
var q = from n in table
group n by new {n.CandidateID,n.Name} into g
select new
{
CandidateID = g.Key.CandidateID,
Name = g.Key.Name,
Filled = g.Max(t=>t.Filled)
};
Test class
class Foo
{
public string Name { get; set; }
public int CandidateID { get; set; }
public DateTime Filled { get; set; }
}
Test case
var ls=new List<Foo>
{
new Foo(){Name="Tom Jones",CandidateID=1003436,
Filled=DateTime.Parse("2014-05-09 07:13:53.087")},
new Foo(){Name="Tom Jones",CandidateID=1003436,
Filled=DateTime.Parse("2014-05-09 07:13:18.957")},
new Foo(){Name="Ed Harris",CandidateID=1421522,
Filled=DateTime.Parse("2014-05-09 08:17:20.234")}
};
var q =
(from n in ls
group n by new {n.CandidateID,n.Name} into g
select new
{
CandidateID = g.Key.CandidateID,
Name = g.Key.Name,
Filled = g.Max(t=>t.Filled)
});
Output
CandidateID Name Filled
1003436 Tom Jones 09/05/2014 7:13:53 AM
1421522 Ed Harris 09/05/2014 8:17:20 AM
var q = from n in table
group n by n.CandidateID into g
select g.OrderByDescending(t=>t.Filled).FirstOrDefault();
you need Group by as shown below
var distinctItems = ls.GroupBy(x => x.CandidateID).Select(y => y.First());
Having some issues with my code, its a many to many between 3 tables, im using LINQ to entities, first I group the quote ids then use the id to get the info from each table and try to put it into a viewmodel, im not sure my inhertance is right
There should be one quote with multiple prices from multiple suppliers for multiple items, the item names need to be in the column and the prices below.
Anyway the issue below is
Error 3 The best overloaded method match for
System.Collections.Generic.List<ITAPP.Models.tblQuotes>.Add(ITAPP.Models.tblQuotes)
has some invalid arguments PurchasingController.cs 48 17 ITAPP`
Error 4 Argument 1: cannot convert from
System.Collections.Generic.List<ITAPP.Models.tblQuotes>' to 'ITAPP.Models.tblQuotes'
PurchasingController.cs 48 37 ITAPP`
and here is the code
var tblQuoting =
from d in db.Quotes_Items_Suppliers
group d by new
{
d.QuoteID
} into g
select new {
QuoteID = g.Key
};
var model = new List<QuoteViewModel>();
foreach (var Quotes in tblQuoting)
{
var ModelItem = new QuoteViewModel();
ModelItem.Quote = new List<tblQuotes>();
ModelItem.Suppliers = new List<tblSuppliers>();
ModelItem.Items = new List<tblItems>();
ModelItem.Prices = new List<tblQuotes_Items_Suppliers>();
//Add the quote info to model
int QuoteID = Convert.ToInt32(Quotes.QuoteID);
var Quote = (from d in db.Quotes
where d.ID == QuoteID
select d).ToList();
ModelItem.Quote.Add(Quote);
//add all the suppliers to the quote model
var Suppliers = (from d in db.Quotes_Items_Suppliers.Include(t => t.tblSuppliers)
where d.QuoteID == QuoteID
select d).ToList();
ModelItem.Suppliers.Add(Suppliers);
//add the items to the quote model
var Items = (from d in db.Quotes_Items_Suppliers.Include(t => t.tblItems)
where d.QuoteID == QuoteID
select d).ToList();
ModelItem.Items.Add(Items);
model.Add(ModelItem);
}
return View("Index", model);
this is my model (if its right?)
public class QuoteViewModel
{
public List<tblQuotes> Quote { get; set; }
public List<tblSuppliers> Suppliers { get; set; }
public List<tblItems> Items { get; set; }
public List<tblQuotes_Items_Suppliers> Prices { get; set; }
}
index
Use AddRange to add sequence of items to list:
ModelItem.Quote.AddRange(Quote);
ModelItem.Suppliers.AddRange(Suppliers);
ModelItem.Items.AddRange(Items);
Or simply assign lists without initialization (thus you will avoid creating intermediate list and copying items from one list to another):
ModelItem.Quote = Quote;
ModelItem.Suppliers = Supplies;
ModelItem.Items = Items;
Or even use object initalizer:
var ModelItem = new QuoteViewModel {
Quote = db.Quotes.Where(q => q.ID == QuoteID).ToList(),
Suppliers = db.Quotes_Items_Suppliers.Include(t => t.tblSuppliers)
.Where(s => s.QuoteID == QuoteID).ToList(),
Items = db.Quotes_Items_Suppliers.Include(t => t.tblItems)
.Where(i => i.QuoteID == QuoteID).ToList()
};
I have a table, generated from a LINQ query on a datatable, which has subcategory and category fields:
Name...........Category.........Subcategory
Kiss...........Rock.............Glam Rock
Metallica......Rock.............Hard Rock
Bon Jovi.......Rock.............Soft Rock
Slade..........Rock.............Glam Rock
Meatloaf.......Rock.............Soft Rock
Wilee..........Dance............Grime
Mgmt...........Dance............Nu Rave
Dizee..........Dance............Grime
The LINQ query I am using to generate this table is:
var qCategory = from c in dtCategory.AsEnumerable()
select new {
Artist = c.Field<string>("Artist"),
Category = c.Field<string>("Category"),
Subcategory = c.Field<string>("Subcategory")
};
Now I want to get a count of each category/subcategory pair. e.g. for the above example I want to return:
Category............Subcategory.......Count
Rock................Glam Rock.........2
Rock................Soft Rock........2
Rock................Hard Rock.........1
Dance...............Grime.............2
Dance...............Nu Rave...........1
How can I acheive this?
Try:
var counts = from artist in qCategory
group artist by new { artist.Category, artist.Subcategory }
into g
select new {
g.Key.Category,
g.Key.Subcategory,
Count = g.Count()
};
If you want to enforce that subcategories always have the same parent category (given that the sub-categories are named "Glam Rock" etc., I assume that this is in fact the case), do:
var counts = from artist in qCategory
group artist by artist.Subcategory into g
select new {
Category = g.Select(a => a.Category)
.Distinct()
.Single(),
Subcategory = g.Key,
Count = g.Count()
};
This will throw an exception if "Rap Rock" turns up as a subcategory of both "Rap" and "Rock".
qCategory.
GroupBy(item => new {Category = item.Category, Subcategory = item.Subcategory}).
Select(group => new {Category = group.Key.Category, Subcategory = group.Key.Subcategory, Count = group.Count()})