GroupBy with linq entity poco - c#

I have this entities
public class Counter
{
public int DocEntry { get; set; }
public int LineId { get; set; }
public string Item { get; set; }
public decimal Quantity { get; set; }
public string FromWarehouse { get; set; }
public string ToWarehouse { get; set; }
public virtual List<Batch> batchs { get; set; }
}
public class Batch
{
public string BatchNumber { get; set; }
public decimal Quantity { get; set; }
}
And I have List count,I should group the elements of the list based on the Item value, FromWarehouse, ToWarehouse, and the result should be grouped with summed quantity and the list of Batch merged, I tried cycling the Counter list using the foreach method and inserting in a new list the first element, in the subsequent iterations if the current row reflected the values of the one already in the list I summarized the quantities and added to the Batch list the elements of the Batch list of the i-th line, otherwise I added a new line, this approach I it seemed too complicated, not being very expert on linq or in any case is there an easier way to manage the group by?
This is my method:
public static List<Counter> GroupBy(List<Counter> list)
{
List<Counter> rows = new List<Counter>();
int i = 0;
foreach (Counter elem in list)
{
if (list.First() == elem)
{
rows.Add(elem);
i++;
}
else
{
if (elem.Item == rows.ElementAt(i).Item &&
elem.FromWarehouse == rows.ElementAt(i).FromWarehouse &&
elem.ToWarehouse == rows.ElementAt(i).ToWarehouse)
{
rows.First().Quantity += elem.Quantity;
rows.First().batchs.Add(elem.batchs.First());
}
else
{
rows.Add(elem);
i++;
}
}
}
return rows;
}
This is the solution:
public static List<Counter> GroupBy(List<Counter> list)
{
List<Counter> rows = new List<Counter>();
foreach (Counter elem in list)
{
if (rows.Any(x=>x.Item == elem.Item &&
x.FromWarehouse == elem.FromWarehouse &&
x.ToWarehouse == elem.ToWarehouse))
{
rows.First().Quantity += elem.Quantity;
rows.First().batchs.Add(elem.batchs.First());
}
else
{
rows.Add(elem);
}
}
return rows;
}

Let's try to write the Linq, step by step.
"I have List count":
List<Counter> count = ...
"I should group the elements of the list based on the Item value, FromWarehouse, ToWarehouse":
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
"result should be grouped with summed quantity and the list of Batch merged", i.e. you should Aggregate items within each chunk
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
.Select(chunk => new {
key = chunk.Key,
summary = chunk.Aggregate(
Tuple.Create(0m, new List<Batch>()), // initial empty
(s, a) => Tuple.Create(s.Item1 + a.Quantity, // add
s.Item2.Concat(a.batchs).ToList()) // merge
})
Finally, let's represent the result in more convenient (readable) format:
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
.Select(chunk => new {
key = chunk.Key,
summary = chunk.Aggregate(
Tuple.Create(0m, new List<Batch>()),
(s, a) => Tuple.Create(s.Item1 + a.Quantity,
s.Item2.Concat(a.batchs).ToList())
})
.Select(item => new {
Item = item.key.Item,
FromWarehouse = item.key.FromWarehouse,
ToWarehouse = item.key.ToWarehouse,
Quantity = item.summary.Item1,
Batches = item.summary.Item2
}); // Add ToArray() if you want to materialize

Related

how to select a row on a table based on a condition in another row of another table?

I want to show only the rows from one table that meet a certain condition in another table. Example on the controller:
using(inventarioEntitiesDBA dc = new inventarioEntitiesDBA())
{
ProductandBodViewModel finalitem = new ProductandBodViewModel();
var m_List = dc.showcase.Where(x => x.prod_on_showcase > 0).ToList();
}
With the code above it's possible to display only the items that meets the condition, prod_on_showcase > 0. The ProductandBodViewModel is a model that I created to show two models on a same view.
The ProductandBodViewModel model:
public class ProductandBodViewModel
{
public List<inventory> inventory { get; set; }
public List<showcase> showcase { get; set; }
}
According to this condition (prod_on_showcase > 0) I want to show other information that is related to the table showcase table.
The model of the showcase table:
public class showcaseViewModel
{
public int id_inventory { get; set; }
public int prod_on_showcase { get; set; }
}
The model of the inventory table:
public partial class inventory
{
public int id_inventory { get; set; }
public Nullable<int> prod_on_inventory { get; set; }
public string prod_code { get; set; }
}
The relation between the tables is the id_inventory field.
In this case, the items displayed for the showcase table are 3, so the inventory table must also display 3 items.
With the code below it's possible to display info of the table showcase that meets the condition but with the other table (inventory) only display the first element that it's found according to the condition prod_on_showcase > 0.
using(inventarioEntitiesDBA dc = new inventarioEntitiesDBA())
{
ProductandBodViewModel finalitem = new ProductandBodViewModel();
var m_List = dc.showcase.Where(x => x.prod_on_showcase > 0).ToList();
var m_Each = dc.showcase.Where(e => e.prod_on_showcase > 0).Count();
for(int i = 0; i < m_Each; i++)
{
var prodEach = dc.inventory.Where(x => x.prod_on_showcase > 0).FirstOrDefault();
var b = dc.inventory.Where(a => a.id_inventory == prodEach.id_inventory).ToList();
finalitem.bod = b;
}
finalitem.showcase = m_List;
return View(finalitem);
}
You're asking var prodEach = dc.inventory.Where(x => x.prod_on_showcase > 0).FirstOrDefault(); to always return the First(or default) element in the array.
Linq's SelectMany is useful for what you're trying to do.
using(inventarioEntitiesDBA dc = new inventarioEntitiesDBA())
{
ProductandBodViewModel finalitem = new ProductandBodViewModel();
var m_List = dc.showcase.Where(x => x. > 0).ToList();
var m_List = showcase.Where(x => x.prod_on_showcase > 0).ToList();
finalitem.showcase = m_List.SelectMany(prodEach
=> dc.inventory.Where(a => a.id_inventory == prodEach.id_inventory)
finalitem.showcase = m_List;
return View(finalitem);
}```

Find all duplicates in a list in C#

I have a Custom class shown below
internal class RecurringClusterModel
{
public int? From { get; set; }
public int? To { get; set; }
public string REC_Cluster_1 { get; set; }
public string REC_Cluster_2 { get; set; }
public string REC_Cluster_3 { get; set; }
public string REC_Cluster_4 { get; set; }
public string REC_Cluster_5 { get; set; }
public string REC_Cluster_6 { get; set; }
public string REC_Cluster_7 { get; set; }
public string REC_Cluster_8 { get; set; }
public string REC_Cluster_9 { get; set; }
public string REC_Cluster_10 { get; set; }
I have a List of this class
List<RecurringClusterModel> recurringRecords = new List<RecurringClusterModel>();
The data can be in the below format
recurringRecords[0].REC_Cluster_1 = "USA";
recurringRecords[0].REC_Cluster_2 = "UK";
recurringRecords[0].REC_Cluster_3 = "India";
recurringRecords[0].REC_Cluster_4 = "France";
recurringRecords[0].REC_Cluster_5 = "China";
recurringRecords[1].REC_Cluster_1 = "France";
recurringRecords[1].REC_Cluster_2 = "Germany";
recurringRecords[1].REC_Cluster_3 = "Canada";
recurringRecords[1].REC_Cluster_4 = "Russia";
recurringRecords[1].REC_Cluster_5 = "India";
....
I want to find the duplicate records between all the Cluster properties..This is just a subset I have 50 properties till REC_Cluster_50. I want to find out which countries are getting duplicated between the 50 cluster properties of the list.
So in this case India and France are getting duplicated. I can group by individual property and then find out the duplicate by getting the count but then I d have to do it for all the 50 Rec_Clusters property. Not sure if there is a better way of doing it.
Thanks
Since you want to capture the From and To, I suggest you structure your class like this:
internal class RecurringClusterModel
{
public int? From { get; set; }
public int? To { get; set; }
public IEnumerable<string> REC_Clusters { get; set; }
}
Then you can search for duplicates:
var dupes = recs
.Select(r => new
{
r.From,
r.To,
DuplicateClusters = r.REC_Clusters.GroupBy(c => c)
.Where(g => g.Count() > 1) // duplicates
.SelectMany(g => g) // flatten it back
.ToArray() // indexed
})
.Where(r => r.DuplicateClusters.Any()) //only interested in clusters with duplicates
.ToArray();
EDIT
If you want all duplicates, then it will be:
var allDupes = recs.SelectMany(r => r.REC_Clusters)
.Select(r => r.GroupBy(c => c)
.Where(g => g.Count() > 1)
.SelectMany(g => g))
.Where(r => r.Any()).ToArray();
But now you lose track of the From/To
I would add an enumerable to your class that iterates over all properties of that class:
internal class RecurringClusterModel
{
public string REC_Cluster_1 { get; set; }
public string REC_Cluster_2 { get; set; }
public string REC_Cluster_3 { get; set; }
public IEnumerable<string> Clusters => GetAllClusters();
private IEnumerable<string> GetAllClusters()
{
if (!string.IsNullOrEmpty(REC_Cluster_1))
yield return REC_Cluster_1;
if (!string.IsNullOrEmpty(REC_Cluster_2))
yield return REC_Cluster_2;
if (!string.IsNullOrEmpty(REC_Cluster_3))
yield return REC_Cluster_3;
}
}
With this you can flatten the list to the individual clusters and then group by. If you need the original object back again, you have to provide it while flattening. Here is an example:
var clusters = Enumerable
.Range(1, 10)
.Select(_ => new RecurringClusterModel
{
REC_Cluster_1 = _Locations[_Random.Next(_Locations.Count)],
REC_Cluster_2 = _Locations[_Random.Next(_Locations.Count)],
REC_Cluster_3 = _Locations[_Random.Next(_Locations.Count)],
})
.ToList();
var dictionary = clusters
// Flatten the list and preserve original object
.SelectMany(model => model.Clusters.Select(cluster => (cluster, model)))
// Group by flattened value and put original object into each group
.GroupBy(node => node.cluster, node => node.model)
// Take only groups with more than one element (duplicates)
.Where(group => group.Skip(1).Any())
// Depending on further processing you could put the groups into a dictionary.
.ToDictionary(group => group.Key, group => group.ToList());
foreach (var cluster in dictionary)
{
Console.WriteLine(cluster.Key);
foreach (var item in cluster.Value)
{
Console.WriteLine(" " + String.Join(", ", item.Clusters));
}
}

Sorting and Updating a Generic List of Object based on a Sub Object

I have the following objects:
public class TestResult
{
public string SectionName { get; set; }
public int Score { get; set; }
public int MaxSectionScore { get; set; }
public bool IsPartialScore { get; set; }
public string Name { get; set; }
public int NumberOfAttempts { get; set; }
}
public class TestResultGroup
{
public TestResultGroup()
{
Results = new List<TestResult>();
Sections = new List<string>();
}
public List<TestResult> Results { get; set; }
public List<string> Sections { get; set; }
public string Name { get; set; }
public int Rank { get; set; }
}
So, a TestResultGroup can have any number of results of type TestResult. These test results only differ by their SectionName.
I have a List<TestResultGroup> which I need to sort into descending order based on a score in the Results property, but only when Results has an item whos SectionName = "MeanScore" (if it doesnt have this section we can assume a score of -1). How would I go about ordering the list? Ideally I would also like to apply the result of this ordering to the Rank property.
Many Thanks
List<TestResultGroup> groups = ...
// group test result groups by the same score and sort
var sameScoreGroups = groups.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(gr => gr.Key);
int rank = 1;
foreach (var sameScoreGroup in sameScoreGroups)
{
foreach (var group in sameScoreGroup)
{
group.Rank = rank;
}
rank++;
}
// to obtain sorted groups:
var sortedGroups = groups.OrderByDescending(gr => gr.Rank).ToArray();
Or even write one expression with a side effect:
List<TestResultGroup> groups = ...
int rank = 1;
var sortedGroups = groups
.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(grouping => grouping.Key)
.SelectMany(grouping =>
{
int groupRank = rank++;
foreach (var group in grouping)
{
group.Rank = groupRank;
}
return grouping;
})
.ToArray(); // or ToList

Reading the dictionary element values

I have the below code where I want to read the value of dictionary 'filter' using for loop.'filter[1]'again has two values. Since dictionary is key value pair, how do I access the element value below.
class program
{
public string RecSys { get; set; }
public string AnsSys { get; set; }
public string IsAddOn { get; set; }
public string Country { get; set; }
public static void Main()
{
program p1 = new program();
program p2 = new program();
program p3 = new program();
List<program> List = new List<program>();
p1.RecSys = "RY1";
p1.AnsSys = "CSCLRC";
p1.IsAddOn = "P";
p1.Country = "India";
List.Add(p1);
p2.RecSys = "RY1";
p2.AnsSys = "APEX";
p2.IsAddOn = "primary";
p2.Country = "Pakistan";
List.Add(p2);
p3.RecSys = "RY1";
p3.AnsSys = "APEX";
p3.IsAddOn = "Addon";
p3.Country = "Pakistan";
List.Add(p3);
var filter = List.GroupBy(item => new { item.RecSys, item.AnsSys }).ToDictionary(grp => grp.Key, grp => grp.ToList()).Values;
for (int i = 0; i < filter.Count; i++)
{
// read the values. 'filter[1]'again has two values
}
}
}
You can fetch the values using two foreach loop like this:-
foreach (var item in filter)
{
foreach (var innerItem in item)
{
Console.WriteLine(innerItem.IsAddOn);
Console.WriteLine(innerItem.Country);
//and so on..
}
}
Or else if you want all the Values of dictionary at once then you can flatten it using SelectMany:-
var filter = List.GroupBy(item => new { item.RecSys, item.AnsSys })
.ToDictionary(grp => grp.Key, grp => grp.ToList())
.SelectMany(x => x.Value);
and finally iterate over the items using a single foreach loop:-
foreach (var item in filter)
{
Console.WriteLine(item.Country);
Console.WriteLine(item.AnsSys);
//and so on..
}

Update List Concatanate string

I have a class
public class RoomAvail
{
public int OCCUPANCY { get; set; }
public int ChildCount { get; set; }
public string ChildAges { get; set; }
}
I have List of RoomAvail i.e List rooms;
rooms = { new RoomAvail{OCCUPANCY =1,ChildCount =1,ChildAges ="1" }
new RoomAvail{OCCUPANCY =2,ChildCount =2,ChildAges ="1,2" }
new RoomAvail{OCCUPANCY =3,ChildCount =3,ChildAges ="1,2,3" }
}
I have populated the value of rooms.
I have List listAge = {12,13,14,14}
My requirement :
if any OCCUPANCY in the list =2 , I should append the value of listAge in ChildAges .
Final Output:
rooms = { new RoomAvail{OCCUPANCY =1,ChildCount =1,ChildAges ="1" }
new RoomAvail{OCCUPANCY =2,ChildCount =2,ChildAges ="1,2,12,13,14,15" }
new RoomAvail{OCCUPANCY =3,ChildCount =3,ChildAges ="1,2,3" }
}
I want to update rooms variable only.
I am doing:
rooms.Where(y => y.OCCUPANCY == 2)
.Select(x => x.ChildAges)
.Aggregate((i, j) => i + listAge + j);
Thanks
LINQ doesn't modify your object(s). Use a loop for that
foreach(var room in rooms.Where(y => y.OCCUPANCY == 2))
{
room.ChildAges = string.Join(",", room.ChildAges.Split(',').Concat(listAge));
}
You could try this one:
// Get the rooms with OCCUPANCY value equal to 2.
rooms = rooms.Where(x=>x.OCCUPANCY==2);
// Iterate through the selected rooms.
foreach(var room in rooms)
{
// Build a list of integers based on the room's list age.
List<int> currentListAge = room.ChildAges.Split(',').Select(int.Parse).ToList();
// Concat the currentListAge with the listAge, order the resulting list and
// then build a comma separated value list.
room.ChildAges = String.Join(",", currentListAge.Concat(listAge)
.OrderBy(x=>x)
.Select(x=>x.ToString())
.ToList());
}

Categories

Resources