How can I find class from nested list ?
I am working on trees and just want to retreive and add child based on id.
Class
public class d3_mitch
{
public int id { get; set; }
public string name { get; set; }
public string type { get; set; }
public string description { get; set; }
public List<d3_mitch> children { get; set; }
}
Object Creation and Query
d3_mitch t = new d3_mitch();
t.id = 1;
t.type = "Root";
t.name = "Animal";
t.description = "A living organism that feeds on organic matter";
t.children = new List<d3_mitch>() {
new d3_mitch() { name = "Carnivores", type = "Type", id = 2, description = "Diet consists solely of animal materials",
children=new List<d3_mitch>(){ new d3_mitch() { id= 3 ,name="Felidae",type="Family",description="Also known as cats"} }
}
};
d3_mitch child = t.children.Where(x => x.id == 3).FirstOrDefault();
//This return null because no direct child has has id = 3 but nested
You need to use recursion. Try next code
d3_mitch FindById(d3_mitch root, int id)
{
if (root.id == id)
return root;
foreach (var child in root.children)
{
if (child.id == id)
return child;
var subTreeResult = FindById(child, id);
if (subTreeResult != null)
return subTreeResult;
}
// no such item
return null;
}
Use SelectMany
t.children.SelectMany(s => s.children)
.FirstOrDefault(s => s.children.Any(d => d.id == 3));
Using a recursive method will resolve your problem.
public static d3_mitch Find(d3_mitch main, int id)
{
if (main.id == id)
return main;
if (main.id != id && main.children != null)
{
foreach (var child in main.children)
{
return child.children.Any(x=>x.id==id)? child.children.First(x=>x.id==id) : Find(child, id);
}
}
return null;
}
Related
i have table looks like below
ID | Reason | PrID
-----------------
1 abc null
2 dhe null
3 aerc 1
4 dwes 2
5 adfje 1
i have class
public class Reason
{
public int Id { get; set; }
public string Reson{ get; set; }
public List<SecondryReason> SecReason{ get; set; }
public int? PrimaryId { get; set; }
}
public class SecondryReason
{
public int Id { get; set; }
public string Reason { get; set; }
public int PrimaryReasonId { get; set; }
}
I want this to be displayed in hierarchy level
if the prid is Null need to treat this as the parent remaining all child
i am trying Linq and unable to achieve this
Suggest me how to do this in an easy way in linq
So: You have a list/enumerable of type , whereof the SecReason List property is null. Then, using linq you want a list, were the only the "root" reasons remain, but the Sub-reasons got put in the lists, but as type SecondaryReason?
If so, I found this way to do it (linq and foreach):
static IEnumerable<Reason> GetReasonsGrouped(List<Reason> reasons)
{
var result = reasons.Where(x => x.PrimaryId == null);
foreach (var item in result)
{
item.SecReason = reasons.Where(x => x.PrimaryId == item.Id)
.Select(x => new SecondryReason()
{ Id = x.Id,
ReasonName = x.ReasonName,
PrimaryReasonId = item.Id
})
.ToList();
}
return result;
}
Or just linq, but harder to read:
var result = reasons.Where(x => x.PrimaryId == null)
.Select(x =>
{
x.SecReason = reasons.Where(r => x.PrimaryId == x.Id)
.Select(r => new SecondryReason()
{
Id = r.Id,
ReasonName = x.ReasonName,
PrimaryReasonId = x.Id
})
.ToList();
return x;
});
Not sure if linq will be the best solution, here is my proposed changes and method to get an Hierarchy type:
public class Reason
{
public int Id { get; set; }
public string Reson { get; set; }
public List<Reason> SecReason { get; set; }
public int? PrimaryId { get; set; }
//Adds child to this reason object or any of its children/grandchildren/... identified by primaryId
public bool addChild(int primaryId, Reason newChildNode)
{
if (Id.Equals(primaryId))
{
addChild(newChildNode);
return true;
}
else
{
if (SecReason != null)
{
foreach (Reason child in SecReason)
{
if (child.addChild(primaryId, newChildNode))
return true;
}
}
}
return false;
}
public void addChild(Reason child)
{
if (SecReason == null) SecReason = new List<Reason>();
SecReason.Add(child);
}
}
private List<Reason> GetReasonsHierarchy(List<Reason> reasons)
{
List<Reason> reasonsHierarchy = new List<Reason>();
foreach (Reason r in reasons)
{
bool parentFound = false;
if (r.PrimaryId != null)
{
foreach (Reason parent in reasonsHierarchy)
{
parentFound = parent.addChild(r.PrimaryId.Value, r);
if (parentFound) break;
}
}
if (!parentFound) reasonsHierarchy.Add(r);
}
return reasonsHierarchy;
}
I'm working on building an object with nested hierarchy from a flattened list of BOM items (bill of materials) from SQL that contain the BOM level and the parent they belong to. Basically each parent that is an assembly of smaller sub parts needs to have its sub parts added to a collection within that object. I believe we have some parts that may iterate up to 10 levels deep, and I don't like the way this pattern is going.
This data set is from a stored procedure that runs a complicated CTE query, and I'm not interested in trying to make this work with Entity Framework, as we have a lot of databases in the mix, and we need better control over the SQL that we're writing (hence the stored procedure). Ultimately this will end up as JSON that I'll render as a collapsible tree in a browser.
Here's the ugly part that I'm hoping to clean up a bit. The gist of what's going on here before I begin iterating over the various levels is:
Establish the first item (incomplete, but this is the main item).
Execute a stored procedure (with parameter) to return a datatable.
Create a flattened list of the objects I want to nest.
Get the total number of nested levels that exist on the BOM.
Iterate over the various levels in a very ugly way.
public BOMItemModel GetItemBOM(string item)
{
try
{
var bom = new BOMItemModel { PLPartNumber = item, BOMLevel = 0 };
var par = new Hashtable();
par.Add("#Item", item);
var dt = db.GetDataTable("[part].[getItemBOM]", par);
var bomList = new List<BOMItemModel>();
foreach(DataRow r in dt.Rows)
{
bomList.Add(MapBomItem(r));
}
var bomLevels = bomList.Max(x => x.BOMLevel);
for(var i = 1; i < bomLevels; i++)
{
foreach (var b in bomList.Where(x => x.BOMLevel == i).ToList())
{
if (i == 1)
{
bom.SubItems.Add(b);
}
else
{
if (i == 2)
{
var lvl2Items = bom.SubItems;
var parent1 = lvl2Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent1 != null) parent1.SubItems.Add(b);
}
if (i == 3)
{
var lvl2Items = bom.SubItems;
foreach(var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
var parent2 = lvl3Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent2 != null) parent2.SubItems.Add(b);
}
}
if (i == 4)
{
var lvl2Items = bom.SubItems;
foreach (var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
foreach (var lvl3 in lvl3Items)
{
var lvl4Items = lvl3.SubItems;
var parent3 = lvl4Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent3 != null) parent3.SubItems.Add(b);
}
}
}
if (i == 5)
{
var lvl2Items = bom.SubItems;
foreach (var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
foreach (var lvl3 in lvl3Items)
{
var lvl4Items = lvl3.SubItems;
foreach (var lvl4 in lvl4Items)
{
var lvl5Items = lvl4.SubItems;
var parent4 = lvl5Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent4 != null) parent4.SubItems.Add(b);
}
}
}
}
if (i == 6)
{
var lvl2Items = bom.SubItems;
foreach (var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
foreach (var lvl3 in lvl3Items)
{
var lvl4Items = lvl3.SubItems;
foreach (var lvl4 in lvl4Items)
{
var lvl5Items = lvl4.SubItems;
foreach (var lvl5 in lvl5Items)
{
var lvl6Items = lvl5.SubItems;
var parent5 = lvl6Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent5 != null) parent5.SubItems.Add(b);
}
}
}
}
}
}
}
}
return bom;
}
catch (Exception ex)
{
Log.Error(ex.Message, ex);
}
return null;
}
This is what my model or class looks like:
public class BOMItemModel
{
public BOMItemModel()
{
SubItems = new List<BOMItemModel>();
}
public string ParentPLNumber { get; set; }
public string PartNumber { get; set; }
public string PartListName { get; set; }
public string ItemNumber { get; set; }
public string PLPartNumber { get; set; }
public string PartType { get; set; }
public string PLManufacturer { get; set; }
public string PLDescription { get; set; }
public decimal Qty { get; set; }
public int BOMLevel { get; set; }
public List<BOMItemModel> SubItems { get; set; }
}
The last property on this class is where I'm stuffing sub items of the parent item, and this can nest several levels deep.
Yes, the following would be much better:
for (var i = 1; i <= bomLevels; i++)
{
foreach (var b in bomList.Where(x => x.BOMLevel == i).ToList())
{
if (i == 1)
{
bom.SubItems.Add(b);
}
else
{
var parent = bomList.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber && b.BOMLevel - 1 == x.BOMLevel);
if (parent != null) parent.SubItems.Add(b);
}
}
}
Also, I would throw an error when parent is not found. For a more generic approach have a look at my other example: Converting table in tree
foreach (var lg in basket)
{
foreach (var acc in lg.Accomodations)
{
if (acc.HotelID == h.ID)
{
hotel.SelectedInPreviousLeg = true;
}
}
}
I try to convert this double foreach to linq. Any suggestions?
So far i tried this, but there is a compile error.
var test = basket.FirstOrDefault(x => x.Accommodations, Any(y => y.HotelID == h.ID));
hotel.SelectedInPreviousLeg = (test != null) ? true : false;
or, assuming classes are defined equivalently to
public class Hotel
{
public int ID { get; set; }
public List<Accomodation> Accomodations { get; set; }
public bool SelectedInPreviousLeg { get set; }
}
public class Accomodation
{
public int HotelID { get; set; }
}
then you could do this:
foreach (var acc in
from lg in basket
from acc in lg.Accomodations
where acc.HotelID == h.ID
select acc)
hotel.SelectedInPreviousLeg = true;
... but that will iterate through all combinations, when you only need to find the first one that satisfies the condiditon, so you could do this, which will stop after finding the first one:
hotel.SelectedInPreviousLeg =
basket
.Any(b => b.Accomodations
.Any(a => a.HotelID == h.ID));
I'm having a problem with my recursive method. This method is not behaving as i wish it to behave.
What i am trying to do is to is to get a sorted list of all the categories i have in my database, those lists contains a list of childs named ChildList, and i am trying to make a method that would recursively add right child to the rightful parent.
On 3 levels down it behaves exactly as i want, it adds a Child to the childlist of CategoryViewModel. after that, it tends to make duplicate childs which i dont want it to.
For example, you have
Root - Root has Electronics as child,
Electronics has Telephones as child,
Telephone has Mobilephones, here is where my recursive method duplicate the child, making 2 Mobilephones, and if i have a category Iphone under Mobilephones it would make 3 Iphone-categorychildrens to each Mobilephone. The Iphone categories doesent have any children and the list is 0. but i bet if it would have, each iphone would have 4 childs of that category.
here is my code
namespace GUI.Controllers
{
public class HomeController : Controller
{
private readonly IRepository<Category> _repo;
private static List<CategoryViewModel> _sources;
public HomeController(IRepository<Category> repo)
{
_repo = repo;
}
public HomeController()
: this(new Repository<Category>())
{
}
public ViewResult Index()
{
var items = _repo.GetAll().ToList();
var sortedList = new CategoryViewModel();
_sources = items.Select(c => new CategoryViewModel
{
Id = c.Id,
Name = c.Name,
Parent = c.Parent.HasValue ? c.Parent.Value : (Guid?) null,
Products = c.Product.ToList(),
ChildList = new List<CategoryViewModel>()
}).ToList();
_sources = _sources.OrderBy(o => o.Parent).ToList();
var root = _sources.First();
sortedList.Id = root.Id;
sortedList.Name = root.Name;
sortedList.Parent = null;
sortedList.ChildList = _sources.Where(o => o.Parent != null && o.Parent == root.Id).ToList();
foreach (var l in _sources)
{
if(l.Parent == null)
continue;
l.ChildList = _sources.Where(o => o.Parent != null && o.Parent == l.Id).ToList();
if (l.ChildList.Any())
AddItem(l.ChildList, ref sortedList);
}
return View(sortedList);
}
private static void AddItem(List<CategoryViewModel> children, ref CategoryViewModel sortedList)
{
foreach (var child in children)
{
var childs = _sources.Where(o => o.Parent != null && o.Parent == child.Id).ToList();
foreach (var c in childs)
{
child.ChildList.Add(c);
}
if (child.ChildList.Any())
AddItem(child.ChildList, ref sortedList);
}
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using ClassLibrary.Entities;
namespace GUI.Models
{
public class CategoryViewModel
{
[Required]
public string Name { get; set; }
[Required]
public string SearchString { get; set; }
public Guid Id { get; set; }
public Guid? Parent { get; set; }
public string ParentName { get; set; }
public List<Product> Products { get; set; }
public List<CategoryViewModel> ChildList { get; set; }
}
}
Ok i found out what the solution to my problem is;
My recursive method was overflow of code, i went too deep without understanding how to practicly use the recursive method. as they say; one thing led to another and i eventually deleted my AddItem Method, it turns out that my lambda expression sorted out the lists for me in the right collection of lists; the final code looks alot more clean and is alot smaller now;
public ViewResult Index()
{
var items = _repo.GetAll().ToList();
var categories = items.Select(c => new CategoryViewModel
{
Id = c.Id,
Name = c.Name,
Parent = c.Parent.HasValue ? c.Parent.Value : (Guid?) null,
Products = c.Product.ToList(),
ChildList = new List<CategoryViewModel>()
}).OrderBy(o => o.Parent).ToList();
var sortedCategories = categories.First();
sortedCategories.ChildList = categories.Where(o => o.Parent != null && o.Parent == sortedCategories.Id).ToList();
foreach (var l in categories.Where(l => l.Parent != null))
{
l.ChildList = categories.Where(o => o.Parent != null && o.Parent == l.Id).ToList();
}
return View(sortedCategories);
}
I have a class called Section
public class Section
{
public Section() { construct(0); }
public Section(int order) { construct(order); }
private void construct(int order)
{
Children = new List<Section>();
Fields = new List<XfaField>();
Hint = new Hint();
Order = order;
}
[Key]
public int Id { get; set; }
public int FormId { get; set; }
public string Name { get; set; }
[InverseProperty("Parent")]
public List<Section> Children { get; set; }
public List<XfaField> Fields { get; set; }
public Section Parent { get; set; }
public Hint Hint { get; set; }
public int Order { get; private set; }
#region Methods
public void AddNewChild()
{
AddChild(new Section
{
Name = "New Child Section",
FormId = FormId,
});
}
private void AddChild(Section child)
{
child.Parent = this;
if (Children == null) Children = new List<Section>();
int maxOrder = -1;
if(Children.Count() > 0) maxOrder = Children.Max(x => x.Order);
child.Order = ++maxOrder;
Children.Add(child);
FactoryTools.Factory.PdfSections.Add(child);
}
// Other methods here
#endregion
}
I am trying to add a new child Section to an already existing parent like this:
private void AddChildSection()
{
var parent = FactoryTools.Factory.PdfSections.FirstOrDefault(x => x.Id == ParentId);
if (parent == null) throw new Exception("Unable to create child because parent with Id " + ParentId.ToString() + " doesn't exist.");
parent.AddNewChild();
FactoryTools.Factory.SaveChanges();
}
When I look at the database, I see that a new row has been added, so for example:
Id Name Parent_Id Hint_Id FormId Order
19 New Child Section 1 27 1 0
However, when I load the parent Section, the Children property is always of Count 0, like this:
public ActionResult EditSection(int formId, int sectionId)
{
var model = FactoryTools.Factory.PdfSections.FirstOrDefault(x => x.Id == sectionId);
if (model == null || model.FormId != formId) model = new Section();
//model.Children = FactoryTools.Factory.PdfSections.Where(x => x.Parent.Id == sectionId).ToList();
return PartialView(model);
}
Of course, when I manually add the children, then they are there (in the above code, by uncommenting the model.Children = ... line)
I am used to the NHibernate way of doing things and am therefore quite frustrated that the above, seemingly simple, task is not working in EntityFramework, what am I doing wrong?
Entity Framework won't eagerly load related entities. Try forcing it to include the children:
var model = FactoryTools.Factory.PdfSections.Include("Children").FirstOrDefault(x => x.Id == sectionId);
There's also a strongly-typed overload to which you can pass a lambda:
var model = FactoryTools.Factory.PdfSections.Include(s => s.Children).FirstOrDefault(x => x.Id == sectionId);