LINQ query to group parent and child elements - c#

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);

Related

Filter List inside List Linq

I have list say list of customers and inside each list there is another list of orders
Class Customer
{
int ID,
string Name
List<Order> orders
}
Class Order{
int ID,
string Name
}
Also have a integer list of filteredorderIds = {1,2,3,4}
I want to filter the list of customers who has got orderIds from filteredorderIds list.
So far I am stuck at query like
var filteredCustomers = Customers.Where(x => x.Orders.Any(filteredorderIds.contains(y => y.Id)));
please give credit to #Johnathan Barclay, since he posted faster than i typed example
void Main()
{
var customers = new List<Customer>(){
new Customer(){
ID =1,
Name = "Cust1",
orders = new List<Order>(){
new Order(){ID = 4, Name = "o11"},
new Order(){ID = 5, Name = "o12"},
new Order(){ID = 6, Name = "o13"}
}
},
new Customer(){
ID = 2,
Name = "Cust2",
orders = new List<Order>(){
new Order(){ID = 3, Name = "o21"},
new Order(){ID = 7, Name = "o22"},
new Order(){ID = 8, Name = "o23"}
}
}
};
customers.Where(w =>
w.orders.Any(w => filteredorderIds.Contains(w.ID))
).Dump();
}
List<int> filteredorderIds = new List<int>() { 1, 2, 3, 4 };
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public List<Order> orders { get; set; }
}
public class Order
{
public int ID { get; set; }
public string Name { get; set; }
}

Convert categories into human readable breadcrumbs 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.

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()
}
);

MongoDb c# driver consecutive SelectMany

If I have objects, lets call them Group that has list of some other objects I will call it Brand, and this object has a list of objects called Model.
Is there a way to get only list of Models using MongoDb c# driver.
I tried using SelectMany multiple times but with no success. If I put more than one SelectMany I always get an empty list.
Code should be self-explanatory.
At the end is comment that explains what confuses me.
class Group
{
[BsonId(IdGenerator = typeof(GuidGenerator))]
public Guid ID { get; set; }
public string Name { get; set; }
public List<Brand> Brands { get; set; }
}
class Brand
{
public string Name { get; set; }
public List<Model> Models { get; set; }
}
class Model
{
public string Name { get; set; }
public int Produced { get; set; }
}
class Program
{
static void Main(string[] args)
{
MongoClient client = new MongoClient("mongodb://127.0.0.1:32768");
var db = client.GetDatabase("test");
var collection = db.GetCollection<Group>("groups");
var fca = new Group { Name = "FCA" };
var alfaRomeo = new Brand { Name = "Alfra Romeo" };
var jeep = new Brand { Name = "Jeep" };
var ferrari = new Brand { Name = "Ferrari"};
var laFerrari = new Model { Name = "LaFerrari", Produced = 4 };
var wrangler = new Model { Name = "Wrangler", Produced = 3 };
var compass = new Model { Name = "Compass", Produced = 8 };
var giulietta = new Model { Name = "Giulietta", Produced = 7 };
var giulia = new Model { Name = "Giulia", Produced = 8 };
var _4c = new Model { Name = "4C", Produced = 6 };
fca.Brands = new List<Brand> { ferrari, alfaRomeo, jeep };
ferrari.Models = new List<Model> { laFerrari };
jeep.Models = new List<Model> { wrangler, compass };
alfaRomeo.Models = new List<Model> { giulietta, giulia, _4c };
collection.InsertOne(fca);
Console.WriteLine("press enter to continue");
Console.ReadLine();
var models = collection.AsQueryable().SelectMany(g => g.Brands).SelectMany(b => b.Models).ToList();
Console.WriteLine(models.Count); //returns 0 I expected 6
Console.ReadLine();
}
}
Try
var models = collection.AsQueryable()
.SelectMany(g => g.Brands)
.Select(y => y.Models)
.SelectMany(x=> x);
Console.WriteLine(models.Count());
Working output (with extra Select())
aggregate([{
"$unwind": "$Brands"
}, {
"$project": {
"Brands": "$Brands",
"_id": 0
}
}, {
"$project": {
"Models": "$Brands.Models",
"_id": 0
}
}, {
"$unwind": "$Models"
}, {
"$project": {
"Models": "$Models",
"_id": 0
}
}])
OP Output without extra Select()
aggregate([{
"$unwind": "$Brands"
}, {
"$project": {
"Brands": "$Brands",
"_id": 0
}
}, {
"$unwind": "$Models"
}, {
"$project": {
"Models": "$Models",
"_id": 0
}
}])

Categories