How do i export child objects with EPPlus as Excel - c#

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:

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 Deserialize Into Local Collections in .NET?

I am trying to build a data pipeline in .NET. I have been given an xsd and have used the XML Schema Definition Tool to generate C# classes that represent the object model. In order to load this data into my data store I need to transform the data coming out of the XML into structure that matches my application schema and collect/dedupe elements. To do this I have a parser class that will read a file and load the contents into local collections which will then be loaded into my database. I see two options to do this -
Manually loop through the XML with an XmlReader and pull out the data I need, loading it into the local collections in stream. This is not desirable because it does not take advantage of the strongly typed/strict xsd that I was given and requires a lot of hard coding things like while (reader.Read()), check for specific XML nodes, and then `reader.GetAttribute("HardCodedString").
Use XmlSerializer to deserialize the whole file at once and then loop through the deserialized collections and insert into my local collections. This is not desirable because the files could be very large and this method forces me to loop through all of the data twice (once to deserialize and once to extract the data to my local collections).
Ideally I would like some way to register a delegate to be executed as each object is deserialized to insert into my local collections. Is there something in the framework that allows me to do this? Requirements are as follows:
Performant - Only loop through the data once.
Functional - Data is inserted into the local collections during deserialization.
Maintainable - Utilize strongly typed classes that were generated via the xsd.
I have created a minimal example to illustrate my point.
Example XML File:
<Hierarchy xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.example.com/example">
<Children>
<Child ChildId="1" ChildName="First">
<Parents>
<Parent ParentId="1" ParentName="First" RelationshipStart="1900-01-01T00:00:00"/>
<Parent ParentId="2" ParentName="Second" RelationshipStart="2000-01-01T00:00:00"/>
</Parents>
</Child>
<Child ChildId="2" ChildName="Second">
<Parents>
<Parent ParentId="2" ParentName="Second" RelationshipStart="1900-01-01T00:00:00"/>
<Parent ParentId="3" ParentName="Third" RelationshipStart="2000-01-01T00:00:00"/>
</Parents>
</Child>
</Children>
</Hierarchy>
Local collections I am trying to load:
public Dictionary<int, string> Parents { get; }
public Dictionary<int, string> Children { get; }
public List<Relationship> Relationships { get; }
Manual version (not maintainable and doesn't use xsd):
public void ParseFileManually(string fileName)
{
using (var reader = XmlReader.Create(fileName))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Hierarchy")
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Child")
{
int childId = int.Parse(reader.GetAttribute("ChildId"));
string childName = reader.GetAttribute("ChildName");
Children[childId] = childName;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Parent")
{
int parentId = int.Parse(reader.GetAttribute("ParentId"));
string parentName = reader.GetAttribute("ParentName");
DateTime relationshipStart = DateTime.Parse(reader.GetAttribute("RelationshipStart"));
Parents[parentId] = parentName;
Relationships.Add(
new Relationship{
ParentId = parentId,
ChildId = childId,
Start = relationshipStart
});
}
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Child")
{
break;
}
}
}
}
}
}
}
}
Deserialize version (loops through the data twice):
public void ParseFileWithDeserialize(string fileName)
{
var serializer = new XmlSerializer(typeof(Hierarchy));
using (var fileStream = new FileStream(fileName, FileMode.Open))
{
var fileData = (Hierarchy) serializer.Deserialize(fileStream);
foreach (var child in fileData.Children)
{
Children[child.ChildId] = child.ChildName;
foreach (var parent in child.Parents)
{
Parents[parent.ParentId] = parent.ParentName;
Relationships.Add(
new Relationship
{
ParentId = parent.ParentId,
ChildId = child.ChildId,
Start = parent.RelationshipStart
});
}
}
}
}
You should use some annotations to get the data from the correct field in the XML, if you use these definitions;
public class Hierarchy
{
public Hierarchy()
{
Children = new List<Child>();
}
public List<Child> Children { get; set; }
}
public class Child
{
public Child()
{
Parents = new List<Parent>();
}
[XmlAttribute("ChildId")]
public int ChildId { get; set; }
[XmlAttribute("ChildName")]
public string ChildName { get; set; }
public List<Parent> Parents { get; set; }
}
public class Parent
{
[XmlAttribute("ParentId")]
public int ParentId { get; set; }
[XmlAttribute("ParentName")]
public string ParentName { get; set; }
[XmlAttribute("RelationshipStart")]
public DateTime RelationshipStart { get; set; }
}
Then you should be able to simplify your code to;
public static Hierarchy Deserialize(string fileName)
{
using (var fileStream = new StreamReader(fileName, Encoding.UTF8))
{
XmlSerializer ser = new XmlSerializer(typeof(Hierarchy));
return (Hierarchy)ser.Deserialize(fileStream);
}
}
To test it out you can create a sample data set and serialize it to a file, then use the above code to read it back
public static void Serialize(Hierarchy h, string fileName)
{
System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(Hierarchy));
StreamWriter sw = new StreamWriter(fileName, false, Encoding.UTF8);
ser.Serialize(sw, h);
}
Test Code
static void Test()
{
Hierarchy h = new Hierarchy();
Parent p1 = new Parent() { ParentId = 1, ParentName = "First", RelationshipStart = DateTime.Now };
Parent p2 = new Parent() { ParentId = 2, ParentName = "Second", RelationshipStart = DateTime.Now };
Parent p3 = new Parent() { ParentId = 3, ParentName = "Third", RelationshipStart = DateTime.Now };
Child c1 = new Child() { ChildId = 1, ChildName = "First" };
c1.Parents.Add(p1);
c1.Parents.Add(p2);
Child c2 = new Child() { ChildId = 2, ChildName = "Second" };
c2.Parents.Add(p2);
c2.Parents.Add(p3);
h.Children.Add(c1);
h.Children.Add(c2);
Serialize(h, AppContext.BaseDirectory + "Text.xml");
Hierarchy hReadBack = Deserialize(AppContext.BaseDirectory + "Text.xml");
}
Edit : To answer your question
Use these classes
public class Hierarchy
{
public Hierarchy()
{
Children = new List<Child>();
}
public List<Child> Children { get; set; }
private Dictionary<int, string> _parents;
private Dictionary<int, string> _childrenList;
private List<Relationship> _relationships;
private void CalcuateLists()
{
_parents = new Dictionary<int, string>();
_childrenList = new Dictionary<int, string>();
_relationships = new List<Relationship>();
foreach (Child c in this.Children)
{
if (!_childrenList.ContainsKey(c.ChildId))
{
_childrenList.Add(c.ChildId, c.ChildName);
}
foreach (Parent p in c.Parents)
{
if (!_parents.ContainsKey(p.ParentId))
{
_parents.Add(p.ParentId, p.ParentName);
}
if (_relationships.FirstOrDefault(dat => dat.ParentId == p.ParentId && dat.ChildId == c.ChildId) == null)
{
_relationships.Add(new Relationship() { ChildId = c.ChildId, ParentId = p.ParentId, Start = p.RelationshipStart });
}
}
}
}
public Dictionary<int, string> Parents {
get
{
if (_parents == null)
CalcuateLists();
return _parents;
}
}
public Dictionary<int, string> ChildrenList {
get
{
if (_childrenList == null)
CalcuateLists();
return _childrenList;
}
}
public List<Relationship> Relationships {
get
{
if (_relationships == null)
CalcuateLists();
return _relationships;
}
}
}
public class Child
{
public Child()
{
Parents = new List<Parent>();
}
[XmlAttribute("ChildId")]
public int ChildId { get; set; }
[XmlAttribute("ChildName")]
public string ChildName { get; set; }
public List<Parent> Parents { get; set; }
}
public class Parent
{
[XmlAttribute("ParentId")]
public int ParentId { get; set; }
[XmlAttribute("ParentName")]
public string ParentName { get; set; }
[XmlAttribute("RelationshipStart")]
public DateTime RelationshipStart { get; set; }
}
Then your test code becomes
public static void Test()
{
Hierarchy h = new Hierarchy();
Parent p1 = new Parent() { ParentId = 1, ParentName = "First", RelationshipStart = DateTime.Now };
Parent p2 = new Parent() { ParentId = 2, ParentName = "Second", RelationshipStart = DateTime.Now };
Parent p3 = new Parent() { ParentId = 3, ParentName = "Third", RelationshipStart = DateTime.Now };
Child c1 = new Child() { ChildId = 1, ChildName = "First" };
c1.Parents.Add(p1);
c1.Parents.Add(p2);
Child c2 = new Child() { ChildId = 2, ChildName = "Second" };
c2.Parents.Add(p2);
c2.Parents.Add(p3);
h.Children.Add(c1);
h.Children.Add(c2);
Serialize(h, AppContext.BaseDirectory + "Text.xml");
Hierarchy hReadBack = Deserialize(AppContext.BaseDirectory + "Text.xml");
Dictionary<int, string> Parents = hReadBack.Parents;
Dictionary<int, string> Children = hReadBack.ChildrenList;
List<Relationship> Relationships = hReadBack.Relationships;
}
EDIT
To get the results directly without looping
You will need this class
public class Relationship
{
public int ParentId { get; set; }
public int ChildId { get; set; }
public DateTime Start { get; set; }
}
And this selection
// Get a list of child ids and names
Dictionary<int, string> Children = (from c in hReadBack.Children select new { ChildId = c.ChildId, Name = c.ChildName}).ToDictionary(dat => dat.ChildId, dat => dat.Name);
// Get a parent ids and names
Dictionary<int, string> Parents = (from p in hReadBack.Children.SelectMany(i => i.Parents) select new { ParentId = p.ParentId, Name = p.ParentName }).Distinct().ToDictionary(dat => dat.ParentId, dat => dat.Name);
// Get the relationships
List<Relationship> Relationship = (from Child c in hReadBack.Children from Parent p in c.Parents select new Relationship() { ChildId = c.ChildId, ParentId = p.ParentId, Start = p.RelationshipStart }).ToList();

C# Copy all object parameters to child class object

Suppose I have an object of a parent class which I can't change - for example, an instance of ListBox with long list of parameters. Now I create a child class:
class PlaylistBox : ListBox
{
void CopySettingsFrom(ListBox In)
{
//...what now?
}
}
Question - how can I efficiently make a shallow copy from In object to the new object of PlaylistBox?
Here an example with 3 methods, based on reflection and AutoMapper with explanation:
internal class Program
{
private static void Main(string[] args)
{
Example1();
Example2();
Example3();
}
public static void Example1()
{
Console.WriteLine("This example shows using copy with reflection. Minus of this method - u have to implement FULL copy for each class or u will copy only references to object properties");
//creating new parent class with some values
var parentClass = new ParentClass
{
Property1 = "qqq",
Property2 = 1,
ObjectProperty = new SomeClassWithObjectProperty
{
ObjectProperty = new SomeObjectClass {SomeProperty = "www"}
}
};
//crating new child class and copy REFERENCES to properties
var childClassReflection = new ChildClassReflection(parentClass);
//changing properties of parent
parentClass.Property1 = "rrr";
parentClass.Property2 = 2;
parentClass.ObjectProperty.ObjectProperty.SomeProperty = "eee";
//we will get OLD values for VALUE types and OLD values for REFERENCE types
//qqq 1 WWW
Console.WriteLine(childClassReflection.Property1 + " " + childClassReflection.Property2 + " " + childClassReflection.ObjectProperty.ObjectProperty.SomeProperty);
}
public static void Example2()
{
Console.WriteLine();
Console.WriteLine("This example shows using copy with reflection WITH FULL COPY");
//creating new parent class with some values
var parentClass = new ParentClass
{
Property1 = "qqq",
Property2 = 1,
ObjectProperty = new SomeClassWithObjectProperty
{
ObjectProperty = new SomeObjectClass {SomeProperty = "www"}
}
};
//crating new child class and copy REFERENCES to properties
var childClassReflection = new ChildClassReflectionWithFullCopy(parentClass);
//changing properties of parent
parentClass.Property1 = "rrr";
parentClass.Property2 = 2;
parentClass.ObjectProperty.ObjectProperty.SomeProperty = "eee";
//we will get OLD values for VALUE types and NEW values for REFERENCE types
//qqq 1 eee
Console.WriteLine(childClassReflection.Property1 + " " + childClassReflection.Property2 + " " + childClassReflection.ObjectProperty.ObjectProperty.SomeProperty);
}
public static void Example3()
{
//here i will show copy using AutoMapper
Console.WriteLine();
Console.WriteLine("This example shows using copy with AutoMapper");
//creating new parent class with some values
var parentClass = new ParentClass
{
Property1 = "qqq",
Property2 = 1,
ObjectProperty = new SomeClassWithObjectProperty
{
ObjectProperty = new SomeObjectClass { SomeProperty = "www" }
}
};
Mapper.Initialize(cfg => cfg.CreateMap<ParentClass, ChildClassAutoMapper>());
//crating new child class and copy REFERENCES to properties
var childClassReflection = Mapper.Map<ChildClassAutoMapper>(parentClass);
//changing properties of parent
parentClass.Property1 = "rrr";
parentClass.Property2 = 2;
parentClass.ObjectProperty.ObjectProperty.SomeProperty = "eee";
//we will get OLD values for VALUE types and OLD values for REFERENCE types
//qqq 1 eee
Console.WriteLine(childClassReflection.Property1 + " " + childClassReflection.Property2 + " " + childClassReflection.ObjectProperty.ObjectProperty.SomeProperty);
}
}
public class ChildClassAutoMapper:ParentClass
{
}
public class ChildClassReflection : ParentClass
{
public ChildClassReflection(ParentClass parentClass)
{
foreach (var p in ParentProperties)
p.SetMethod.Invoke(this, new[] {p.GetMethod.Invoke(parentClass, null)});
}
//do it only once for best performance
private static PropertyInfo[] ParentProperties { get; } = typeof(ParentClass).GetProperties().Where(c => c.CanRead && c.CanWrite).ToArray();
}
public class ChildClassReflectionWithFullCopy : ParentClass
{
public ChildClassReflectionWithFullCopy(ParentClass parentClass)
{
var parentClassLocal = JsonConvert.DeserializeObject<ParentClass>(JsonConvert.SerializeObject(parentClass));
foreach (var p in ParentProperties)
p.SetMethod.Invoke(this, new[] {p.GetMethod.Invoke(parentClassLocal, null)});
}
//do it only once for best performance
private static PropertyInfo[] ParentProperties { get; } = typeof(ParentClass).GetProperties().Where(c => c.CanRead && c.CanWrite).ToArray();
}
public class ParentClass
{
public string Property1 { get; set; }
public int Property2 { get; set; }
public SomeClassWithObjectProperty ObjectProperty { get; set; }
}
public class SomeClassWithObjectProperty
{
public SomeObjectClass ObjectProperty { get; set; }
}
public class SomeObjectClass
{
public string SomeProperty { get; set; }
}
You can use reflection
//Other Imports...
using System.Reflection;
public PlaylistBox(ListBox In)
{
PropertyInfo[] properties = typeof(ListBox).GetProperties();
foreach (PropertyInfo p in properties)
if (p.CanRead && p.CanWrite)
p.SetMethod.Invoke(this, new object[] { p.GetMethod.Invoke(In, null) });
}
For .NET < 4.5, substitute calls to the GetMethod and SetMethod properties with calls to the GetGetMethod() and GetSetMethod() methods respectively.

Retrieve GrandChild collection from root parent

I have the following object graph:
Root ( Root_Id)
----Child (Child_Id,Root_Id)
-------GrandChild (GrandChild_Id, Child_Id)
And I want to bypass Child and return GrandChild collection having a Root object. So far i have tried this:
var child_Ids = db.Root
.SingleOrDefault( r => r.Root_Id == rootID )
.Childs
.Select( ch => new { Child_Id = ch.Child_Id} ).ToArray();
return db.GrandChilds.Where( gc => child_Ids.Contains( gc.Child_Id ) );
But that wont even compile with the following errors :
1) IEnumerable does not contain a definition for Contains...
2) Argument instance: can not convert from 'AnonymousType # 1 []' to 'System.Linq.IQueryable
How can i accomplish this?
db.Root
.SingleOrDefault( r => r.Root_Id == rootID )
.Childs.SelectMany(ch=>ch.GrandChilds).Distinct()
Use the .SelectMany extension to get the grandchildren collection
Try this
var child_Ids = db.Root
.SingleOrDefault( r => r.Root_Id == rootID )
.Childs
.Select( ch => ch.Child_Id)
.ToArray();
return
from grandChild in db.GrandChild
join child_id in child_Ids
on child_id == grandChild.HandlingUnit_Id
select grandChild;
P.S: I am still a bit unsure about your goal but it looks like working approximation of your original solution
EDIT:
If your hierarchy and classes are something like:
public class Db
{
public Db(IEnumerable<Root> roots)
{ this.Roots = new List<Root>(roots); }
public ICollection<Root> Roots { get; private set; }
}
public class Root
{
public Root(IEnumerable<Child> children )
{
this.Children = new List<Child>(children);
}
public ICollection<Child> Children { get; private set; }
}
public class Child
{
public Child(Int32 childId, Int32 rootId, IEnumerable<GrandChild> grandChildren)
{
this.Child_Id = childId;
this.Root_Id = rootId;
this.GrandChildren = new List<GrandChild>(grandChildren);
}
public Int32 Child_Id { get; private set; }
public Int32 Root_Id { get; private set; }
public ICollection<GrandChild> GrandChildren {get; private set;}
}
public class GrandChild
{
public GrandChild (Int32 grandChildId, Int32 childId)
{
this.GrandChild_Id = grandChildId;
this.Child_Id = childId;
}
public Int32 GrandChild_Id {get; private set;}
public Int32 Child_Id {get; private set;}
}
Then as it was already suggested by AD.NET you could try the SelectMany method
GrandChild gc1 = new GrandChild(1, 10);
GrandChild gc2 = new GrandChild(2, 10);
GrandChild gc3 = new GrandChild(3, 11);
Child c1 = new Child(10, 100, new GrandChild[]{ gc1, gc2 });
Child c2 = new Child(11, 100, new GrandChild[]{ gc3 });
Root r1 = new Root(new Child[]{c1, c2});
Db db = new Db(new Root[] { r1 });
var rootGrandChildren = db
.Roots
.FirstOrDefault()
.Children
.SelectMany(child => child.GrandChildren);
In query syntax it will look like
var rootGrandChildren = from child in db.Roots.FirstOrDefault().Children
from grandChild in child.GrandChildren
select grandChild;
But if your Child class does not know his GrandChildren and they(GrandChildren) are contained in Root:
public class Child
{
public Child(Int32 childId, Int32 rootId)
{
this.Child_Id = childId;
this.Root_Id = rootId;
}
public Int32 Child_Id { get; private set; }
public Int32 Root_Id { get; private set; }
}
public class Root
{
public Root(IEnumerable<Child> children, IEnumerable<GrandChild> grandChildren )
{
this.Children = new List<Child>(children);
this.GrandChildren = new List<GrandChild>(grandChildren );
}
public ICollection<Child> Children { get; private set; }
public ICollection<GrandChild> GrandChildren{ get; private set; }
}
you will have to use:
Root r1 = new Root(new Child[]{c1, c2}, new GrandChild[]{gc1, gc2, gc3});
Db db = new Db(new Root[] { r1 });
Root root = db.Roots.FirstOrDefault();
var rootGrandChildren = from child in root.Children
join grandChild in root.GrandChildren
on child.Child_Id equals grandChild.Child_Id
select grandChild;

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