Convert categories into human readable breadcrumbs C# - c#

I need a function that converts categories into flats (see code bellow).
I get categories from the database and then I want to convert them into a breadcrumbs format so I can later display them in a combobox.
using System.Collections.Generic;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var categories = new List<ProductCategory>
{
new ProductCategory { ProductCategoryId = 1, ParentId = null, Name = "Drinks" },
new ProductCategory { ProductCategoryId = 2, ParentId = null, Name = "Food" },
new ProductCategory { ProductCategoryId = 3, ParentId = 1, Name = "Beers" },
new ProductCategory { ProductCategoryId = 4, ParentId = 1, Name = "Wines" },
new ProductCategory { ProductCategoryId = 5, ParentId = 3, Name = "Local beers" },
new ProductCategory { ProductCategoryId = 6, ParentId = 3, Name = "Foreign beers" },
new ProductCategory { ProductCategoryId = 7, ParentId = 4, Name = "Red wines" },
new ProductCategory { ProductCategoryId = 8, ParentId = 4, Name = "White wines" },
};
// todo to get below structure...
var flats = new List<ProductCategoryFlatItem>
{
new ProductCategoryFlatItem { NameWithAncestors = "Drinks", ProductCategoryId = 1 },
new ProductCategoryFlatItem { NameWithAncestors = "Drinks / Beers", ProductCategoryId = 3 },
new ProductCategoryFlatItem { NameWithAncestors = "Drinks / Beers / Local beers", ProductCategoryId = 5 },
new ProductCategoryFlatItem { NameWithAncestors = "Drinks / Beers / Foreingn beers", ProductCategoryId = 6 },
new ProductCategoryFlatItem { NameWithAncestors = "Drinks / Wines", ProductCategoryId = 4 },
new ProductCategoryFlatItem { NameWithAncestors = "Drinks / Wines / Red wines", ProductCategoryId = 7 },
new ProductCategoryFlatItem { NameWithAncestors = "Drinks / Wines / White wines", ProductCategoryId = 8 },
new ProductCategoryFlatItem { NameWithAncestors = "Food", ProductCategoryId = 2 },
};
}
}
public class ProductCategory
{
public int ProductCategoryId { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
}
public class ProductCategoryFlatItem
{
public int ProductCategoryId { get; set; }
public string NameWithAncestors { get; set; }
}
}
UPDATE:
I successfully build a tree, and then I am trying to use tree to build breadcrumbs by searching for ancestors, see my code bellow (this is work in progress...)
public interface IProductCategoryExtensions
{
List<ProductCategoryTreeItem> BuildTreeAndGetRoots(List<ProductCategory> allCategories);
List<ProductCategoryFlatItem> CreateComboboxItems(List<ProductCategory> categories);
}
public class ProductCategoryExtensions : IProductCategoryExtensions
{
public List<ProductCategoryTreeItem> BuildTreeAndGetRoots(List<ProductCategory> allCategories)
{
var treeItems = new List<ProductCategoryTreeItem>();
var rootItems = allCategories.Where(x => x.ParentId == null);
foreach (var rootItem in rootItems)
{
treeItems.Add(new ProductCategoryTreeItem
{
Item = rootItem,
Disabled = false,
Parent = null,
Children = GetChildren(allCategories, rootItem)
});
}
return treeItems;
}
private List<ProductCategoryTreeItem> GetChildren(List<ProductCategory> allCategories, ProductCategory productCategory)
{
var children = new List<ProductCategoryTreeItem>();
var childrenTemp = allCategories.Where(x => x.ParentId == productCategory.ProductCategoryId);
foreach (var childTemp in childrenTemp)
{
var child = new ProductCategoryTreeItem
{
Disabled = false,
Item = childTemp,
Children = GetChildren(allCategories, childTemp),
};
children.Add(child);
}
return children;
}
public List<ProductCategoryFlatItem> CreateComboboxItems(List<ProductCategory> categories)
{
var flats = new List<ProductCategoryFlatItem>();
var tree = BuildTreeAndGetRoots(categories);
foreach (var treeItem in tree)
{
flats.Add(CreateFlatItem(treeItem, categories));
if (treeItem.HasChildren)
{
flats.AddRange(GetChildrenFlats(treeItem.Children));
}
}
return flats;
}
private List<ProductCategoryFlatItem> GetChildrenFlats(List<ProductCategoryTreeItem> children)
{
var flatChildren = new List<ProductCategoryFlatItem>();
foreach (var child in children)
{
//if (child.Children != null && child.Children.Count > 0)
// Get
}
return flatChildren;
}
private ProductCategoryFlatItem CreateFlatItem(ProductCategoryTreeItem treeItem, List<ProductCategory> allCategories)
{
var flat = new ProductCategoryFlatItem();
if (treeItem.Parent == null)
{
flat.Description = treeItem.Item.Description;
flat.ProductCategoryId = treeItem.Item.ProductCategoryId;
}
else
{
}
return flat;
}
public List<ProductCategoryTreeItem> BuildTreeAndGetRoots(List<ProductCategory> allCategories)
{
var treeItems = new List<ProductCategoryTreeItem>();
var rootItems = allCategories.Where(x => x.ParentId == null);
foreach (var rootItem in rootItems)
{
treeItems.Add(new ProductCategoryTreeItem
{
Item = rootItem,
Disabled = false,
Parent = null,
Children = GetChildren(allCategories, rootItem)
});
}
return treeItems;
}
}
public class ProductCategoryTreeItem
{
public ProductCategory Item { get; set; }
public bool Disabled { get; set; }
public ProductCategoryTreeItem Parent { get; set; }
public List<ProductCategoryTreeItem> Children { get; set; } = new List<ProductCategoryTreeItem>();
public bool HasChildren
{
get
{
return Children != null && Children.Count > 0;
}
}
}

Sorry this is kinda messy code, but I'll leave the refactoring to you.
Assuming we have two classes:
public class ProductCategory
{
public int ProductCategoryId { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public Dictionary<ProductCategory, int> AncestorsWithHierarchy { get; set; } = new Dictionary<ProductCategory, int>();
}
public class ProductCategoryFlatItem
{
public string NameWithAncestors { get; set; }
public int ProductCategoryId { get; set; }
}
I added AncestorsWithHierarchy to ProductCategory to be able to set up breadcrumb order right.
Then you can do setup a continuous search backwards among ancestors in a recursive way, while adding the hierarchy level to use it for .OrderBy()
var result = new List<ProductCategoryFlatItem>();
Func<ProductCategory, ProductCategory> FindParent = null;
FindParent = thisItem =>
{
var parent = categories.Find(c => c.ProductCategoryId == thisItem.ParentId);
return parent;
};
foreach (var category in categories)
{
int hierarchyLevel = 0;
var parent = FindParent(category);
while (parent != null)
{
category.AncestorsWithHierarchy.Add(parent, hierarchyLevel);
hierarchyLevel++;
parent = FindParent(parent);
}
// Add self since we want it in the breadcrumb
category.AncestorsWithHierarchy.Add(category, -1);
result.Add(new ProductCategoryFlatItem()
{
NameWithAncestors = string.Join(" / ", category.AncestorsWithHierarchy.OrderByDescending(x => x.Value).Select(anc => anc.Key.Name)),
ProductCategoryId = category.ProductCategoryId
});
}
Which gives you your desired result:
However, I would never do this kind of operation during every read. I'm assuming this data will be read much more than it'll be written. So, what I would really do is, to move this logic where you are CRUD'ing to the database and build your breadcrumb there as a new field, and only recalculate if a category changes. This is much better than calculating on every single read request for every single user.

Related

LINQ query to group parent and child elements

I'm currently working on a .NET 4.7 application. I need to create a tree structure out of unsorted data.
The final tree structure looks like this:
public class LocationViewModel
{
public int Id { get; set; }
public string Code { get; set; }
public List<LocationViewModel> ChildLocations { get; set; }
}
One Parent LocationViewModel can have several ChildLocations. Each ChildLocation can have several ChildLocations itself again.
I need to sort the data from the following structure. My unsorted data is a List<LinkParentChildViewModel> LinksParentChild, and looks like this:
public class LinkParentChildViewModel
{
public Location Parent { get; set; }
public LocationLink Child { get; set; }
}
public class Location
{
public int Id { get; set; }
public string Code { get; set; }
}
public class LocationLink
{
public int ParentLocationId { get; set; }
public int ChildLocationId { get; set; }
}
First I have a List<Location> Locations, which contains all the locations.
Then I'm getting a List<LinkParentChildViewModel> LinksParentChild, the entries are all mixed up - thus a Parent can be a child and a child can be a parent.
var LinksParentChild = new List<LinkParentChildViewModel>
{
new LinkParentChildViewModel
{
Parent = new Location
{
Id = 8,
Code = "ParLoc1",
},
Child = new LocationLink
{
ChildLocationId = 4,
ParentLocationId = null
}
},
new LinkParentChildViewModel
{
Parent = new Location
{
Id = 4,
Code = "Loc1",
},
Child = new LocationLink
{
ChildLocationId = 6,
ParentLocationId = 8
}
},
new LinkParentChildViewModel
{
Parent = new Location
{
Id = 6,
Code = "ChildLoc1",
},
Child = new LocationLink
{
ChildLocationId = null,
ParentLocationId = 4
}
},
new LinkParentChildViewModel
{
Parent = new Location
{
Id = 10,
Code = "LeftLoc1",
},
Child = new LocationLink
{
ChildLocationId = 11,
ParentLocationId = 4
}
},
new LinkParentChildViewModel
{
Parent = new Location
{
Id = 11,
Code = "LeftChildLoc1",
},
Child = new LocationLink
{
ChildLocationId = null,
ParentLocationId = 10
}
}
};
I need to write a LINQ query to group all nodes from my data into a List<LocationViewModel> result.
var result = LinksParentChild.GroupBy(x => x.Parent.Id).Select(x => new LocationViewModel
{
Id = x.First().Parent.Id,
Code = x.First().Parent.Code,
ChildLocations = new List<LocationViewModel>
{
// ... I'm stuck unfortunately, somehow i need to query and group all locations
}
}).ToList();
I tried, but unfortunately I'm stuck:
I need to select all Locations like a tree structure
Do you know how to solve this issue?
Thanks a lot!
The result looks like this:
var result = new List<LocationViewModel>
{
new LocationViewModel
{
Id = 8,
Code = "ParLoc1",
ChildLocations = new List<LocationViewModel>
{
new LocationViewModel
{
Id = 4,
Code = "Loc1",
ChildLocations = new List<LocationViewModel>
{
new LocationViewModel
{
Id = 6,
Code = "ChildLoc1"
},
new LocationViewModel
{
Id = 10,
Code = "LeftLoc1",
ChildLocations = new List<LocationViewModel>
{
new LocationViewModel
{
Id = 11,
Code = "LeftChildLoc1"
}
}
}
}
}
}
}
};
you can try with Recursion
public static List<LocationViewModel> GetHierarchy(List<LinkParentChildViewModel> linkParentChildViewModels, int parentId)
{
return linkParentChildViewModels.Where(x => x.Parent.Id == parentId).Select(x => new LocationViewModel
{
Id = x.Parent.Id,
Code = x.Parent.Code,
ChildLocations = GetHierarchy(linkParentChildViewModels, x.Child.ChildLocationId)
}).ToList();
}
Call this from Main method
var result = GetHierarchy(LinksParentChild, 8);

LINQ distinct on groups of information

I have multiple items (List)
I need to get distinct features for the items but the issue is that each item has two features. So both features need to match on GroupTypeId and GroupId to become a distinct group.
I need to group items that have the same features (per above distinct groups found). I don't need features here again at the item level since I will have these per above in a separate object.
I need to keep items order intact, the first item will go in group 1, then second might go in group 1 or group 2 and so on.
Also, each item in group, item number needs to be overwritten per the new sequence in that group.
Can i do above tasks purely with LINQ rather than using nested loops?
In the below sample for items
i have 3 distinct feature groups
and 3 item groups. Item 2 and 4 needs to be grouped together and the line no needs to change to 1 and 2. For item 1 and 3, line number should become as 1.
Need to add to List whose count will be 3
Index[0] will have 2 features and 1 item
Index[1] will have 2 features and 2 items
Index[2] will have 2 features and 1 item
public class ItemPicked
{
public int Id { get; set; }
public string Description { get; set; }
public int LineNumber { get; set; }
public int PartId { get; set; }
public List<ItemFeature> Features { get; set; }
}
public class ItemFeature
{
public string OriginalReceived { get; set; }
public Group Feature { get; set; }
}
public class Group
{
public int Id { get; set; }
public string GroupTypeId { get; set; }
public string GroupId { get; set; }
public int SequenceNo { get; set; }
}
public class PickedGrouping
{
public List<ItemFeature> Features { get; set; }
public List<ItemPicked> Items { get; set; }
}
var SampleFeatures1 = new List<ItemFeature>() {
new ItemFeature {
OriginalReceived = "SomeThing",
Feature = new Group() {
Id = 1, SequenceNo = 1, GroupTypeId = "A", GroupId = "B"
}
},
new ItemFeature {
OriginalReceived = "SomeThing2",
Feature = new Group() {
Id = 2, SequenceNo = 2, GroupTypeId = "Y", GroupId = "Z"
}
}
};
var SampleFeatures2 = new List<ItemFeature>() {
new ItemFeature {
OriginalReceived = "SomeThing3",
Feature = new Group() {
Id = 3, SequenceNo = 3, GroupTypeId = "C", GroupId = "D"
}
},
new ItemFeature {
OriginalReceived = "SomeThing4",
Feature = new Group() {
Id = 4, SequenceNo = 4, GroupTypeId = "X", GroupId = "Y"
}
}
};
var SampleFeatures3 = new List<ItemFeature>() {
new ItemFeature {
OriginalReceived = "SomeThing5",
Feature = new Group() {
Id = 3, SequenceNo = 3, GroupTypeId = "C", GroupId = "D"
}
},
new ItemFeature {
OriginalReceived = "SomeThing5",
Feature = new Group() {
Id = 2, SequenceNo = 2, GroupTypeId = "M", GroupId = "K"
}
}
};
var items = new List<ItemPicked>(){
new ItemPicked{
Id = 1, Description = "Item 1", LineNumber = 1, Features = SampleFeatures1
},
new ItemPicked{
Id = 2, Description = "Item 2", LineNumber = 2, Features = SampleFeatures2
},
new ItemPicked{
Id = 3, Description = "Item 3", LineNumber = 3, Features = SampleFeatures3
},
new ItemPicked{
Id = 4, Description = "Item 4", LineNumber = 4, Features = SampleFeatures2
}
};
var pickedGroupings = new List<PickedGrouping>();
PickedGrouping selectedGroup = null;
foreach (var item in items)
{
var found = 0;
if(item.Features == null || !item.Features.Any())
{
selectedGroup = pickedGroupings.FirstOrDefault(x => x.Features == null || !x.Features.Any());
if (selectedGroup == null) selectedGroup = new PickedGrouping();
selectedGroup.Features.AddRange(item.Features);
}
else
{
foreach (var feature in item.Features)
{
foreach (var pg in pickedGroupings)
{
if ((item.Features == null || !item.Features.Any()) && (pg.Features == null || !pg.Features.Any())){
selectedGroup = pg;
found += 1;
}
else
{
foreach (var pgf in pg.Features)
{
if (pgf.Feature == null) continue;
if (pgf.Feature.GroupId == feature.Feature.GroupId && pgf.Feature.GroupTypeId == feature.Feature.GroupTypeId)
{
selectedGroup = pg;
found += 1;
}
}
}
}
}
if (found < 2)
{
pickedGroupings.Add(new PickedGrouping() { Features = item.Features });
selectedGroup = pickedGroupings[pickedGroupings.Count - 1];
}
}
//add item
if (selectedGroup.Items == null) selectedGroup.Items = new List<ItemPicked>();
selectedGroup.Items.Add(item);
}
//update line number
foreach(var pg in pickedGroupings)
{
var lineNum = 1;
foreach(var item in pg.Items)
{
item.LineNumber = lineNum;
lineNum += 1;
}
}

LINQ. Loading related elements

I have an entity Contracts, ListKindWorks and KindWorks.
public partial class Contracts
{
public Contracts()
{
ListKindWorks = new HashSet<ListKindWorks>();
}
public int Id { get; set; }
...
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
}
public partial class ListKindWorks
{
public int IdContract { get; set; }
public int IdKindWork { get; set; }
public virtual Contracts IdContractNavigation { get; set; }
public virtual KindWorks IdKindWorkNavigation { get; set; }
}
public partial class KindWorks
{
public KindWorks()
{
ListKindWorks = new HashSet<ListKindWorks>();
}
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
}
I want to load related elements. Something like this pseudocode:
source = model.Contracts
.Select(c => new MyType
{
IdContract = c.Id,
KindWork = new List<Item>
{ Id = KindWorks.Id, Value = KindWorks.Title }
// or
// KindWork = c.ListKindWorks
// .Where(x => x.IdContract == c.Id)
// .Select(y => new Item
// { Id = y.IdKindWork, Value = y.IdKindWorkNavigation.Title })
...
})
.ToList();
public class Item
{
public int Id { get; set; }
public string Value { get; set; }
}
Can I load List<Item> for each Contracts?
If I understand what you are looking for, I create a List for each contract in a dictionary. And here is my result:
var contracts = new List<Contracts>
{
new Contracts { Id = 1 },
new Contracts { Id = 2 },
new Contracts { Id = 3 },
};
var listKindWorks = new List<ListKindWorks>
{
new ListKindWorks { IdContract = 1, IdKindWork = 1 },
new ListKindWorks { IdContract = 1, IdKindWork = 2 },
new ListKindWorks { IdContract = 2, IdKindWork = 2 },
new ListKindWorks { IdContract = 2, IdKindWork = 3 }
};
var kindWorks = new List<KindWorks>
{
new KindWorks { Id = 1, Title = "Title 1" },
new KindWorks { Id = 2, Title = "Title 2" },
new KindWorks { Id = 3, Title = "Title 3" },
};
Dictionary<Contracts, List<Item>> myDic = contracts.Select(
contract => contract).ToDictionary(
contract => contract,
contract => listKindWorks.Where(
listKindWork => listKindWork.IdContract.Equals(contract.Id))
.Select(listKindWork => new Item
{
Id = kindWorks.FirstOrDefault(kindWork => kindWork.Id.Equals(listKindWork.IdKindWork))?.Id?? listKindWork.IdKindWork,
Value = kindWorks.FirstOrDefault(kindWork => kindWork.Id.Equals(listKindWork.IdKindWork))?.Title?? "KindWork not found"
}).ToList());
I obtain this for my test :
Contract1 : Title1, Title2
Contract2 : Title2, Title3
Contract3 : Nothing
IEnumerable<Item> KindWork = c.ListKindWorks
.Select(y => new Item
{
Id = y.IdKindWork,
Value = y.IdKindWorkNavigation.Title
})
IEnumerable<Item> Subject = c.ListSubjects
.Select(y => new Item
{
Id = y.IdSubject,
Value = y.IdSubjectNavigation.Title
})

Select n Items from the Child List?

how can I get N elements of the child list? Let's say I'd like to get 2 children for each parent.
public class Program
{
static void Main(string[] args)
{
var data = new List<Parent>()
{
new Parent()
{
Id = 1,
Name = "ParentName1",
Children = new List<Child>()
{
new Child() { Id = 1, Name = "ChildName1"},
new Child() { Id = 2, Name = "ChildName2"},
new Child() { Id = 3, Name = "ChildName3"},
new Child() { Id = 4, Name = "ChildName4"},
new Child() { Id = 5, Name = "ChildName5"},
}
},
new Parent()
{
Id = 2,
Name = "ParentName2",
Children = new List<Child>()
{
new Child() { Id = 6, Name = "ChildName6"},
new Child() { Id = 7, Name = "ChildName7"},
new Child() { Id = 8, Name = "ChildName8"},
new Child() { Id = 9, Name = "ChildName9"},
new Child() { Id = 10, Name = "ChildName10"},
}
}
};
// Get only 2 child elements for parent
var filteredData = data.Where(x => x.Children.Count >= 2)
.ToList();
foreach (var filteredParent in filteredData)
{
Console.WriteLine($"Parent {filteredParent.Id} with {filteredParent.Children.Count} children.");
}
Console.ReadKey();
}
}
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public Parent Parent { get; set; }
}
One way to do it is to use a multi statement lambda expression for the result selector and List<T>'s RemoveRange method:
var query = data.Select
(
p =>
{
p.Children.RemoveRange(2, p.Children.Count - 2);
return p;
}
);
As Flater commented, It might be better to return a shallow copy of the parent, with only the first two children. That way your query does not change the source data:
var query = data.Select
(
p => new Parent()
{
Id = p.Id,
Name = p.Name,
Children = p.Children.Take(2).ToList()
}
);

IGrouping trouble

I have the following list
class Programm
{
public static void Main(string[] args)
{
List<Service> Services =new List<Service>
{
new Service
{
Name = "name1",
Prices = new List<BEPrice>
{
new BEPrice
{
Price = 120,
Quantity = 3
}
}
},
new Service
{
Name = "name2",
Prices = new List<BEPrice>
{
new BEPrice
{
Price = 123,
Quantity = 3
}
}
},
new Service
{
Name = "name3",
Prices = new List<BEPrice>
{
new BEPrice
{
Price = 100,
Quantity = 3
}
}
},
new Service
{
Name = "name4",
Prices = new List<BEPrice>
{
new BEPrice
{
Price = 900,
Quantity = 8
}
}
}
};
}
public class Tariff
{
public string Name { get; set; }
public List<BEPrice> Prices { get; set; }
}
public class Service
{
public string Name { get; set; }
public List<BEPrice> Prices { get; set; }
public Tariff Tariff;
}
public class BEPrice
{
public decimal Price { get; set; }
public int Quantity { get; set; }
}
I want a result as
Tariff-1 -> Name - "blabla", Prices = {
Price1 = {Price = 343, Quantity = 3},
Price2 = {Price = 900, Quantity = 8} }
The tariff first price Price1 343 is a sum of 100, 120, 123 for 3 (Quantity) month.
Here is my unsuccessful attemp
foreach (var groupedPrices in Services.Select(s => s.Prices.GroupBy(p => p.Quantity)))
{
foreach (var p in groupedPrices.Select(x => x.Key))
Console.WriteLine(p);
foreach (var price in groupedPrices)
{
_prices.AddRange(price.Select(p => p));
}
}
Not sure what is name blabla, but this is how you can get prices part
var prices = Services
.SelectMany(arg => arg.Prices)
.GroupBy(arg => arg.Quantity)
.Select(arg => new { Price = arg.Sum(x => x.Price), Quantity = arg.Key })
.ToList();

Categories