How to do Many To Many LINQ entity grouping queries? - c#

I can see many examples on this site of the sort of queries I'm after, but I can't relate to them and how they work. I was wondering if you could help me.
I have set up my many-to-many tables and relationships with entity designer in Visual Studio.
tblQuotes
ID | QuoteNo | Date
tblItems
ID | PartNo | Desc
tblSuppliers
ID | Supplier | email
tblQIS (quotes items suppliers)
ID | SupplierID | QuoteID | ItemID
I've put some test data in and have begun to try to type this, but I think I first need to group by quoteNo then group by supplier, to get the details in the correct view.
var tblQuotes = from d in db.tblQuotes_Items_Suppliers
.Include(t => t.tblItems)
.Include(t => t.tblQuotes)
.Include(t => t.tblSuppliers)
group by (d.QuoteID,d.SupplierID)
select d;
Can anyone help me out?

you could try this:
var tblQuotes =
from d in db.tblQuotes_Items_Suppliers // or tblQIS whatever the name
group d by new
{
d.QuoteID,
d.SupplierID
} into g
select g;
that would give you Quotes grouped by both QuoteID and SupplierID
Update
the tblQuotes is a list (IQueryable) of grouped quotes, so you can access other entities as follow:
var firstGroupOfQuotes = tblQuotes.First(); // will give you the first group of quotes
var firstQuote = firstGroupOfQuotes.First(); // will give you the first quote in the first group
var item = firstQuote.tblItems; // will give you the item of this quote
var partNo = item.PartNo; // will give you the PartNo of this item

Related

LinQ getting distinct fields with average number error

I am having a "Type expected" error which I have no idea why.
My query simply link 3 tables together while trying to get the distinct package and average number of rating.
The outcome should be like this
| PackageName | Average Rating |
| SG | 4 |
| USA | 4 |
IQueryable<Recommendation> recommendationQuery = db.Recommendations;
IQueryable<Booking> bookingQuery = db.Bookings;
IQueryable<Package> packageQuery = db.Packages;
recommendationQuery = (from recommendationItem in recommendationQuery
join bookingItem in bookingQuery
on recommendationItem.BookingId equals bookingItem.BookingId
join packageItem in packageQuery
on recommendationItem.Booking.PackageId equals packageItem.PackageId
select recommendationItem).GroupBy(c => c.Booking.Package.PackageTitle)
.Select(c => new ( c.Key, c.Average(d=>d.Rating)));
The type expected occurs in the .Select(c => new (.....
May I know if I have query it wrongly?
Because
1) I inner joined all my 3 tables together
2) Assuming I have all the table joined, I tried to group them by PackageName to distinct the name to one name
3) I tried to select the average sum of the rating of the same package.
any idea if there's a better solution for this?
database class diagram
Solution error
You need to store results back into a new variable to match your new type:
var results = from recommendationItem in recommendationQuery
join bookingItem in bookingQuery
on recommendationItem.BookingId equals bookingItem.BookingId
join packageItem in packageQuery
on recommendationItem.Booking.PackageId equals packageItem.PackageId
group recommendationItem
by recommendationItem.Booking.Package.PackageTitle
into grp
select new
{
PackageName = grp.Key,
AverageRating = grp.Average(d => d.Rating)
};

How do I use Group By and MAX in Linq to return multiple rows?

Clarified example
I have a database of users that is created by a script that scans through Active Directory. One of the fields it applies is a "ScanDate" field, which indicates when the scan took place. The script scans through multiple Active Directory domains.
GOAL: Obtain an IList from the database that contains the list of users for ALL domains, but where the ScanDate is the MAX(ScanDate) for each domain.
This ensures I get the freshest data for each domain.
A SQL query that appears to work for me:
SELECT *
FROM ADScans a
WHERE a.ScanDate = (SELECT MAX(b.ScanDate) FROM ADScans b WHERE a.Domain = b.Domain) AND Enabled = 1
However, having trouble getting that expressed in LINQ
e.g.:
Category | Date
Cat1 4/4/16
Cat2 | 4/4/16
Cat3 | 4/4/16
Cat1 | 4/3/16
I would expect a list:
Cat1 | 4/4/16
Cat2 | 4/4/16
Cat3 | 4/4/16
Some clarification
I would expect to have multiple rows returned per category - the MAX(Date) will not just give me one. I am looking to obtain ALL of the rows for the MAX(Date) of each category.
Something like this should work:
var result =
list
//Group by Category
.GroupBy(x => x.Category)
//For each Category, select Category and max Date within the Category
//This would create an anonymous object, you could do a "new Entity" instead if you want
.Select(g => new {Category = g.Key, Date = g.Max(x => x.Date)})
.ToList();
I'm an idiot.
from u in this.db.ADScans
where u.ScanDate ==
(from s in this.db.ADScans where u.Domain == s.Domain select s.ScanDate).Max()
&& u.Enabled
select u;
Rather than using Max(), just order the items in the groups and take the top item: since you ordered the items, it's guaranteed to be the highest one:
var mostRecentScanFromEachDomain =
from u in this.db.ADScans
where u.Enabled
group u by u.Domain into g
select g.OrderByDescending(u => u.ScanDate)
.FirstOrDefault();
You can GroupBy by Domain to get the max ScanDate, then keep only the rows with that Date.
For a model like this:
class ADScan
{
public int Domain { get; set; }
public DateTime ScanDate { get; set; }
}
You can get the scans doing this:
var result = scans.GroupBy(s => s.Domain)
.SelectMany(g => g.Where(s => s.ScanDate == g.Max(d => d.ScanDate)));
This produces a collection containing the scans with the max ScanDate for its Domain.

Need to group LINQ to Entities Results

Here is my code:
var context = new InventoryContext();
var res = from i in context.Inventories
select new FullInventory
{
InventoryID = i.InventoryID,
ItemModelID = i.ItemModelID,
ModelName = i.ItemModel.ModelName,
...
Quantity = context.Inventories.Select(x => x.ItemModelID).Count()
};
return res.ToList();
The code I have works great, however the results look like this:
CATEGORY | MANUFACTURER | MODEL | QUANTITY
1. Hard drive | Dell | 250GB | 2
2. Hard drive | Dell | 250GB | 2
As you can see, I have 2 entries in the table and since those entries are exactly the same, I would like them to be grouped together. To group them together I need to group by ItemModelID, however I still need to put all of the data into a list of FullInventory classes.
How can I group my data together while still "selecting" the data into a list of FullInventory classes?
Group by that value and then just select out the first item from each group using let.
var res = from inventory in context.Inventories
group inventory by inventory.ItemModelID into model
let i = model.FirstOrDefault()
select [...]
I would attempt the Group By after retrieving the full list with a Lambda expression:
var context = new InventoryContext();
return (from i in context.Inventories
select new FullInventory
{
InventoryID = i.InventoryID,
ItemModelID = i.ItemModelID,
ModelName = i.ItemModel.ModelName,
...
Quantity = context.Inventories.Select(x => x.ItemModelID).Count()
}).GroupBy(g => g.ItemModelID).ToList();
You may use Distinct()
return res.Distinct().ToList()

How to SUM up results by column value in db query result

My database has a sales table with entries like so:
_____________________________________
| id | title_id | qty |
-------------------------------------
| 0 | 6 | 10 |
-------------------------------------
| 1 | 5 | 5 |
-------------------------------------
| 2 | 6 | 2 |
-------------------------------------
Title_id is Foreign key pointing to Titles table which is as follows:
_____________________________________
| id | title_id | title |
-------------------------------------
| 0 | 5 | Soda |
-------------------------------------
| 1 | 6 | Coffee |
-------------------------------------
I want to find top 5 sold products wich means i need to calculate the qty value for each product for all it's entried in sales table then order the result by qty in descending order and limit the select to 5.
However I'm new to C# ASP.NET and somewhat new to SQL. I dont know how to do this with LINQ.
This is my code so far:
var getIds = (from sale in db.sales
join tit in db.titles on sale.title_id equals tit.title_id
group sale by sale.qty into result
orderby result.Sum(i => i.qty) descending
select new Publication
{
PubID = sales.title_id, Title = tit.title
}
).Take(5);
Assuming you have a navigation property Sale.Title, something like this should do:
var tops =
db.Sales
.GroupBy( o => o.Title )
.Select( o => new { Title = o.Key, Sum = o.Sum( x => x.Quantity ) } )
.OrderByDescending( o => o.Sum )
.Take( 5 )
.ToList();
tops is then a list of an anonymous type with two properties: the Title object and the sum of the quantities.
You can then get the values like this:
foreach( var top in tops )
{
int titleId = top.Title.title_id;
string title = top.Title.title;
int sumOfQuantities = top.Sum;
...
If you just want the top Title objects, can can select them like this:
List<Title> topTitles = tops.Select( o => o.Title ).ToList();
var result= (from p in sales
let k = new
{
Name = p.Name
}
group p by k into t
orderby Name descending
select new
{
Name = t.Name,
Qty = t.Sum(p => p.Qty)
}).Take(5);
If the entries in the Sales table are more than one per item (ie: in your example you have 'Soda' 10 + 'Soda' 2, then you need to GroupBy(), using the name as the key (or it's related id if it's in another table), but not the qty.
var topSales = db.sales.GroupBy(x => x.title)
.Select(g => new
{
Title = g.Key,
Qty = g.Sum(x => x.qty)
})
.OrderByDescending(x => x.Qty)
.Select(x => new Publication
{
PubID = x.Title.title_id,
Title = x.Title.title1
})
.Take(5)
.ToList();
Note that I've omitted the join statement assuming that you have a foreign key between sales.title_id -> title.id, and you are using LINQ to SQL. Also note that I've avoided using the query syntax in favor of the chained method syntax, I think it's much clear in this use case (although not always true, ie: cross-joins).
Also, SQL and LINQ have some similarities but don't let the names of clauses/methods fool you, LINQ is not SQL, IMHO, Microsoft just tried to make people comfortable by making it look similar ;)
EDIT: fixed GroupBy()
var result= (from p in sales
let k = new
{
Name = p.Name
}
group p by k into t
select new
{
Name = t.Name,
Qty = t.Sum(p => p.Qty)
}).OrderByDescending(i => i.Qty).Take(5);
You need to look at GroupBy; this will give you what you need
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b

LINQ GroupBy, whilst keeping all object fields

I've currently got this sample table of data:
ID | Policy ID | History ID | Policy name
1 | 1 | 0 | Test
2 | 1 | 1 | Test
3 | 2 | 0 | Test1
4 | 2 | 1 | Test1
Out of this, I want to group by the Policy ID and History ID (MAX), so the records I want to be kept are ID's 2 and 4:
ID | Policy ID | History ID | Policy name
2 | 1 | 1 | Test
4 | 2 | 1 | Test1
I've tried to do this in LINQ and stumbling on the same issue every time. I can group my entities, but always into a group where I have to re-define the properties, rather than have them kept from my Policy objects. Such as:
var policies = _context.Policies.GroupBy(a => a.intPolicyId)
.Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
This simply just brings out a list of objects which have "Policy ID" and "History ID" within them. I want all the properties returned from the Policies object, without having to redefine them all, as there are around 50+ properties in this object.
I tried:
var policies = _context.Policies.GroupBy(a => a.intPolicyId)
.Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
PolicyObject = group;
});
But this errors out.
Any ideas?
Group by composite key
_context.Policies.GroupBy(a => new {a.intPolicyId, *other fields*}).Select(
group=> new {
PolicyId = group.Key.intPolicyId,
HistoryId = group.Max(intHistoryId),
*other fields*
}
);
Another way - grab histories, than join back with the rest of the data, something like this (won't work out of the box, will require some refining)
var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
var finalData = from h in historyIDs
join p in _context.Policies on h.intPolicyId equals p.intPolicyId
select new {h.HistoryId, *all other policy fields*}
And yet another way, even simpler and not require a lot of typing :):
var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
var finalData = from h in historyIDs
join p in _context.Policies on h.PolicyId equals p.intPolicyId && h.HistoryId equals p.HistoryId
select p
Basically it's somewhat equivalent to the following SQL query:
select p.*
from Policy p
inner join (
select pi.policyId, max(pi.historyId)
from Policy pi
group by pi.policyId
) pp on pp.policyId = p.policyId and pp.historyId = p.historyId
In LINQ to Objects, I'd do this as
var policies = _context.Policies
.GroupBy(a => a.intPolicyId)
.Select(g => g.OrderByDescending(p => p.intHistoryID).First());
but your _context impleis there might be a database involved and I'm not 100% sure this will translate.
Basically it groups by the policy ID as you'd expect, then within each group orders by history ID and from each group selects the row with the highest history ID. It returns exactly the same type as is found in Policies.

Categories

Resources