I get 2 objects of Type "Shipment" ("Shipment1" and "Shipment2") and must read each value of them. If the value of Shipment1 is NULL/empty I want to look into the same Value of Shipment2 and if the Value is not NULL/empty I have to copy it to Shipment1.
I tried to iterate through my objects with Reflection but the nested Objects "Consignor", "Consignee", "Invoices" let me fail. I hope you can help me.
I have the following simplified class structure:
public class Shipment
{
public int Id { get; set; }
public Address Consignor { get; set; }
public Address Consignee { get; set; }
public IEnumerable<Invoice> Invoices{ get; set; }
}
public class Address
{
public string Name { get; set; }
public string Street{ get; set; }
public string Zip { get; set; }
public string City{ get; set; }
public string Country{ get; set; }
}
public class Invoice
{
public IEnumerable<Item> Items{ get; set; }
}
public class Item
{
public string Description{ get; set; }
public int Amount { get; set; }
}
I tried it this way. It worked for the top level properties of shipment, but not for Consignor, Consignee, Invoices etc.
foreach (PropertyInfo info1 in shipment1.GetType().GetProperties())
{
var datatype = info1.PropertyType;
switch (datatype.Name.ToLower())
{
case "string":
if (!string.IsNullOrEmpty((string)info1.GetValue(shipment1)))
{
string value= (string)info1.GetValue(shipment1);
string name = info1.Name;
Type type = input.GetType();
PropertyInfo info2 = shipment2.GetType().GetProperty(name);
if (string.IsNullOrEmpty((string)info2.GetValue(shipment2)))
{
info2.SetValue(shipment2, value, null);
}
}
break;
case "integer":
// and so on
}
}
I ended up with this, having shipment1 with the same values that shipment2, even if shipment1 was null at the beginning.
Please note that it copies IEnumerable like pointers. After the copy, editing copy.Invoices will also edit source.Invoices, and vice versa.
// used for my test:
Shipment shipment1 = null;
Shipment shipment2 = new Shipment { Id = 42, Consignor = new Address { Name = "Foo1", Street = "Bar1", Zip = "Baz1", City = "Qux1", Country = "Quux1" }, Consignee = new Address { Name = "Foo2", Street = "Bar2", Zip = "Baz2", City = "Qux2", Country = "Quux2" }, Invoices = new Invoice[] { new Invoice { Items = new Item[] { new Item { Description = "FooBar1", Amount = 1 }, new Item { Description = "BazQux1", Amount = 1 } } }, new Invoice { Items = new Item[] { new Item { Description = "FooBar2", Amount = 2 }, new Item { Description = "BazQux2", Amount = 2 } } } } };
// kind of ugly but I didn't manage to do it prettier:
shipment1 = Work(shipment2, shipment1);
private T Work<T>(T source, T copy)
{
if (source == null)
return copy;
if (copy == null)
copy = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in typeof(T).GetProperties())
{
switch (prop.PropertyType.Name.ToLower())
{
case "string":
string str = (string)prop.GetValue(source);
if (!string.IsNullOrEmpty(str))
if (string.IsNullOrEmpty((string)prop.GetValue(copy)))
prop.SetValue(copy, str);
break;
case "int32":
int i = (int)prop.GetValue(source);
if (i != 0)
if ((int)prop.GetValue(copy) == 0)
prop.SetValue(copy, i);
break;
case "address":
prop.SetValue(copy, Work(prop.GetValue(source) as Address, prop.GetValue(copy) as Address));
break;
case "ienumerable`1":
switch (prop.PropertyType.GetGenericArguments()[0].Name.ToLower())
{
case "invoice":
IEnumerable<Invoice> invoices = (IEnumerable<Invoice>)prop.GetValue(source);
if (invoices != null && invoices.Count() > 0)
if ((IEnumerable<Invoice>)prop.GetValue(copy) == null)
prop.SetValue(copy, invoices);
break;
// edit: this is actually useless
/*
case "item":
IEnumerable<Item> items = (IEnumerable<Item>)prop.GetValue(source);
if (items != null && items.Count() > 0)
if ((IEnumerable<Item>)prop.GetValue(copy) == null)
prop.SetValue(copy, items);
break;
*/
}
break;
}
}
return copy;
}
Related
I have 2 classes User and UserDto both have a Phones property, all the other properties I can map perfectly.
But the problem occurs with the Phones list of the User class, since being null, I cannot identify what type of list it is.
What I need is to be able to and create a list based on its type using reflection.
var list = new List<PhoneDto>();
list.Add(new PhoneDto { Code = 1, Number = 11111111 });
list.Add(new PhoneDto { Code = 2, Number = 11111112 });
list.Add(new PhoneDto { Code = 3, Number = 11111113 });
list.Add(new PhoneDto { Code = 4, Number = 11111114 });
list.Add(new PhoneDto { Code = 5, Number = 11111115 });
var userDto=new UserDto
{
Age=18,
IsData=true,
CreationDate=DateTime.Now,
Phones =list
};
var myMapper = new MyMapper();
var user= myMapper.Map<User>(userDto);
public class User
{
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public int Age { get; set; }
public bool IsData { get; set; }
public ICollection.Phone. Phones { get; set; }
}
public class UserDto
{
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public int Age { get; set; }
public bool IsData { get; set; }
public List.PhoneDto. Phones { get; set; }
}
namespace CustomMapping;
public class MyMapper
{
public T Map<T>(object sourceObject)
{
if (sourceObject == null) throw new Exception("Source object is null");
var instance = Activator.CreateInstance(typeof(T));
if (instance == null) throw new Exception("Can not create de typeof(T) Instance");
var sourceType = sourceObject.GetType();
var targetType = instance.GetType();
PropertyInfo[] sourceProperties = sourceType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo[] targetProperties = targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var sourceProperty in sourceProperties)
{
if (sourceProperty == null) continue;
var targetProperty = targetProperties.FirstOrDefault(p => p.Name.ToLower() == sourceProperty.Name.ToLower() && p.CanWrite);
if (targetProperty == null) continue;
var sourceValue = sourceProperty?.GetValue(sourceObject, null);
if (sourceProperty?.PropertyType == targetProperty.PropertyType)
{
instance?.GetType()?.GetProperty(sourceProperty.Name)?.SetValue(instance, sourceValue);
continue;
}
var isEnumerable = IsEnumerable(targetProperty);
if (isEnumerable)
{
//---------Here I need to map the lists---------
}
}
return (T)instance;
}
private bool IsEnumerable(PropertyInfo propertyInfo)
{
return (propertyInfo.PropertyType.GetInterfaces().Any(p => p.Name == "IEnumerable`1") && propertyInfo.PropertyType.FullName.IndexOf("System.String") < 0);
}
}
I would appreciate any help.
I have this class, a hierarchy of categories.
class Categories
{
public long Id { get; set; }
public long ParentId { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public List<Categories> ChildrenData { get; set; }
}
How can I recursively iterate through this class of unknown depth and return the path to get there?
All "Id" values are unique. Say I want to find Id = 23 and get the path to get there by concatenating "Name".
For example, in the image below searching for ID = 23 would return: Default Category/Books/Nonfiction/Best-sellers
Example Hierarchy
My suggestion is that you first build an index:
public static Dictionary<long, Category> IndexBuilder(Category c)
{
var index = new Dictionary<long, Category>();
IndexBuilder(c, index);
return index;
}
private static void IndexBuilder(Category c, Dictionary<long, Category> index)
{
if (index.ContainsKey(c.Id))
return;
index[c.Id] = c;
foreach(var child in c.ChildrenData)
IndexBuilder(child, index);
}
Now you have a lookup, and your path is then easy to produce:
static IEnumerable<Category> PathToRoot(long id, Dictionary<long, Category> index)
{
// Presumably the parent id of the top category is a sentinel.
long current = id
while (current != 0)
{
var category = index[current];
yield return category;
current = category.ParentId;
}
}
Or maybe we just go until we run out of index:
static IEnumerable<Category> PathToRoot(long id, Dictionary<long, Category> index)
{
long current = id
while (index.ContainsKey(current))
{
var category = index[current];
yield return category;
current = category.ParentId;
}
}
Now you have a tool you can use to make your string:
static string Slash<T>(this IEnumerable<T> items) =>
string.Join("/", items);
var s = PathToRoot(23, index)
.Reverse()
.Select(c => c.Name)
.Slash();
See what I am doing here? Make a bunch of helper methods each of which is about five lines long, that can be composed together to make powerful solutions.
I have provided 2 ways, the first way is recursive and the last is not.
Recursive way, add a reference to your parent. This way when you find a match you can easily traverse your way back up the chain to create your path.
class Categories
{
public Categories Parent { get; set; }
public long Id { get; set; }
public long ParentId { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public List<Categories> ChildrenData { get; set; }
}
Then add a Find() method:
public string Find(long id)
{
if( Id == id )
{
return GetPath(); //<-- we need to code this next.
}
else
{
foreach( var entry in Categories)
{
string path = entry.Find(id);
if( path != null )
{
return path;
}
}
return null;
}
}
And finally the GetPath(), the assumption here is that the highest level instances of Categories do not have a Parent:
public string GetPath()
{
System.Text.StringBuilder sb = new StringBuilder();
Categories current = this;
while( current != null)
{
sb.Insert(0,current.Name);
if( current != this)
{
sb.Insert(0,"/");
}
current = Parent;
}
return sb.ToString();
}
Now if recursion isn't what you want, then pass in the current path to the Find() method.
public string Find(long id, string pathSoFar)
{
if (pathSoFar == null)
{
pathSoFar = Name;
}
else
{
pathSoFar = pathSoFar + Name;
}
if ( Id == id)
{
return pathSoFar;
}
else
{
foreach( var entry in Categories)
{
string path = entry.Find(id, pathSoFar + "/");
if( path != null )
{
return path;
}
}
return null;
}
}
Usage:
var nonRecusive = cats.Find(23, null);
This will get what you are looking for using recursion:
void Main()
{
var data = GetData();
Console.WriteLine(GetPath(data, 23, ""));
}
public String GetPath(Categories c, Int32 id, String path)
{
if (c.Id == id)
{
return path + "/" + c.Name;
}
foreach (var cd in c.ChildrenData)
{
var p = GetPath(cd, id, path + "/" + c.Name);
if (!String.IsNullOrWhiteSpace(p))
{
return p;
}
}
return "";
}
public class Categories
{
public long Id { get; set; }
public long ParentId { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public List<Categories> ChildrenData { get; set; }
}
public Categories GetData()
{
return
new Categories
{
Id = 1,
Name = "Default Category",
ChildrenData = new List<Categories>
{
new Categories
{
Id = 2,
Name = "Magazines",
ChildrenData = new List<Categories> {}
},
new Categories
{
Id = 2,
Name = "Books",
ChildrenData = new List<Categories>
{
new Categories
{
Id = 20,
Name = "Fiction",
ChildrenData = new List<Categories> {}
},
new Categories
{
Id = 21,
Name = "Nonfiction",
ChildrenData = new List<Categories>
{
new Categories
{
Id = 22,
Name = "New",
ChildrenData = new List<Categories> {}
},
new Categories
{
Id = 23,
Name = "Best-Sellers",
ChildrenData = new List<Categories> {}
},
}
}
}
}
}
};
}
I want to write a function which it turns every properies and child properties of an object. And if all properties of one property are null, then I will set that property as null. I will explain with one example.
For example, if both TeacherName and TeacherSurname are null then I want to set Teacher to null. Then, if ExamMark and ExamName and Teacher are null, then Exam will be null.
You can see json version of like my question from this link Json Version
public class Student
{
public string Name { get; set; }
public string Surname { get; set; }
public Address Address { get; set; }
public Exam Exam { get; set; }
}
public class Exam
{
public string ExamMark { get; set; }
public string ExamName { get; set; }
public Teacher Teacher { get; set; }
}
public class Teacher
{
public string TeacherName { get; set; }
public string TeacherSurname { get; set; }
}
public class Address
{
public string Country { get; set; }
public string City { get; set; }
}
I wrote this method. But this doing only first step. But I need recursive for child class. How can I convert this method to recursive?
public static object ConvertToNull(object obj)
{
Type objType = obj.GetType();
PropertyInfo[] properties = objType.GetProperties();
var mainClassProperties = properties.Where(p => p.PropertyType.Assembly == objType.Assembly);
foreach (var mainClassProperty in mainClassProperties)
{
object propValue = mainClassProperty.GetValue(obj, null);
var classAllProperties = propValue.GetType().GetProperties();
if (propValue.GetType().GetProperties().All(propertyInfo => propertyInfo.GetValue(propValue) == null))
{
mainClassProperty.SetValue(obj, null);
}
}
return obj;
}
What you're asking for is actually an anti-pattern. What this does is it propagates the requirement for tons of null checks in your code. Not only do you have to check if all properties are null, you also have to check if your object is null when you want to use it. Also reflection is very slow and if you're doing this often, you'll bog down your application.
You should look into the Null Object Pattern. It basically does what you want it to, and you remove all those pesky null checks:
public class Student
{
public string Name { get; set; }
public string Surname { get; set; }
public Address Address { get; set; }
public Exam Exam { get; set; }
public static Student NullStudent { get; } = new Student
{
Name = null,
Surname = null,
Address = Address.NullAddress,
Exam = Exam.NullExam,
}
}
You can do this for each object in your code that acts this way (you can see that I did it on your nested Address and Exam types as well) and then instead of doing this:
if (Student.EverythingIsNull) { Student = null }
if (Student is null) { //do null stuff }
You can do this:
if (item == Student.NullStudent) { //do null stuff }
This leads to clearer code, your intent stands out more, and you can specifically define in each object what constitutes as null.
using Generics and Reflection.
public T ConvertToNull<T>(T model) where T : class
{
if (model == null) return null;
Type type = model.GetType();
PropertyInfo[] properties = type.GetProperties();
var valueTypes = properties.Where(p => p.PropertyType.Assembly != type.Assembly);
var nonValueTypes = properties.Where(p => p.PropertyType.Assembly == type.Assembly);
foreach (var nonValueType in nonValueTypes)
nonValueType.SetValue(model, ConvertToNull(nonValueType.GetValue(model)));
if (valueTypes.All(z => z.GetValue(model) == null) && nonValueTypes.All(z => z.GetValue(model) == null))
return null;
else
return model;
}
Here you can call it
List<Student> students = new List<Student>();
Student student = new Student() { Name = "StudentName", Surname = "StudentSurname", Address = new Address() { City = "City", Country = "Country" }, Exam = new Exam() { ExamMark = "ExamMark", ExamName = "ExamName", Teacher = new Teacher() { TeacherName = "TeacherName", TeacherSurname = "TeacherSurname" } } };
Student student2 = new Student() { Name = "StudentName", Surname = "StudentSurname", Address = new Address(), Exam = new Exam() { ExamMark = "ExamMark", ExamName = "ExamName", Teacher = new Teacher() } };
students.Add(student);
students.Add(student2);
List<Student> results = new List<Student>();
foreach (var item in students)
{
var result = ConvertToNull(item);
results.Add(result);
}
Have a look at this The GetValueOrNull should work and do what you need, not tested with all possible use cases but it oculd be tweaked a little if doesn't work in all cases
public static bool IsSimpleType(Type type)
{
return
type.IsPrimitive ||
new Type[] {
typeof(Enum),
typeof(String),
typeof(Decimal),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(Guid)
}.Contains(type) ||
Convert.GetTypeCode(type) != TypeCode.Object ||
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0]))
;
}
public object GetValueOrNull(object obj)
{
if (obj == null) return null;
Type objType = obj.GetType();
PropertyInfo[] properties = objType.GetProperties();
var simpleTypes = properties.Where(t => IsSimpleType(t.PropertyType));
var nonValueTypes = properties.Where(p => !simpleTypes.Contains(p));
foreach (var child in nonValueTypes)
{
child.SetValue(obj, GetValueOrNull(child.GetValue(obj)));
}
return simpleTypes.All(z => z.GetValue(obj) == null) && nonValueTypes.All(z => z.GetValue(obj) == null) ? null : obj;
}
call it as follows
var student = new Student { Address = new Address { }, Exam = new Exam { Teacher = new Teacher() } };
var test = GetValueOrNull(student);
hope it helps :)
This is my code in which a list contains 2 more lists where the WorkItem collection contains a large number of records such as 7,000 it takes 10 minutes.
Is there any way to make it faster and in case it's deciding the WorkItem type if it's a bug, task, or product backlog item?
Please tell me how to make the looping faster.
It's taking 10 minutes to loop 7,000 records. Can we use threading to make it faster?
var workItemList = new List<WorkItemViewModel>();
for (int i = 0; i < workItemCollection.Count; i++)
{
var workItem = workItemCollection[i];
if (workItem.Type.Name == "Product Backlog Item")
{
var model = new WorkItemViewModel()
{
FID = (workItem.WorkItemLinks.Count > 0) ?
((workItem.WorkItemLinks[0].LinkTypeEnd.Name.ToString() != "Child") ?
workItem.WorkItemLinks[0].TargetId : 0) : 0,
ID = workItem.Id,
Name = workItem.Title,
State = workItem.State,
priorty = Convert.ToInt32(workItem.Fields["Priority"].Value),
// Size =(int) workItem.Fields["Size"].Value ,
Size = Convert.ToInt32(workItem.Fields["Effort"].Value),
StoryPoints = Convert.ToInt32(workItem.Fields["Story Points"].Value),
DoneStatus = workItem.Fields["Done Status"].Value.ToString(),
StoryOwner = workItem.Fields["Story Owner"].Value.ToString(),
Assignedto = workItem.Fields["Assigned To"].Value.ToString(),
StoryAuthor = workItem.Fields["Story Author"].Value.ToString(),
IterationPath = workItem.IterationPath
};
workItemList.Add(model);
}
else
{
switch (workItem.Type.Name)
{
case "Task":
var task = new TFSTask()
{
Storyid = (workItem.WorkItemLinks.Count > 0) ?
workItem.WorkItemLinks[0].TargetId : 0,
ID = workItem.Id,
name = workItem.Title,
//activity = workItem.Fields["MyCompany.Activity"].Value.ToString(),
//start = (DateTime?)workItem.Fields["MyCompany.ActivityStart"].Value,
//due = (DateTime?)workItem.Fields["MyCompany.ActivityFinish"].Value,
status = workItem.State,
IterationPath = workItem.IterationPath,
Assignedto = workItem.Fields["Assigned To"].Value.ToString(),
priorty = Convert.ToInt32(workItem.Fields["Priority"].Value),
effort = Convert.ToInt32(workItem.Fields["effort"].Value),
Completed = Convert.ToInt32(workItem.Fields["Completed"].Value)
};
if (task.Storyid != 0)
{
workItemList.Last().Tasks.Add(task);
}
break;
case "Bug":
var bug = new TFSIssue()
{
Storyid = (workItem.WorkItemLinks.Count > 0) ?
workItem.WorkItemLinks[0].TargetId : 0,
ID = workItem.Id,
Name = workItem.Title,
//start = (DateTime?)workItem.Fields["MyCompany.ActivityStart"].Value,
//due = (DateTime?)workItem.Fields["MyCompany.ActivityFinish"].Value,
State = workItem.State,
IterationPath = workItem.IterationPath,
Assignedto = workItem.Fields["Assigned To"].Value.ToString(),
priorty = Convert.ToInt32(workItem.Fields["Priority"].Value),
effort = Convert.ToInt32(workItem.Fields["effort"].Value),
// Completed = Convert.ToInt32(workItem.Fields["Completed"].Value)
};
if (bug.Storyid != 0)
{
workItemList.Last().Issues.Add(bug);
}
break;
default:
break;
}
}
}
public class WorkItemViewModel
{
public string Name { get; set; }
public int ID { get; set; }
public string State { get; set; }
// public DateTime? due { get; set; }
public int priorty { get; set; }
public int Size { get; set; }
// public int effort { get; set; }
public int StoryPoints { get; set; }
public string DoneStatus { get; set; }
public string StoryOwner { get; set; }
public string Assignedto { get; set; }
public string StoryAuthor { get; set; }
public string IterationPath { get; set; }
public int FID { get; set; }
public List<TFSIssue> Issues { get; set; }
public List<TFSTask> Tasks { get; set; }
public WorkItemViewModel() // Added a public constructor
{
Issues = new List<TFSIssue>();
Tasks = new List<TFSTask>();
}
}
I think you can't with the current structure of your data. As I understand it, the relation between your backlog items and the related tasks/bugs is only explained by the relative order in the workItemCollection object. So if you were to use Parrallel.For with a ConccurentBag, you would not know to which backlog item each task/bug item should be added...
On another note, 10 minutes seems like a very long time for a simple loop of 7.000 records. Is there anything else going on that is not clear in the code? You should investigate the sub steps, to see what is taking the time.
I have one modification to suggest but i'm not sure if it's gonna improve the performance since I can't really test it:
var workItemList = new List<WorkItemViewModel>();
for (int i = 0; i < workItemCollection.Count; i++)
{
var workItem = workItemCollection[i];
WorkItemViewModel model = null;
switch (workItem.Type.Name)
{
case "Product Backlog Item":
model = new WorkItemViewModel()
{
// ...
};
workItemList.Add(model);
case "Task":
var task = new TFSTask()
{
// ...
};
if (task.Storyid != 0)
{
model.Tasks.Add(task);
}
break;
case "Bug":
var bug = new TFSIssue()
{
// ...
};
if (bug.Storyid != 0)
{
model.Issues.Add(bug);
}
break;
default:
break;
}
}
This way no need to call Last() each time you wanna add a Task or a Bug, it's better to just save the last model as a variable and make the other switch cases use that variable.
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)