Group By object within a nested list linq - c#

What i am trying to do is i have sales order object which contains
sales order header
and list of order lines
within the order lines i have the actual order line, product information object and stock information object:
public class SalesOrder
{
public Header SalesHeader { get; set; }
public List<OrderLineProductInfo> OrderLines { get; set; }
}
public class OrderLineProductInfo
{
public Line salesOrderLine { get; set; }
public Info ProductInfo { get; set; }
public Stock ProductStock { get; set; }
}
so i can get a list of SalesOrder Objects so example sales order index 0
has 2 lines the ProductStockObject within one of these lines has Preferred Supplier of abc and the other line has Preferred Supplier 123
i want to be able to group on the Preferred Supplier property
var separatePreferredSuppliers =
(from b in x.OrderLines
.GroupBy(g => g.ProductStock.PreferredSupplier )
select ...
).ToList();
not quite sure what comes next what needs to be selected? a new list of SalesOrder?
I want it so that it gives two instances of the sales order but split in 2 one for each preferred supplier

I think I get what you mean
from line in x.OrderLines
group line by line.ProductStock.PreferredSupplier into grouped
select new SalesOrder
{
OrderLines = grouped.ToList()
}
though I'm not sure how you populate your SalesHeader

easy when you know what the groupby function ruturns -- you want the key and the list:
.Select( (g) => new { supplier = g.Key, prodlist = g.ToList()}

Related

Filtering parent collection by grandchildren properties in EF Core

Using EF core 5 and ASP.NET Core 3.1, I am trying to get a filtered collection based on a condition on its grandchildren collection.
I have the following entities:
public class Organisation
{
public int Id { get; set; }
public int? OrganisationId { get; set; }
public IEnumerable<Customer> Customers { get; set; }
}
public partial class Customer
{
[Key]
public uint Id { get; set; }
public int? EmployerId { get; set; }
public int? OrganisationId { get; set; }
public List<TimecardProperties> TimecardsProperties { get; set; }
}
public partial class TimecardProperties
{
[Key]
public int Id { get; set; }
public int? EmployerId { get; set; }
public int? Week { get; set; }
public short? Year { get; set; }
}
The goal is to get all Organisations that have at least one customer and the customer has at least 1 timecard property that is in week=34 and year=2021.
So far I have tried the following:
////necessary join to get Organisations for user id
IQueryable<Organisation> ouQuery = (from cou in _dbContext.Organisations
join uou in _dbContext.table2 on cou.OrganisationId equals uou.OrganisationId
where uou.UsersId == int.Parse(userId)
select cou)
.Where(cou => cou.Customers.Where(c => c.TimecardsProperties.Count > 0).Any())
.Include(cou => cou.Customers.Where(c => c.TimecardsProperties.Count > 0))
.ThenInclude(c => c.TimecardsProperties.Where(tc => tc.tWeek == 34 && tc.Year > 2020))
;
This returns a organisation list that each have a customers list but some customers have a count of timecards 0. I don't want to have organisation in the returned list that does not have at least one item in the timecards collection.
Also, it is too slow, and if I try to filter the produced list its even
slower (over 15 seconds)
I have also tried a raw sql query on the organisation db context but it is again very slow:
select distinct count(id) from organisation a where organisation_id in (
select organisation_id from customers where employer_id in (select distinct employer_id from timecards a
inner join timecard_components b on a.id=b.timecards_id
where week IN(
34) and year in (2021,2021) and invoice !=0 and type = 'time'
group by employer_id, week)
);
In general, I want to know the the total
count of the returned organisation collection for pagination (so I don't need to include all attributes of each entity)
as well as return only a part of the correct results, which satisfy the conditions,
an organisation list that has at least 1 timecards in
their customers by executing the query in the end like so:
ouQuery.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
I have also tried the EntityFramework.Plus and projection with no results.
How could I write this to achieve getting the total count of the organisation list and a part of these results (first 10) to display to the user?
Use navigation properties. This is the query you want:
var orgsQuery = dbContext.Organizations
.Where( o => o.Customers.Any( c =>
c.TimecardProperties.Any( tp =>
tp.Year = 2021
&& tp.Week = 34 ) ) );
Add includes and other predicates as needed

How to take the Max value from a List of object where the same objects exists with many duplicate rows

I have list of objects which returned from database, sometimes it may have even 25k to 50k records.
I am grouping them right now with one order to make the group of corresponding items.
But also I need to take the max version of the objects in that list for each grouped list where so many duplicate versions also will be available.
public class Items
{
public int ID { get; set; }
public string ProductName { get; set; }
public int ProductId { get; set; }
public int Version { get; set; }
}
public class ItemInfo : ReleaseInfo
{
public virtual IGrouping<int,Items> ItemList { get; set; }
}
For example,
var groupedList = (from cn in ItemList
group cn by cn.ProductId into itemGroup
orderby itemGroup.Key ascending
select new ItemInfo
{
cn.ProductId = itemGroup.Key,
ProductName = itemGroup.FirstOrDefault().ProductName,
ItemList= itemGroup
});
This Itemlist is returned from DB and columns are below,
ID, Name, ProductName, ProductId and Version
So now we need to check with same ID and ProductId is there any items duplicated for each groupedList, if then we need to take max value of that using the Version column and keep only that in the list.
Example dataset:
First Grouped List:
**ROW 1** ID - 1, Product ID - 1 Version 1
**ROW 2** ID - 1, Product ID - 1 Version 2
**ROW 3** ID - 1, Product ID - 1 Version 3
Second Grouped List:
**ROW 1** ID - 1, Product ID - 2 Version 1
**ROW 2** ID - 2, Product ID - 2 Version 2
from the above data it need to pass ID-1, product ID- 1, Version 3 from First list and second row from second list to the next steps.
How we can do the group by, If it is one single entity I can do orderbydescending and take the first element but i need to loop through the list of items where other entities also available.
I'm giving this answer as per my understanding, If I miss anything or miss understood your requirement please comment, I'll update the answer.
Assuming your classes are as below:
public class Items
{
public int ID { get; set; }
public string ProductName { get; set; }
public int ProductId { get; set; }
public int Version { get; set; }
}
public class ItemInfo
{
public virtual IEnumerable<IGrouping<int, Items>> ItemList { get; set; }
}
LINQ:
List<Items> itemLists = new List<Items>();
var filteredItems = itemLists.Where(x => x.Version == itemLists.Where(y => y.ProductId == x.ProductId).Max(z => z.Version));
var itemsInfo = new ItemInfo { ItemList = filteredItems.GroupBy(x => x.ProductId) };

Way to create a tree from two lists with a matching ID

Let`s say I have the following two models :
public class Order
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public string Street { get; set; }
public IList<Appointment> Appointments { get; set; }
}
public class Appointment
{
public Guid Id {get; set;}
public DateTime StartDate {get; set;}
public DateTime EndDate {get; set;}
public Guid OrderId { get; set; }
}
I have a list with Order items (all with the list of Appointments empty) and a list with all appointments and I don`t know how to match elements from the two lists in order to obtain the Order object with all corresponding appointments (based on OrderId).
How can I do that in an efficient manner ? I don`t want to iterate through all orders, and assign the corresponding appointments..
First, make a dictionary of Appointment by OrderId:
var appointmentByOrderId = appointments
.GroupBy(a => a.OrderId)
.ToDictionary(g => g.Key, g => g.ToList());
After that walk through the list of Orders, and set Appointments to the list from the dictionary:
foreach (var order in orders) {
List<Appointment> appointmentList;
if (appointmentByOrderId.TryGetValue(order.Id, out appointmentList) {
order.Appointments = appointmentList;
} else {
order.Appointments = new List<Appointment>();
}
}
The else branch is optional. You can drop it if you do not want to set an empty list to orders with empty appointment lists.
One way would be to "group join" the two lists and project a new collection:
var neworders =
from o in orders
join a in appointments
on o.Id equals a.OrderId
into g
select new Order
{
Id = o.Id,
Name = o.Name,
Location = o.Location,
Street = o.Street,
Appointments = g.ToList()
};
Obviously this creates new Order objects - if that is not desirable another option would be to loop through all of the orders and "attach" the matching appointments - using ToLookup to pre-group them:
var groups = appointments.ToLookup(a => a.OrderId);
foreach(var o in orders)
o.Appointments = groups[o.Id].ToList();
Build a Dictionary<Guid, Order>, and populate it with all of your orders. Then go through the list of appointments and add to the proper order. For example (assuming you have a list called Orders and a list called Appointments):
var OrdersDictionary = Orders.ToDictionary(o => o.Id, o => o);
foreach (var appt in Appointments)
{
OrdersDictionary[appt.OrderId].Appointments.Add(appt);
}
There's probably a LINQ way to do that rollup all in a single statement, but I didn't take the time to puzzle it out.

Convert nested relational model to a string

I have a data model in MVC5 entity framework in which a post has a category. This category can be nested such as.
Top Level: 0
-> Lower Level: 1
-> Lowest Level: 2
This is represented in my model as:
public class CategoryModel
{
public int Id { get; set; }
public CategoryModel ParentCategory { get; set; }
public string Title { get; set; }
}
Now when I display my post which has (from the above example) category "Lowest Level 2", I would like to display
"Top level: 0 > Lower Level: 1 > Lowest Level: 2"
somewhere on that page to inform the user where they are.
Problem is I dont have any idea of how to do this.
Propably really simple (as with all things in lambda) but I don't really know how and my googling skills are really off.
Edit as per comment question:
The post is defined as this:
public class PostModel
{
public int Id { get; set; }
public CategoryModel Category { get; set; } // a post can only have one category
public string Text { get; set; }
}
What I want to do is follow the CategoryModel relation, and then keep following the Categories ParentCategory untill it is null. This is always a 1 to 1 relation.
More Edit:
I was fairly simply able to do this with a TSQL-CTE expression but still no idea how to convert this to lambda.
SQL:
;WITH Hierarchy(Title, CatId, LEVEL, CategoryPath)
AS
(
Select c.Title, c.Id, 0, c.Title
FROM Categories c
WHERE c.[ParentCategory_Id] IS NULL
UNION ALL
SELECT c.Title, c.Id, H.LEVEL+1, H.CategoryPath+' > '+c.Title
FROM Categories c
INNER JOIN Hierarchy H ON H.CatId = c.[ParentCategory_Id]
)
SELECT SPACE(LEVEL*4) + H.Title, *
FROM Hierarchy H
ORDER BY H.CategoryPath
Result:
Assuming you have an instance of CategoryModel you could write a function that will build a string list with the chain of all titles:
private void FormatCategories(CategoryModel model, List<string> result)
{
result.Add(model.Title);
if (model.ParentCategory != null)
{
FormatCategories(model.ParentCategory, result);
}
}
and then:
CategoryModel model = ... // fetch your model from wherever you are fetching it
var result = new List<string>();
FormatCategories(model, result);
Now all that's left is to reverse the order of elements in the list and join them to retrieve the final result:
result.Reverse();
string formattedCategories = string.Join(" -> ", result);
// At this stage formattedCategories should contain the desired result

LINQ Sum list of items grouped by type inside list

I have the following order object which contains a list of order addons. I am trying to create a report that shows all the addon types and their quantities summed.
public class Order {
public IList<OrderAddon> OrderAddons { get; set; }
}
public class OrderAddon {
public enum OrderType { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
This is where I am at and can't figure out if the entire query is wrong of I am just missing something.
var query = from order in Model.Orders
from addon in order.OrderAddons
group order by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.) << This is where I am stuck
};
When I hit . my intellisense is showing me properties in order object not the addon object.
That's because you're saying group order by ..., so the orderAddons object becomes a grouping of orders. You can use this if you're going to need properties from both objects:
from order in Model.Orders
from addon in order.OrderAddons
group new{addon, order} by addon.AddonType
into orderAddons select new
{
Name = orderAddons.Key,
Quantity = orderAddons.Sum(x => x.addon.Quantity)
};
If this is all the data you need, this is a little simpler:
from order in Model.Orders
from addon in order.OrderAddons
group order.Quantity by addon.AddonType
into quantityByAddonType select new
{
Name = quantityByAddonType.Key,
Quantity = quantityByAddonType.Sum()
};
an alternative syntax same result...
var result = Model.Orders
.SelectMany(order => order.OrderAddons)
.GroupBy(addon => addon.OrderType)
.Select(grouping => new
{
Name = grouping.Key,
Quantity = grouping.Sum(addon => addon.Quantity)
});

Categories