Normally, I do this:
var a = from p in db.Products
where p.ProductType == "Tee Shirt"
group p by p.ProductColor into g
select new Category {
PropertyType = g.Key,
Count = g.Count() }
But I have code like this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select("new ( Key, it.Count() as int )");
What syntax could I alter to produce identical results, i.e., how do I do a projection of Category from the second Linq statement?
I know in both that g and it are the same and represent the entire table record, and that I am pulling the entire record in just to do a count. I need to fix that too. Edit: Marcelo Cantos pointed out that Linq is smart enough to not pull unnecessary data. Thanks!
Why would you have to do it at all? Since you still have all of the information after the GroupBy call, you can easily do this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select(c => new Category {
PropertyType = g.Key, Count = g.Count()
});
The type of Products should still flow through and be accessible and the regular groupings/filtering shouldn't mutate the type that is flowing through the extension methods.
Related
I've been looking at other threads here to learn how to do a GroupBy in linq. I am following the EXACT syntax that has worked for others, but, it's not working.
Here's the query:
var results = from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.ContactID, // <--- Error here
Amount = amount
};
pending is a List<T> that contains, among other columns, ContactID and Amount, but those are the only two I care about for this query.
The trouble is, inside the the select new, the g. won't give me any of the columns inside the original list, pending. And when I try, I get:
IGrouping <int, LeadPurchases> does not contain a definition for ContactID, and no extension method blah blah blah...
This is the SQL I am trying to emulate:
SELECT
lp.PurchasedFromContactID,
SUM (lp.Amount)
FROM
LeadPurchases lp
GROUP BY
lp.PurchasedFromContactID
You are grouping on the basis of ContactID, so it should be the Key for the result, So you have to use g.Key instead of g.ContactID; Which means the query should be like the following:
from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.Key,
Amount = amount
};
updates :
If you want to perform grouping based on more than one column then the GroupBy clause will be like this:
group p by new
{
p.ContactID,
p.Field2,
p.Field3
}into g
select new PaymentItemModel()
{
ContactID = g.Key.ContactID,
anotherField = g.Key.Field2,
nextField = g.Key.Field3
};
i've got a List of objects, lets call them Product, which each of them contains a bunch of properties and also a List of Version (which are also objects).
Version also has a bunch of properties and does contain a List of Customer (which again are objects).
Customer again has properties, one of them is its ID (=Guid).
What i try to do is to make a List of Product, selected by a certain ID of its Product.VersionList.Version.ID.
I would prefere a join query, but every efficient way is welcome. I tried so far this, but because i have only a single ID to compare with, i don't know how to construct the join.
lp = List<Entity.Product>;
g = GetGuid();
var query = from product in Entity.ProductCollection
join g in g
on product.Version.Where(x => x.id == g)
select product;
lp.AddRange(query);
I'm guessing you mean:
var query = from product in Entity.ProductCollection
where product.Version.Any(x => x.id == g)
select product;
i.e. select all the products that have a version where the id matches the guid you were thinking of.
Note that joining to the versions would cause product duplication if any product has multiple matching versions.
Try this .... May be you wants more deep digging on it..
var query = from Product product in pc
from varsion in product.Version
let v= varsion as Entity.Version
where v.id == g
select product;
var query = Entity.ProductCollection.Where(p => p.Version.Any(v => v.Id == g));
You can use Any rather than having to do a self join.
I have an object called Ticket with that contains a list of objects called TicketActions. The Ticket object has a field called Date_Closed and the Actions object has a field called Action_Date:
Ticket
Date_Closed
TicketActions
-Action_Date
What I'm trying to do is order a List of tickets (List) based on the latest date of each Action in ascending order where the Ticket does not have a value for Date_Closed. The goal is to load this list into a listview and show tickets in a way that displays tickets in order on the page, placing the ones that have gone the longest without an action at the top. Does that make sense?
Here is what I ended up with so far that isn't working:
protected List<FullTicket> BuildTickets(int ticketsToShow)
{
using (var db = new SupportLogDBDataContext())
{
var result =
(from ticket in db.Support_Tickets
join status in db.Ticket_Statuses on ticket.Status_ID equals status.ID
select new FullTicket
{
TicketID = ticket.ID,
DateOpened = (DateTime)ticket.Date_Opened,
DateClosed = (DateTime)ticket.Date_Closed,
Subject = ticket.Subject,
Status = new KeyPair { Key = status.Status, Value = status.ID },
CreatedBy = new GuidPair { Key = ticket.Reported_By, Value = (Guid)ticket.AD_GUID },
TicketActions =
(from a in db.Ticket_Actions
where a.Ticket_ID == ticket.ID
select a).ToList()
}).Take(ticketsToShow).ToList();
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max()); //error on this line (invalid arguments)
return result;
}
}
People reply quick here!
Try this:
var result = (from ticket in tickets
where !ticket.DateClosed.HasValue
select ticket).OrderByDescending(t => (from a in t.TicketActions
select a.ActionDate).Max());
From here you can take as many as you need.
David B's analysis is slightly off. The line...
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
... will not compile because the argument to the Where method is not a lambda expression or delegate.
I would suggest this solution (assuming that the relevant property of the TicketAction type is ActionDate):
return result.Where(i => i.DateClosed == null)
.OrderBy(i => i.TicketActions.Max(a => a.ActionDate));
Or, in query comprehension syntax:
return from i in result
where i.DateClosed == null
orderby i.TicketActions.Max(a => a.ActionDate)
select i;
Here is some simple code.
var sorted = tickets.Where(t => t.DateClosed == null)
.OrderBy(t => t.TicketActions.Max(ta => ta.Action_Date.Ticks));
Sorry, I prefer LINQ function syntax, but if you want it in query syntax, it shouldn't be too hard to convert.
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
This line generates an error because TicketActions.Max() is not defined.
You need to project TicketAction into something that can be Max'd. For example:
result.OrderBy(i =>
i.TicketActions
.Where(ta => i.DateClosed == null)
.Select(ta => ta.Id)
.Max()
);
Also note:
OrderBy does not modify its source. OrderBy returns an ordered IEnumerable, which you didn't assign anywhere.
OrderBy's enumerable is deferred, and you want a List result instead, so you should call ToList.
You are accessing Ticket.TicketActions outside of the query. This will cause one database round trip per ticket to load that property.
Here is a modification to your query that avoids the problems mentioned above by ordering and using navigational properties within the query.
from ticket in db.Support_Tickets
where ticket.DateClosed == null
let lastDate = ticket.TicketActions
.Select(ta => ta.ActionDate)
.OrderByDescending(date => date)
.FirstOrDefault()
let ticketStatus = ticket.TicketStatus
order by lastDate
select new FullTicket
{
...
}
I currently a list of a Supplier class, within that supplier class is a list of orders.
Each order has a userID and an empty string variable for username.
I then have a list of users which contains userID and username.
The way I am doing this now is:
foreach(supplier s in SupplierList)
{
foreach (order o in s.childorders)
{
user u = _users.First(p => p.userid == o.userid);
o.username = u.username;
}
}
I feel this might be a little inefficient and I was wondering if it is possible to compact it down into one linq query?
The logic should be
set supplierslist.childorders.username to the value in _users where supplierslist.childorders.userid == _users.userid.
Im fairly new to Linq so any advice for this would be apreciated, or also if its a bad idea and to leave it as it is / reasons why would be good too.
Thanks
What you want to do here is iterate over a collection (many collections, really, but it doesn't make a difference) and mutate its members. LINQ is not really targeted at performing mutating operations but rather at querying. You can do it with LINQ, but it's against the spirit of the tool.
If you are constructing the SupplierList yourself, it might be possible to fetch the data appropriately with LINQ so that it comes pre-populated as you want it to be.
Otherwise, I 'd leave the foreach as it is. You can make a dictionary that maps ids to users to make the inner loop faster, but that's your call and it depends on your data size.
var orderUserPairs = SupplierList
.SelectMany(s => s.ChildOrders)
.Join(_users, o => o.UserId, u => u.userId, (Order, User) => new {Order, User});
foreach (var orderUserPair in orderUserPairs)
orderUserPair.Order.username = orderUserPair.User.username;
Though having both username and userId as part of order looks suspicious.
First a question...
It looks like you are operating on every order. Why do you need to cycle through the supplierlist first since you don't seem to be using it inside the loop? Unless there are orders that don't belong to any supplierlist, you might be able to skip that step.
If that isn't the case, then I think you can use a join. If you aren't familiar with the syntax for joins in linq, this is one (simplified) way to approach it:
var x = from S in SupplierList
join C in childorders on C.supplierlistID equals S.ID
where [whatever you need here if anything]
select new { field1, field2};
foreach var y in x
{
}
Note I assumed a foreign key in childorders to supplierlist. If that isn't the case you will have to modify accordingly.
Hope that helps.
You need to use SelectMany or join depending on weather you are using linq-to-sql or linq with local collections. If you are using local collections the better way is to use join, else use SelectMany.
Like this...join:
var selection = (from s in SupplierList
join o in s.childholders on s.userid equals o.userid
select new { username = o.username);
or, in case of linq-to-sql:
var selection = (from s in SupplierList
from o in s.childholders
select { username = o.username);
You can then use the anonymous type you projected the way you want.
I agree with Jon, but you could say:
var orders = (from s in supplier
from o in s.childorders
select new
{
Order = o,
User = _users.First(p => p.userid == o.userid)
}).ToList();
foreach(var order in orders) {
order.Order.username = order.User.username;
}
Untested of course :)
If users list contains many elements, it can be really slow so I'd use a temporary dictionary:
var userById = users.GroupBy(x => x.userid)
.ToDictionary(x => x.Key, x => x.First());
foreach(var order in supplier.SelectMany(x => x.childorders))
{
order.username = userById[order.userid].username;
}
var query =context.Categories.Include("ChildHierarchy")
.Where(c =>
context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID));
Questions:
I need to include some data from another Navigation Propery (".Include("otherprop")")
Is it possible to do a select new after all of this?
Thanks
The title to your question intrigued me with the words "Crazy Query", and yes, you're right, it is a bit crazy.
You have a .Where(...) clause with the following predicate:
ch => ch.ParentCategoryID == ch.ParentCategoryID
Now that's going to always be true. So I guess that you're trying to do something else. I'll have a crack at what that might be at the end of my answer.
I then did some cleaning up of your query to get a better idea of what you're doing. This is what it now looks like:
var query =
context
.Categories
.Where(c => context
.CategoryHierarchy
.Select(ch => ch.ChildCategoryID)
.Contains(c.CategoryID));
So rather than use nested queries I would suggest something like this might be better in terms of readability and possibly performance:
var query =
from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID into ghs
where ghs.Any()
select c;
This gives the same results as your query so hopefully this is helpful.
I do get the impression that you're trying to do a query where you want to return each Category along with any child categories it may have. If that's the case here are the queries you need:
var lookup =
(from c in context.Categories
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
select new { Category = c, Children = lookup[c.CategoryID], };
The lookup query first makes a join on categories and the category hierarchies to return all children categories and their associated ParentCategoryID and then it creates a lookup from ParentCategoryID to a list of associated Category children.
The query now just has to select all categories and perform a lookup on the CategoryID to get the children.
The advantage of using the .ToLookup(...) approach is that it easily allows you to include categories that don't have children. Unlike using a Dictionary<,> the lookup does not throw an exception when you use a key that it hasn't got a value for - instead it returns an empty list.
Now, you can add back in the .Include(...) calls too.
var lookup =
(from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
join h in context.CategoryHierarchy
on c.CategoryID equals h.ChildCategoryID
select new { ParentCategoryID = h.ParentCategoryID, Category = c, }
).ToLookup(x => x.ParentCategoryID, x => x.Category);
var query =
from c in context.Categories
.Include("ChildHierarchy")
.Include("otherprop")
select new { Category = c, Children = lookup[c.CategoryID], };
Is that what you're after?
1) Then add it - context.Categories.Include("ChildHierarchy").Include("OtherCollection");
2) Absolutely, yes
var query = context.Categories
.Include("ChildHierarchy")
.Include("OtherProp")
.Where(c => context.CategoryHierarchy.Where(ch => ch.ParentCategoryID == ch.ParentCategoryID)
.Select(ch => ch.ChildCategoryID).Contains(c.CategoryID))
.Select(c => new { c.A, c.B, c.etc });