Recursively build a list based on condition - c#

I have a list of Orders that contains subOrders and so on. These are linked via the ChildOrderID and OrderID. However, within each order there are Products and child products that are linked via the PID and ParentPID. Not every product however has a child product.
I have attached an example screenshot of the data in the db to illustrate what I mean and what I hope to achieve. The issue I'm having is that I need to take this flat file structure and put it into a nested c# list. Each BOMLineClass has a list of BOMLinesClass
var navigationItems = bomline.Select(
i => new BOMLineClass
{
ParentOrderID = i.ParentOrderID,
BSOOrderNo = i.BSOOrderNo,
BSODemandDate = i.BSODemandDate,
OrderTypeDesc = i.OrderTypeDesc,
OrderType = i.OrderType,
BuildRef = i.BuildRef,
HasSubAssembly = i.HasSubAssembly,
ChildOrderID= i.ChildOrderID,
ChildOrderNo= i.ChildOrderNo,
OrderID = i.OrderID,
OrderLineID = i.OrderLineID,
ProductCode = i.ProductCode,
ParentPID = i.ParentPID,
PID = i.PID,
}
).ToList();
foreach (var i in navigationItems)
{
i.BOMLines = navigationItems.Where(n => n.ChildOrderID== i.OrderID).ToList();
foreach (var x in i.BOMLines)
{
//Thought I could link the children via the product ID and parent product Id here
}
}
List<BOMLineClass> rootNavigationItems2 = navigationItems.Where(n => n.ChildOrderID == false).ToList();
bh.BOMLines = rootNavigationItems2;
I've been struggling with this for at least 5 days now.

See if following works. You have to start at leafs where ChildOrderID is null and work your way up to root. You also need to have siblings and children nodes. See code below
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static DataTable dt = new DataTable();
static void Main(string[] args)
{
dt.Columns.Add("ChildOrderID", typeof(int));
dt.Columns.Add("ChildOrderNo", typeof(string));
dt.Columns.Add("OrderID", typeof(int));
dt.Columns.Add("OrderLineID", typeof(int));
dt.Columns.Add("ProductCode", typeof(string));
dt.Columns.Add("PID", typeof(int));
dt.Columns.Add("ParentPID", typeof(int));
dt.Rows.Add(null, null, 40551, 193085, "BP6FS", 261649, 303032);
dt.Rows.Add(null, null, 40551, 193086, "HT1-BP", 299119, 303032);
dt.Rows.Add(40551, "BSOMSO20022DDT_1_2", 40550, 193083, "AG947", 253420, 290961);
dt.Rows.Add(40551, "BSOMSO20022DDT_1_2", 40550, 193084, "JS10", 303032, 290961);
dt.Rows.Add(40550, "BSOMSO20022DDT_1", 40549, 193081, "CA9680", 266226, 269143);
dt.Rows.Add(40550, "BSOMSO20022DDT_1", 40549, 193082, "FU552-BP", 290961, 3269143);
List<BOMLineClass> navigationItems = BOMLineClass.GroupBOM(dt);
foreach (BOMLineClass order in navigationItems)
{
BOMLineClass.Print(order, 0);
}
Console.ReadLine();
}
}
public class BOMLineClass
{
public int ParentOrderID { get;set;}
public string BSOOrderNo { get;set;}
public DateTime BSODemandDate { get;set;}
public string OrderTypeDesc { get;set;}
public string OrderType { get;set;}
public string BuildRef { get;set;}
public string HasSubAssembly { get;set;}
public int? ChildOrderID { get;set;}
public string ChildOrderNo { get;set;}
public int OrderID { get;set;}
public int OrderLineID { get;set;}
public string ProductCode { get;set;}
public int ParentPID { get;set;}
public int PID { get; set; }
public List<BOMLineClass> children { get; set; }
public List<BOMLineClass> siblings { get; set; }
static DataTable dt;
public static List<BOMLineClass> GroupBOM(DataTable dt)
{
BOMLineClass.dt = dt;
List<BOMLineClass> leafOrders = new List<BOMLineClass>();
foreach(DataRow row in dt.AsEnumerable().Where(x => x.Field<int?>("ChildOrderID") == null))
{
leafOrders.Add(AddFields(row));
}
return GroupBOMRecursive(leafOrders);
}
static List<BOMLineClass> GroupBOMRecursive(List<BOMLineClass> childOrders)
{
List<BOMLineClass> orders = new List<BOMLineClass>();
var groups = childOrders.GroupBy(x => x.OrderID).ToList();
Boolean hasParents = false;
foreach (var group in groups)
{
//find parent datarow
List<DataRow> parentRow = dt.AsEnumerable().Where(x => x.Field<int?>("ChildOrderID") == group.Key).ToList();
if (parentRow.Count == 0)
{
//no parent
orders.Add(group.First());
}
else
{
hasParents = true;
BOMLineClass order = null;
for (int i = 0; i < parentRow.Count; i++)
{
if (i == 0)
{
order = AddFields(parentRow[i]);
order.children = new List<BOMLineClass>();
order.children.AddRange(group);
orders.Add(order);
}
else
{
if (order.siblings == null) order.siblings = new List<BOMLineClass>();
order.siblings.Add(AddFields(parentRow[i]));
}
}
}
}
if (hasParents)
{
return GroupBOMRecursive(orders);
}
else
{
return orders;
}
}
static public BOMLineClass AddFields(DataRow row)
{
BOMLineClass order = new BOMLineClass();
order.ChildOrderID = row.Field<int?>("ChildOrderID");
order.ChildOrderNo = row.Field<string>("ChildOrderNo");
order.OrderID = row.Field<int>("OrderID");
order.OrderLineID = row.Field<int>("OrderLineID");
order.ProductCode = row.Field<string>("ProductCode");
order.PID = row.Field<int>("PID");
order.ParentPID = row.Field<int>("ParentPID");
return order;
}
public static void Print(BOMLineClass order, int level)
{
const int IDENT = 10;
Console.WriteLine("{0}{1}({2})", new string('-', IDENT * level), order.OrderID, order.ParentPID);
if (order.siblings != null)
{
foreach (BOMLineClass sibling in order.siblings)
{
Console.WriteLine("{0}{1}({2})", new string('-', IDENT * level), sibling.OrderID, sibling.ParentPID);
}
}
if (order.children != null)
{
foreach (BOMLineClass child in order.children)
{
Print(child, level + 1);
}
}
}
}
}

Related

c# how to access all instances of a class outside of the function?

I am new to C# and OOP, in general, I've kinda hit a wall I am reading in this CSV using the CSV Helper package, but there are some unwanted rows, etc so I have cleaned it up by iterating over "records" and creating a new class LineItems.
But Now I appear to be a bit stuck. I know void doesn't return anything and is a bit of a placeholder. But How can I access all the instances of LineItems outside of this function?
public void getMapper()
{
using (var StreamReader = new StreamReader(#"D:\Data\Projects\dictUnitMapper.csv"))
{
using (var CsvReader = new CsvReader(StreamReader, CultureInfo.InvariantCulture))
{
var records = CsvReader.GetRecords<varMapper>().ToList();
foreach (var item in records)
{
if (item.name != "#N/A" && item.priority != 0)
{
LineItems lineItem = new LineItems();
lineItem.variableName = item.Items;
lineItem.variableUnit = item.Unit;
lineItem.variableGrowthCheck = item.growth;
lineItem.variableAVGCheck = item.avg;
lineItem.variableSVCheck = item.svData;
lineItem.longName = item.name;
lineItem.priority = item.priority;
}
}
}
}
}
public class LineItems
{
public string variableName;
public string variableUnit;
public bool variableGrowthCheck;
public bool variableAVGCheck;
public bool variableSVCheck;
public string longName;
public int priority;
}
public class varMapper
{
public string Items { get; set; }
public string Unit { get; set; }
public bool growth { get; set; }
public bool avg { get; set; }
public bool svData { get; set; }
public string name { get; set; }
public int priority { get; set; }
}
You should write your method to return a list.
public List<LineItems> GetMapper()
{
using (var StreamReader = new StreamReader(#"D:\Data\Projects\dictUnitMapper.csv"))
{
using (var CsvReader = new CsvHelper.CsvReader(StreamReader, CultureInfo.InvariantCulture))
{
return
CsvReader
.GetRecords<varMapper>()
.Where(item => item.name != "#N/A")
.Where(item => item.priority != 0)
.Select(item => new LineItems()
{
variableName = item.Items,
variableUnit = item.Unit,
variableGrowthCheck = item.growth,
variableAVGCheck = item.avg,
variableSVCheck = item.svData,
longName = item.name,
priority = item.priority,
})
.ToList();
}
}
}
Here's an alternative syntax for building the return value:
return
(
from item in CsvReader.GetRecords<varMapper>()
where item.name != "#N/A"
where item.priority != 0
select new LineItems()
{
variableName = item.Items,
variableUnit = item.Unit,
variableGrowthCheck = item.growth,
variableAVGCheck = item.avg,
variableSVCheck = item.svData,
longName = item.name,
priority = item.priority,
}
).ToList();

How do i export child objects with EPPlus as Excel

I am using EPPlus to help me export data as excel. I am still learning to export data properly but somehow am stuck at a point where i am not able to export an object with child objects all flatted out.
ParentObject
public string A;
public string B;
public ChildObject ChildObject;
ChildObject
public string C;
public string D;
so i want my exported excel to look like
A B C D
aa1 bb1 cc1 dd1
aa2 bb2 cc2 dd2
aa3 bb3 cc3 dd3
This is how my current implementation looks like
public void CreateExcel(IEnumerable<T> dataCollection, string fullyQualifiedFileName, string worksheetName)
{
using (var package = new ExcelPackage(new FileInfo(fullyQualifiedFileName)))
{
var worksheet =
package.Workbook.Worksheets.FirstOrDefault(excelWorksheet => excelWorksheet.Name == worksheetName) ??
package.Workbook.Worksheets.Add(worksheetName);
var membersToInclude = typeof(T)
.GetMembers(BindingFlags.Instance | BindingFlags.Public)
.Where(p => Attribute.IsDefined(p, typeof(ExcelUtilityIgnoreAttribute)) == false
|| p.GetCustomAttribute<ExcelUtilityIgnoreAttribute>().IsIgnored == false)
.ToArray();
worksheet.Cells["A1"].LoadFromCollection(dataCollection, true, OfficeOpenXml.Table.TableStyles.None,
BindingFlags.Public, membersToInclude);
package.Save();
}
}
I tried using Microsoft generics using expando object but EPPlus wont work with generics, is there a way where in i can export objects with child objects ?
also: is there any other library that i could use ?
There is no native function that could do that. Hard to come up with something generic as it would require a great deal of assumption. What property type should be automatically exported vs what should be treated a child node and have ITS properties exported or expanded. But if you come up with that it is a basic tree traversal from there.
Below is something I adapted from a similar task. Here, I assume that anything that is a either a string or a data type without properties is considered an value type for exporting (int, double, etc.). But it is very easy to tweak as needed. I threw this together so it may not be fully optimized:
public static void ExportFlatExcel<T>(IEnumerable<T> dataCollection, FileInfo file, string worksheetName)
{
using (var package = new ExcelPackage(file))
{
var worksheet =
package.Workbook.Worksheets.FirstOrDefault(excelWorksheet => excelWorksheet.Name == worksheetName) ??
package.Workbook.Worksheets.Add(worksheetName);
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
var props = typeof (T).GetProperties(flags);
//Map the properties to types
var rootTree = new Branch<PropertyInfo>(null);
var stack = new Stack<KeyValuePair<PropertyInfo, IBranch<PropertyInfo>>>(
props
.Reverse()
.Select(pi =>
new KeyValuePair<PropertyInfo, IBranch<PropertyInfo>>(
pi
, rootTree
)
)
);
//Do a non-recursive traversal of the properties
while (stack.Any())
{
var node = stack.Pop();
var prop = node.Key;
var branch = node.Value;
//Print strings
if (prop.PropertyType == typeof (string))
{
branch.AddNode(new Leaf<PropertyInfo>(prop));
continue;
}
//Values type do not have properties
var childProps = prop.PropertyType.GetProperties(flags);
if (!childProps.Any())
{
branch.AddNode(new Leaf<PropertyInfo>(prop));
continue;
}
//Add children to stack
var child = new Branch<PropertyInfo>(prop);
branch.AddNode(child);
childProps
.Reverse()
.ToList()
.ForEach(pi => stack
.Push(new KeyValuePair<PropertyInfo, IBranch<PropertyInfo>>(
pi
, child
)
)
);
}
//Go through the data
var rows = dataCollection.ToList();
for (var r = 0; r < rows.Count; r++)
{
var currRow = rows[r];
var col = 0;
foreach (var child in rootTree.Children)
{
var nodestack = new Stack<Tuple<INode, object>>();
nodestack.Push(new Tuple<INode, object>(child, currRow));
while (nodestack.Any())
{
var tuple = nodestack.Pop();
var node = tuple.Item1;
var currobj = tuple.Item2;
var branch = node as IBranch<PropertyInfo>;
if (branch != null)
{
currobj = branch.Data.GetValue(currobj, null);
branch
.Children
.Reverse()
.ToList()
.ForEach(cnode => nodestack.Push(
new Tuple<INode, object>(cnode, currobj)
));
continue;
}
var leaf = node as ILeaf<PropertyInfo>;
if (leaf == null)
continue;
worksheet.Cells[r + 2, ++col].Value = leaf.Data.GetValue(currobj, null);
if (r == 0)
worksheet.Cells[r + 1, col].Value = leaf.Data.Name;
}
}
}
package.Save();
package.Dispose();
}
}
So say you have these as a structure:
#region Classes
public class Parent
{
public string A { get; set; }
public Child1 Child1 { get; set; }
public string D { get; set; }
public int E { get; set; }
public Child2 Child2 { get; set; }
}
public class Child1
{
public string B { get; set; }
public string C { get; set; }
}
public class Child2
{
public Child1 Child1 { get; set; }
public string F { get; set; }
public string G { get; set; }
}
#endregion
#region Tree Nodes
public interface INode { }
public interface ILeaf<T> : INode
{
T Data { get; set; }
}
public interface IBranch<T> : ILeaf<T>
{
IList<INode> Children { get; }
void AddNode(INode node);
}
public class Leaf<T> : ILeaf<T>
{
public Leaf() { }
public Leaf(T data) { Data = data; }
public T Data { get; set; }
}
public class Branch<T> : IBranch<T>
{
public Branch(T data) { Data = data; }
public T Data { get; set; }
public IList<INode> Children { get; } = new List<INode>();
public void AddNode(INode node)
{
Children.Add(node);
}
}
#endregion
And this as a test:
[TestMethod]
public void ExportFlatTest()
{
var list = new List<Parent>();
for (var i = 0; i < 20; i++)
list.Add(new Parent
{
A = $"A-{i}",
D = $"D-{i}",
E = i*10,
Child1 = new Child1
{
B = $"Child1-B-{i}",
C = $"Child1-C-{i}",
},
Child2 = new Child2
{
F = $"F-{i}",
G = $"G-{i}",
Child1 = new Child1
{
B = $"Child2-Child1-B-{i}",
C = $"Child2-Child1-C-{i}",
}
}
});
var file = new FileInfo(#"c:\temp\flat.xlsx");
if (file.Exists)
file.Delete();
TestExtensions.ExportFlatExcel(
list
, file
, "Test1"
);
}
Will give you this:

Trouble getting MemberName of "Name" from CustomAttributes = ColumnAttribute of Linq Table Class

I have a little algo I wrote to compare the Linq DataContext table to the sql table. It rolls through the properties of the Linq table and gets the CustomeAttributes of the property, (table columns). It's been working for years, but somebody created a table field with a # sign in it, (UPS#). Linq doesn't like such a name for its properties for obvious reasons. So, it has a member of the ColumnAttribute called "Name" to handle the swap. But, I've always used the "Storage" member for my column name. You would think you would just pick up the "Name" member if it's present, but I can't find it to save my life.
This is the code. Any help is very much appreciated.
public static ColumnInfo[] GetColumnsInfo(Type linqTableClass)
{
// Just looking in the loop to see if I missed something.
foreach (var fld in linqTableClass.GetProperties())
{
foreach (var attr in fld.CustomAttributes)
{
foreach (var arg in attr.NamedArguments)
{
if (arg.MemberName == "Name")
Debug.WriteLine(arg.MemberName);
Debug.WriteLine("{0}", arg.MemberName);
}
}
}
var columnInfoQuery =
from field in linqTableClass.GetProperties()
from attribute in field.CustomAttributes
from namedArgument in attribute.NamedArguments
where namedArgument.MemberName == "DbType"
select new ColumnInfo
{
//ColumnName = field.Name,
ColumnName = namedArgument.MemberName,
DatabaseType = namedArgument.TypedValue.Value.ToString(),
};
return columnInfoQuery.ToArray();
}
and this is the property in the Table Class:
[global::System.Data.Linq.Mapping.ColumnAttribute(Name="PEER_UPS#", Storage="_PEER_UPS_", DbType="Char(31) NOT NULL", CanBeNull=false)]
public string PEER_UPS_
{
get
{
return this._PEER_UPS_;
}
set
{
if ((this._PEER_UPS_ != value))
{
this.OnPEER_UPS_Changing(value);
this.SendPropertyChanging();
this._PEER_UPS_ = value;
this.SendPropertyChanged("PEER_UPS_");
this.OnPEER_UPS_Changed();
}
}
}
I couldn't find a pretty way to get this done. For some reason the ColumnAttribute just didn't want to play nice. Ugly as this is, it works.
public class ColumnInfo
{
public string ColumnName { get; set; }
public string DatabaseType { get; set; }
}
public static IEnumerable<ColumnInfo> GetColumnsInfo(Type linqTableClass)
{
Debug.WriteLine(string.Format("Table: {0}", linqTableClass.Name));
/// In-Case this has to grow in the future. Using a list for the arg names to search for.
/// The primary arg should be in position 0 of the array.
string dbTypeArgName = "DbType";
string fldPrimayName = "Storage";
string fldSecondaryName = "Name";
List<string> fldArgnames = new List<string>() { fldPrimayName, fldSecondaryName };
foreach (var fld in linqTableClass.GetProperties())
{
Debug.WriteLine(string.Format("Field Name: {0}", fld.Name));
foreach (var attr in fld.GetCustomAttributesData().Cast<CustomAttributeData>()
.Where(r => r.AttributeType == typeof(ColumnAttribute))
.Where(a => a.NamedArguments
.Select(n => n.MemberName)
.Intersect(fldArgnames)
.Any()))
{
var fldName = attr.NamedArguments.Where(r => r.MemberName == fldSecondaryName).Count() != 0
? attr.NamedArguments.Where(r => r.MemberName == fldSecondaryName).SingleOrDefault().TypedValue.Value.ToString()
: fld.Name;
var fldType = attr.NamedArguments
.Where(r => r.MemberName == dbTypeArgName)
.Select(r => r.TypedValue.Value.ToString())
.SingleOrDefault();
Debug.WriteLine(string.Format("\tTable Field Name {0} Table Type {1}", fldName, fldType));
yield return new ColumnInfo()
{
ColumnName = fldName,
DatabaseType = fldType,
};
}
}
}
and here is what i suggest:
[sorry, my first example was indeed too simplistic]
Here is how i'd do it:
namespace LinqAttributes
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
public class ColumnInfo
{
public string ColumnName { get; set; }
public string DatabaseType { get; set; }
}
public class Test
{
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Whatever", Storage = "Whatever", DbType = "Char(20)", CanBeNull = true)]
public string MyProperty { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "PEER_UPS#", Storage = "_PEER_UPS_", DbType = "Char(31) NOT NULL", CanBeNull = false)]
public string PEER_UPS_ { get; set; }
}
internal class Program
{
public static IEnumerable<ColumnInfo> GetColumnsInfo(Type type)
{
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(type))
{
var columnAttribute = descriptor.Attributes
.OfType<System.Data.Linq.Mapping.ColumnAttribute>().SingleOrDefault();
if (columnAttribute != null)
{
yield return new ColumnInfo
{
ColumnName = columnAttribute.Name,
DatabaseType = columnAttribute.DbType
};
}
}
}
private static void Main(string[] args)
{
foreach (var item in GetColumnsInfo(typeof(Test)))
{
Debug.WriteLine(item.ColumnName);
}
}
}
}
Just tested it.
Cheers!
public class City
{
public City() { }
[Column("id_city")]
public int Id { get; private set; }
}
var obj = new City();
var pro = obj.GetType().GetProperties();
string columnAttribute = pro.GetCustomAttributes<ColumnAttribute>().FirstOrDefault().Name;
if(columnAttribute == "id_city") {
//sucess
}

I want to use an asp Treeview control with LinqToSql

I am trying to understand how to implement a treeview control - it all looks hideously complicated. However, the Treeview control would be more appropriate.
I have an SQL table containing fields ID and ParentLevelID.
I have added a basic Treeview control to my code:
<asp:TreeView ID="tvLevels" runat="server">
</asp:TreeView>
I want to populate this table using LinqToSQL. Presently, I am displaying the same data as a Gridview:
protected void SetupLevelsPanel()
{
// display levels according to current parentId
_svsCentralDataContext = new SVSCentralDataContext();
object levels;
if (_intParentLevelId == 0)
{
levels = (from sl in _svsCentralDataContext.SVSSurvey_Levels
where sl.ParentLevelID == null && sl.SurveyID == _intSurveyId
select new
{
sl.ID,
sl.SurveyID,
sl.UserCode,
sl.ExternalRef,
sl.Description,
sl.ParentLevelID,
sl.LevelSequence,
sl.Active
});
backUpButton.Visible = false;
}
else
{
levels = (from sl in _svsCentralDataContext.SVSSurvey_Levels
where sl.ParentLevelID == _intParentLevelId && sl.SurveyID == _intSurveyId
select new
{
sl.ID,
sl.SurveyID,
sl.UserCode,
sl.ExternalRef,
sl.Description,
sl.ParentLevelID,
sl.LevelSequence,
sl.Active
});
}
grdLevels.DataSource = levels;
grdLevels.DataBind();
GrdLevelButtons();
}
How can I convert this information to use my Treeview control?
This is my solution.
On my code behind page:
private void BuildTree()
{
tvLevels .Nodes.Clear();
_svsCentralDataContext = new SVSCentralDataContext();
List<DataAccessLayer.Level> items = DataAccessLayer.Levels.GetLevels(_intSurveyId).ToList();
List<DataAccessLayer.Level> rootItems = items.FindAll(p => p.ParentLevelId == null);
foreach (DataAccessLayer.Level item in rootItems)
{
var tvi = new TreeNode(item.Description, item.Id.ToString(CultureInfo.InvariantCulture) );
BuildChildNodes(tvi, items, item.Id);
tvLevels.Nodes.Add(tvi);
}
}
private void BuildChildNodes(TreeNode parentNode, List<DataAccessLayer.Level> items, int parentId)
{
List<DataAccessLayer.Level> children = items.FindAll(p => p.ParentLevelId == parentId).ToList();
foreach (DataAccessLayer.Level item in children)
{
var tvi = new TreeNode(item.Description, item.Id.ToString(CultureInfo.InvariantCulture));
parentNode.ChildNodes.Add(tvi);
BuildChildNodes(tvi, items, item.Id);
}
}
Class Levels.cs
using System;
using System.Collections.Generic;
using System.Linq;
using SVSVoidSurveyDesigner.Database;
namespace SVSVoidSurveyDesigner.DataAccessLayer
{
public class Levels
{
public static IEnumerable<Level> GetLevels(int intSurveyId)
{
var dataContext = new SVSCentralDataContext();
var levels = (from l in dataContext.SVSSurvey_Levels where l.SurveyID == intSurveyId
select new Level
{
Id = l.ID,
SurveyId = l.SurveyID,
UserCode = l.UserCode ,
ExternalRef = l.ExternalRef ,
Description = l.Description ,
ParentLevelId = (l.ParentLevelID),
LevelSequence = ( l.LevelSequence ),
Active = Convert .ToBoolean( l.Active )
});
return levels;
}
}
}
Class Level.cs
namespace SVSVoidSurveyDesigner.DataAccessLayer
{
public class Level
{
public int Id { get; set; }
public int SurveyId { get; set; }
public string UserCode { get; set; }
public string ExternalRef { get; set; }
public string Description { get; set; }
public int? ParentLevelId { get; set; }
public int? LevelSequence { get; set; }
public bool Active { get; set; }
}
}

Get the Depth of an object tree of objects with the same type using LAMBDA expression

I have this object:
public class dtHeader
{
public dtHeader ParentHeader { get; set; }
public string HeaderText { get; set; }
public string DataField { get; set; }
public bool Visible { get; set; }
public int DisplayOrder { get; set; }
}
I want to calculate using a lambda expression, the depth of the object, how many layers of the object in itself exists?
I saw this JavaScript post, but I am struggling to translate it to a one line lambda statement.
Lets say the object is as this new dtHeader(){ ParentHeader = null, HeaderText = "col1" };
the result would be 1
and for new dtHeader(){ ParentHeader = new dtHeader(){ ParentHeader = null, HeaderText = "col1" }, HeaderText = "col1" }; the result would be 2
I want to achieve this with a list<dtHeader>, so some of them would have a depth of 1 and others with deeper depths, and want the deepest depth.
_______ITEM_IN_LIST_OBJECT__
______1___2___3___4___5___6_
D 1. |_o_|_o_|_o_|_o_|_o_|_o_|
E 2. |_o_|___|_o_|___|_o_|_o_|
P 3. |___|___|_o_|___|_o_|___|
T 4. |___|___|___|___|_o_|___|
H 5. |___|___|___|___|_o_|___|
It must go infinitly(Until where it allows for objects to heap up inside eachother) deep.
var HeaderLayerCount = lDtCol.Where(n => n.ParentHeader != null)
.Where(n => n.ParentHeader.ParentHeader != null)
.Where(n => n.ParentHeader.ParentHeader.ParentHeader != null);
EDIT:
I just want to add that if you want to work on a specific depth level, for instance, all objects on a depth of 3, you can use this extra recursion function in the class
public class dtCol
{
public dtCol ParentHeader { get; set; }
public string HeaderText { get; set; }
public string DataField { get; set; }
public bool Visible { get; set; }
public int DisplayOrder { get; set; }
public int Depth { get { return ParentHeader != null ? ParentHeader.Depth + 1 : 1; } }
public int CurrentDepth { get; set; } //Set on initialisation
public dtCol getParent(dtCol col, int getDepth) //Gets the parent on a specific level after the first base level (1) else returns the previous not null child
{
return (col.ParentHeader != null && col.ParentHeader.CurrentDepth == getDepth) ? col.ParentHeader : this.getParent(col.ParentHeader, getDepth);
}
}
You can use it like so:
var HeaderLayerCount = lDtCol.OrderByDescending(n => n.Depth).First().Depth;
for (int hlc = 1; hlc <= HeaderLayerCount; hlc++)
{
var headerrow = new List<dtCol>();
//This foreach adds the parent header if not null else adds the not null child
lDtCol.ForEach(n =>
{
var h = n.getParent(n, hlc); //Get Parent, null is returned if parent does not exists
headerrow.Add((h != null) ? h : n); //If parent is null, add base dtCol so that the headers can be merged upwards.
});
//Do what you need with your new single dimensional list of objects
}
Why not implementing a int GetDepth() method on your class, that will reach the top most ancestor, counting each level?
Your query would then be much simpler.
I was outrunned by Frode, kudos to him
I had the same implementation:
public int GetDepth()
{
if (ParentHeader == null)
{
return 1;
}
else return 1 + ParentHeader.GetDepth();
}
using System;
using System.Linq;
namespace ConsoleApplication3
{
public class dtHeader
{
public dtHeader ParentHeader { get; set; }
public string HeaderText { get; set; }
public string DataField { get; set; }
public bool Visible { get; set; }
public int DisplayOrder { get; set; }
public int Depth
{
get
{
// If header has parent, then this depth is parent.depth + 1
if (ParentHeader != null)
return ParentHeader.Depth+1;
else
return 1; // No parent, root is depth 1
}
}
}
class Program
{
static void Main(string[] args)
{
dtHeader[] headers = {
new dtHeader { HeaderText = "dt1" },
new dtHeader { HeaderText = "dt2" },
new dtHeader { HeaderText = "dt3" },
new dtHeader { HeaderText = "dt4" },
new dtHeader { HeaderText = "dt5" }
};
headers[1].ParentHeader = headers[0];
headers[2].ParentHeader = headers[1];
headers[3].ParentHeader = headers[2];
headers[4].ParentHeader = headers[3];
var deepest = headers.OrderByDescending(item=>item.Depth).First();
Console.WriteLine(deepest.Depth+ ", " + deepest.HeaderText);
var runner = deepest;
while (runner.ParentHeader != null)
runner = runner.ParentHeader;
Console.WriteLine("The deepest root header is:" + runner.HeaderText);
}
}
}
Here's a lambda expression to get what you want:
Func<dtHeader, int> getDepth = null;
getDepth = dth =>
{
var depth = 1;
if (dth.ParentHeader != null)
{
depth += getDepth(dth.ParentHeader);
}
return depth;
};
You have to define it in two parts (assigning null & assigning the body) to let recursion work.
I modified Enigmativity's answer to make it work correctly:
Func<dtHeader, int, int> getDepth = null;
getDepth = (dth, depth) =>
{
if (dth.ParentHeader != null)
{
depth = getDepth(dth.ParentHeader, ++depth);
}
return depth;
};
Call it like this:
int depth = getDepth(header, 0)

Categories

Resources