Reading a space indented List<string> - c#

I have a List where the contents of the list are indented by spaces.
There are two columns (a control type as well as an ID) and the columns are of fixed length.
The indention denotes the parent – child relationship between the elements.
As an example:
Modal 000
Child1 100
Child1 110
Child2 200
Child1 210
Child2 220
Child1 221
Child3 300
To hold the Information I have the following simple classes:
class CustomDefinition
{
public List<CustomControl> Children { get; set; }
}
class CustomControl
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
public List<CustomControl> Children { get; set; }
}
The plan is to have an instance of the CustomDefinition class which will contain the root element whilst the other elements, being children of the root element, will be in the children collection of the root itself. The root element will have a ParentId of 000 whereas the children should receive the id of its parent element.
As the children themselves have children, they are to be placed in their own children collection.
I have an Extension method found here which provides me with the index of the first non-space character, but I have no idea how I should continue:
private static void _TestParse(List<string> items)
{
CustomDefinition definition = new CustomDefinition() {
Children = new List<CustomControl>()
};
int currentControlId = 0;
for (int i = 0; i < items.Count; i++)
{
// the current element
var control = items[i];
// the aforementioned extension method
var controlIndex = control.IndexOf<char>(c => !char.IsWhiteSpace(c));
// fixed length for the first column
var controlName = control.Substring(0, 20).Trim();
// fixed length for the second column
var controlId = control.Substring(20, 3);
int.TryParse(controlId, out currentControlId);
definitions.Children.Add(new CustomControl() {
Id = currentControlId,
Name = controlName
});
// i need to find if the current element has children
// (in the example above the root element has 3 children)
// then add these children to the current element
var currentElement = definitions.Children
.Find(x => x.Id == currentControlId);
// then do this for all children and their children?!
}
}
I do hope this all makes sense and I appreciate any help whatsoever.

If I start with this data:
var data = new []
{
"Modal 000",
" Child1 100",
" Child1 110",
" Child2 200",
" Child1 210",
" Child2 220",
" Child1 221",
" Child3 300",
};
Then I can create a list of raw items like this:
var items =
from x in data
let idText = x.Substring(20)
let indentedText = x
.Substring(0, x.Length - idText.Length)
.TrimEnd()
let name = indentedText.Trim()
let indent = indentedText.Length - name.Length
select new
{
id = int.Parse(idText),
name,
indent,
};
And that gives me:
Now I can use this code to turn it into the desired object model:
var itemDictionary = new Dictionary<int, CustomControl>();
foreach (var item in items)
{
itemDictionary[item.indent] = new CustomControl()
{
Id = item.id,
Name = item.name,
Children = new List<CustomControl>(),
};
if (item.indent != 0)
{
itemDictionary[item.indent].ParentId =
itemDictionary[item.indent - 1].Id;
itemDictionary[item.indent - 1]
.Children.Add(itemDictionary[item.indent]);
}
}
I then get this output:

In the Main method I created sample data, initialized your classes, and performed parsing of the data. There is a recursive method that is responsible for searching for parent of the current node. I had to modify your CustomControl class. Looking at the structure of your IDs in the sample it makes more sens to keep them as strings (revert it to int if you prefer them that way - will only require slight modifications of my code). I also added a property to your CustomControl. It's responsible for remembering the indentation level of the item.
More explanations in the code as comments. Below the code:
static void Main(string[] args)
{
// Create sample data
List<string> items = new List<string>();
items.Add("Modal 000");
items.Add(" Child1 100");
items.Add(" Child1 110");
items.Add(" Child2 200");
items.Add(" Child1 210");
items.Add(" Child2 220");
items.Add(" Child1 221");
items.Add(" Child3 300");
// Initialize the collection object
CustomDefinition customDefinition = new CustomDefinition();
customDefinition.Children = new List<CustomControl>();
// Keep reference to the previous item in the list
CustomControl prevItem = null;
for (int i = 0; i < items.Count; ++i)
{
// Find indentation level
int currentIndent = items[i].TakeWhile(c => char.IsWhiteSpace(c)).Count();
string[] itemIdPair = items[i].Trim().Split(' ').ToArray();
// Create CustomControl from data
CustomControl currentItem = new CustomControl();
// Before split the string is trimmed so name is always first item and ID - last one
currentItem.Id = (itemIdPair[itemIdPair.Length -1]);
currentItem.Name = itemIdPair[0];
currentItem.IndentLevel = currentIndent;
// Add CustomControl reference to the collection
customDefinition.Children.Add(currentItem);
// First item - main node
if (prevItem == null)
{
prevItem = currentItem;
}
// Siblings - get the same parent
else if (currentItem.IndentLevel == prevItem.IndentLevel)
{
CustomControl parent = customDefinition.Children.Where(item => item.Id == prevItem.ParentId).FirstOrDefault();
currentItem.ParentId = parent.Id;
if (parent.Children == null)
parent.Children = new List<CustomControl>();
parent.Children.Add(currentItem);
}
// Child
else if (currentItem.IndentLevel > prevItem.IndentLevel)
{
currentItem.ParentId = prevItem.Id;
if (prevItem.Children == null)
prevItem.Children = new List<CustomControl>();
prevItem.Children.Add(currentItem);
}
// Item's level is higher than the previous item - find how far does it go and
// get the parent for the item
else
{
// parent's indentation will be one level higher than the current one (currentIndent - 1)
// try to find it recursively
// Use the previous node in the list of nodes as the starting point
CustomControl parent = GoBackToLevel(customDefinition.Children, prevItem, currentIndent - 1);
currentItem.ParentId = parent.Id;
if (parent.Children == null)
parent.Children = new List<CustomControl>();
parent.Children.Add(currentItem);
}
// Replace the reference to the previous item.
prevItem = currentItem;
}
}
private static CustomControl GoBackToLevel(List<CustomControl> items, CustomControl start, int parentLevel)
{
// Find a parent of the starting CustomControl using the control's ID.
CustomControl parent = items.Where(item => item.Id == start.ParentId).FirstOrDefault();
// Does the parent has the expected indentation level? Yes - get the parent
// else use the parent as the starting point for search instead.
if (parent.IndentLevel == parentLevel)
return parent;
else
return GoBackToLevel(items, parent, parentLevel);
}
And the modified CustomControl:
class CustomControl
{
public string Id { get; set; }
public string ParentId { get; set; }
public string Name { get; set; }
public List<CustomControl> Children { get; set; }
public int IndentLevel { get; set; }
}

Related

Working with CSV file

I've been working and trying to solve this problem for maybe a whole week, which at this point I am wondering if I can solve it without it diving even deeper into the C# language, and I'm quite fairly new to C#, as well as working with CSV files and sorting and organizing them, so I'm fairly inexperienced into the whole spectrum of this.
I'm trying to sort a CSV file alphabetically, hide items that need to be hidden and have them have depth levels based on their parents, child and grandchild elements.
I've been successful with a couple of them, and written somewhat working code, but I don't know how to sort them alphabetically and give them the proper depth layer based on the parent and child they belong to.
Here's the mockup CSV that I've been trying to organize:
ID;MenuName;ParentID;isHidden;LinkURL
1;Company;NULL;False;/company
2;About Us;1;False;/company/aboutus
3;Mission;1;False;/company/mission
4;Team;2;False;/company/aboutus/team
5;Client 2;10;False;/references/client2
6;Client 1;10;False;/references/client1
7;Client 4;10;True;/references/client4
8;Client 5;10;True;/references/client5
10;References;NULL;False;/references
I've delimited the items by the semicolon, I've displayed the items that needs to be shown, but I fail to sort them like I should.
The sorting should look like this:
Company
About Us
Team
Mission
References
Client 1
Client 2
I've tried to sort them or display them in that order by getting the index of the slash, but what the code reproduces is not how it should be displayed, and, it looks like this:
Company
About Us
Mission
Team
Client 2
Client 1
References
In the other try, where I recursively match their parent id with the id, the console display looks like this:
Company
About Us
Mission
Team
Client 2
Client 1
References
I've tried solving this with a friend, and, even he doesn't know how to approach this problem, since this code should work on a different file that uses different parent ids.
On top of all this, I am unable to index them to an array, because there's only index of 0 or the index is based on their letters or crashes the console if I enter the index position of 1.
Here's the code for the first part where I fail to sort them:
class Program
{
static void Main(string[] args)
{
StreamReader sr = new StreamReader(#"Navigation.csv");
string data = sr.ReadLine();
while (data != null)
{
string[] rows = data.Split(';');
int id;
int parentId;
bool ids = Int32.TryParse(rows[0], out id);
string name = rows[1];
bool pIds = Int32.TryParse(rows[2], out parentId);
string isHidden = rows[3];
string linkUrl = rows[4];
string[] splitted = linkUrl.Split('/');
if (isHidden == "False")
{
List<CsvParentChild> pIdCid = new List<CsvParentChild>()
{
new CsvParentChild(id, parentId, name, linkUrl)
};
}
data = sr.ReadLine();
}
}
}
class CsvParentChild
{
public int Id;
public int ParentId;
public string Name;
public string LinkUrl;
public List<CsvParentChild> Children = new List<CsvParentChild>();
public CsvParentChild(int id, int parentId, string name, string linkUrl)
{
Id = id;
ParentId = parentId;
Name = name;
LinkUrl = linkUrl;
string[] splitted = linkUrl.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (splitted.Length == 1)
{
Console.WriteLine($". { name }");
}
else if (splitted.Length == 2)
{
Console.WriteLine($".... { name }");
}
else if (splitted.Length == 3)
{
Console.WriteLine($"....... { name }");
}
}
}
And here's for the second part:
class Program
{
static void Main(string[] args)
{
// Get the path for the file
const string filePath = #"../../Navigation.csv";
// Read the file
StreamReader sr = new StreamReader(File.OpenRead(filePath));
string data = sr.ReadLine();
while (data != null)
{
string[] rows = data.Split(';');
ListItems lis = new ListItems();
int id;
int parentId;
// Get the rows/columns from the Csv file
bool ids = Int32.TryParse(rows[0], out id);
string name = rows[1];
bool parentIds = Int32.TryParse(rows[2], out parentId);
string isHidden = rows[3];
string linkUrl = rows[4];
// Split the linkUrl so that we get the position of the
// elements based on their slash
string [] splitted = linkUrl.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// If item.isHidden == "False"
// then display the all items whose state is set to false.
// If the item.isHidden == "True", then display the item
// whose state is set to true.
if (isHidden == "False")
{
// Set the items
ListItems.data = new List<ListItems>()
{
new ListItems() { Id = id, Name = name, ParentId = parentId },
};
// Make a new instance of ListItems()
ListItems listItems = new ListItems();
// Loop through the CSV data
for (var i = 0; i < data.Count(); i++)
{
if (splitted.Length == 1)
{
listItems.ListThroughItems(i, i);
}
else if (splitted.Length == 2)
{
listItems.ListThroughItems(i, i);
}
else
{
listItems.ListThroughItems(i, i);
}
}
}
// Break out of infinite loop
data = sr.ReadLine();
}
}
public class ListItems
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public static List<ListItems> data = null;
public List<ListItems> Children = new List<ListItems>();
// http://stackoverflow.com/a/36250045/7826856
public void ListThroughItems(int id, int level)
{
Id = id;
// Match the parent id with the id
List<ListItems> children = data
.Where(p => p.ParentId == id)
.ToList();
foreach (ListItems child in children)
{
string depth = new string('.', level * 4);
Console.WriteLine($".{ depth } { child.Name }");
ListThroughItems(child.Id, level + 1);
}
}
}
}
For each item, you need to construct a kind of "sort array" consisting of ids. The sort array consists of the ids of the item's ancestors in order from most distant to least distant. For "Team", our sort array is [1, 2, 4].
Here are the sort arrays of each item:
[1]
[1, 2]
[1, 3]
[1, 2, 4]
[10, 5]
[10, 6]
[10, 7]
[10, 8]
[10]
Once you have this, sorting the items is simple. When comparing two "sort arrays", start with the numbers in order in each array. If they are different, sort according to the value of the first number and you're done. If they are the same, look at the second number. If there is no second number, then sort by the length of the arrays, i.e., nothing comes before something.
Applying this algorithm, we get:
[1]
[1, 2]
[1, 2, 4]
[1, 3]
[10]
[10, 5]
[10, 6]
[10, 7]
[10, 8]
After that, hide the items based on the flag. I leave that to you because it's so simple. Depth is easy: It's the length of the sort array.
My Application was compiled and produced the following output with your data:
Company
About Us
Team
Mission
References
Client 1
Client 2
Client 4
Client 5
I would attempt to use object relation to create your tree like structure.
The main difficulty with the question is that parents don't matter. Children do.
So at some point in your code, you will need to reverse the hierarchy; Parsing Children first but reading their Parents first to create the output.
The roots of our tree are the data entries without parents.
Parsing
This should be pretty self explanatory, we have a nice class with a constructor that parses the input array and stores the data in it's properties.
We store all the rows in a list. After we are done with this, we pretty much converted the list, but no sorting happened at all.
public partial class csvRow
{
// Your Data
public int Id { get; private set; }
public string MenuName { get; private set; }
public int? ParentId { get; private set; }
public bool isHidden { get; private set; }
public string LinkURL { get; private set; }
public csvRow(string[] arr)
{
Id = Int32.Parse(arr[0]);
MenuName = arr[1];
//Parent Id can be null!
ParentId = ToNullableInt(arr[2]);
isHidden = bool.Parse(arr[3]);
LinkURL = arr[4];
}
private static int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i))
return i;
else
return null;
}
}
static void Main(string[] args)
{
List<csvRow> unsortedRows = new List<csvRow>();
// Read the file
const string filePath = #"Navigation.csv";
StreamReader sr = new StreamReader(File.OpenRead(filePath));
string data = sr.ReadLine();
//Read each line
while (data != null)
{
var dataSplit = data.Split(';');
//We need to avoid parsing the first line.
if (dataSplit[0] != "ID" )
{
csvRow lis = new csvRow(dataSplit);
unsortedRows.Add(lis);
}
// Break out of infinite loop
data = sr.ReadLine();
}
sr.Dispose();
//At this point we got our data in our List<csvRow> unsortedRows
//It's parsed nicely. But we still need to sort it.
//So let's get ourselves the root values. Those are the data entries that don't have a parent.
//Please Note that the main method continues afterwards.
Creating our Tree Strukture and Sorting the items
We start by defining Children and a public ChildrenSorted property that returns them sorted. That's actually allsorting we are doing, it's alot easier to sort than to work recursively.
We also need a function that add's children. It will pretty much filter the input and find all the rows where row.parentId = this.ID.
The last one is the function that defines our output and allows us to get something we can print into the console.
public partial class csvRow
{
private List<csvRow> children = new List<csvRow>();
public List<csvRow> ChildrenSorted
{
get
{
// This is a quite neet way of sorting, isn't it?
//Btw this is all the sorting we are doing, recursion for win!
return children.OrderBy(row => row.MenuName).ToList();
}
}
public void addChildrenFrom(List<csvRow> unsortedRows)
{
// Add's only rows where this is the parent.
this.children.AddRange(unsortedRows.Where(
//Avoid running into null errors
row => row.ParentId.HasValue &&
//Find actualy children
row.ParentId == this.Id &&
//Avoid adding a child twice. This shouldn't be a problem with your data,
//but why not be careful?
!this.children.Any(child => child.Id == row.Id)));
//And this is where the magic happens. We are doing this recursively.
foreach (csvRow child in this.children)
{
child.addChildrenFrom(unsortedRows);
}
}
//Depending on your use case this function should be replaced with something
//that actually makes sense for your business logic, it's an example on
//how to read from a recursiv structure.
public List<string> FamilyTree
{
get
{
List<string> myFamily = new List<string>();
myFamily.Add(this.MenuName);
//Merges the Trees with itself as root.
foreach (csvRow child in this.ChildrenSorted)
{
foreach (string familyMember in child.FamilyTree)
{
//Adds a tab for all children, grandchildren etc.
myFamily.Add("\t" + familyMember);
}
}
return myFamily;
}
}
}
Adding Items to the Tree and accessing them
This is the second part of my main function, where we actually work with our data (Right after sr.Dispose();)
var roots = unsortedRows.Where(row => row.ParentId.HasValue == false).
OrderBy(root => root.MenuName).ToList();
foreach (csvRow root in roots)
{
root.addChildrenFrom(unsortedRows);
}
foreach (csvRow root in roots)
{
foreach (string FamilyMember in root.FamilyTree)
{
Console.WriteLine(FamilyMember);
}
}
Console.Read();
}
Entire Sourcecode (Visual Studio C# Console Application)
You can use this to test, play around and learn more about recursive structures.
Copyright 2017 Eldar Kersebaum
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication49
{
class Program
{
static void Main(string[] args)
{
List<csvRow> unsortedRows = new List<csvRow>();
const string filePath = #"Navigation.csv";
StreamReader sr = new StreamReader(File.OpenRead(filePath));
string data = sr.ReadLine();
while (data != null)
{
var dataSplit = data.Split(';');
//We need to avoid parsing the first line.
if (dataSplit[0] != "ID" )
{
csvRow lis = new csvRow(dataSplit);
unsortedRows.Add(lis);
}
// Break out of infinite loop
data = sr.ReadLine();
}
sr.Dispose();
var roots = unsortedRows.Where(row => row.ParentId.HasValue == false).
OrderBy(root => root.MenuName).ToList();
foreach (csvRow root in roots)
{
root.addChildrenFrom(unsortedRows);
}
foreach (csvRow root in roots)
{
foreach (string FamilyMember in root.FamilyTree)
{
Console.WriteLine(FamilyMember);
}
}
Console.Read();
}
}
public partial class csvRow
{
// Your Data
public int Id { get; private set; }
public string MenuName { get; private set; }
public int? ParentId { get; private set; }
public bool isHidden { get; private set; }
public string LinkURL { get; private set; }
public csvRow(string[] arr)
{
Id = Int32.Parse(arr[0]);
MenuName = arr[1];
ParentId = ToNullableInt(arr[2]);
isHidden = bool.Parse(arr[3]);
LinkURL = arr[4];
}
private static int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i))
return i;
else
return null;
}
private List<csvRow> children = new List<csvRow>();
public List<csvRow> ChildrenSorted
{
get
{
return children.OrderBy(row => row.MenuName).ToList();
}
}
public void addChildrenFrom(List<csvRow> unsortedRows)
{
this.children.AddRange(unsortedRows.Where(
row => row.ParentId.HasValue &&
row.ParentId == this.Id &&
!this.children.Any(child => child.Id == row.Id)));
foreach (csvRow child in this.children)
{
child.addChildrenFrom(unsortedRows);
}
}
public List<string> FamilyTree
{
get
{
List<string> myFamily = new List<string>();
myFamily.Add(this.MenuName);
foreach (csvRow child in this.ChildrenSorted)
{
foreach (string familyMember in child.FamilyTree)
{
myFamily.Add("\t" + familyMember);
}
}
return myFamily;
}
}
}
}

Get parent department node in Entity Framework

I have a SQL table like this:
DepartmentID is parent of department. I've build a tree by this table(in ASP.net (C#) project):
Records in tree above is:
I need to get parents in this tree.
I can do it in SQL Server like this(for Example id=2, id is input argument):
with cte1
as
(
select id,name,DepartmentID, 0 AS level
from Department
where id =2
union all
select Department.ID,Department.name,Department.DepartmentID, level+1
from Department
inner join cte1 on Department.ID=cte1.DepartmentID
)
select * from cte1
Output(id=2 (A))
Output(id=4 (A1))
I know EF does not support cte, but I need to get this result in EF.
It would be very helpful if someone could explain solution for this problem.
These posts are similar to your question.please see these:
writing-recursive-cte-using-entity-framework-fluent-syntax-or-inline-syntax
converting-sql-statement-that-contains-with-cte-to-linq
I think there is no way to write a single LINQ to SQL query that could get all However, LINQ supports a method to execute a query (strangly enough called DataContext.ExecuteQuery). Looks like you can use that to call a arbitrary piece of SQL and map it back to LINQ.
See this post:
common-table-expression-in-entityframework
The easiest way I can think of is to map the relationship in EF and then retrieve all departments and then get the root parent from that list. All of them should be loaded in memory and EF will take care of the tree structure with the mapping. Alternatively you can enable lazy loading and just get the parent but then with each child or childset a query will be executed by EF during retrieval.
Model
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int? DepartmentId { get; set; }
public Department ParentDepartment { get; set; }
public virtual ICollection<Department> ChildDepartments { get; set; }
}
Mapping (using fluent)
public DbSet<Department> Departments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// other mapping code
modelBuilder.Entity<Department>()
.HasOptional(x => x.ParentDepartment)
.WithMany(x => x.ChildDepartments)
.HasForeignKey(x => x.DepartmentId);
// other mapping code
}
Eager retrieval of root parent
using (var context = new YourDbContext())
{
var allDepartments = context.Departments.ToList(); // eagerly return everything
var rootDepartment = allDepartments.Single(x => x.DepartmentId == null);
}
Retrieval of only root parent and then use lazy loading, note that the DbContext needs to be available for Lazy Loading to work and it must also be enabled on the DbContext
using (var context = new YourDbContext())
{
var rootDepartment = context.Departments.Single(x => x.DepartmentId == null);
// do other stuff, as soon as context is disposed you cant lazy load anymore
}
Try one of these,
1-
int _ID = 2; // ID criteria
List<object> result = new List<object>(); // we will use this to split parent at child, it is object type because we need Level
var departments = entites.Departments.Where(x => x.ID == _ID).SelectMany(t => entites.Departments.Where(f => f.ID == t.DepartmentID),
(child, parent) => new { departmentID = child.DepartmentID, Name = child.Name, ID = child.ID, level = 0,
Parent = new { DepartmentID = parent.DepartmentID, Name = parent.Name, ID = parent.ID, level = 1 }});
// first we check our ID (we take A from where criteria), then with selectmany T represents the Department A, we need
// department A's departmentID to find its parent, so another where criteria that checks ID == DepartmentID, so we got T and the new list
// basically child from first where parent from second where, and object created.
// for showing the results
foreach (var item in departments)
{
result.Add(new { DepartmentID = item.departmentID,ID = item.ID, level= item.level,Name = item.Name}); // child added to list
result.Add(new { DepartmentID = item.Parent.DepartmentID, ID = item.Parent.ID, level = item.Parent.level, Name = item.Parent.Name }); // parent added to list
}
Result;
2-
List<object> childParent = new List<object>();
// basically get the child first
Departments child1 = entites.Departments.Where(x => x.ID == _ID).FirstOrDefault();
// find parent with child object
Departments parent1 = entites.Departments.Where(x => x.ID == child1.DepartmentID).FirstOrDefault();
// create child object with level
childParent.Add(new { child1.DepartmentID, child1.ID,child1.Name , level = 0});
// create parent object with level
childParent.Add(new { parent1.DepartmentID,parent1.ID,parent1.Name, level = 1 });
Result (not the same image, check column Header Text);
Edit 1:
3-
Another way, by giving ID as input and assuming that ID column is unique, so there will be always 2 values at the array and by returning list, the index of items actually represent their levels. (won't add results because they are same :)).Btw you can also use Union instead of Concat.
var ress = list.Where(x=> x.ID ==2)
.SelectMany(x=> list.Where(c=> c.ID == x.ID).Concat(list.Where(s => s.ID == x.DepartmentID))).ToList();
DataTable dt = new DataTable();
dt.Columns.Add("DepartmentID");
dt.Columns.Add("ID");
dt.Columns.Add("Name");
dt.Columns.Add("Level");
for (int i = 0; i < ress.Count(); i++)
{
dt.Rows.Add(ress[i].DepartmentID, ress[i].ID, ress[i].Name, i);
}
dataGridView1.DataSource = dt;
Edit 2
There is not cte in linq, basically using view,sp is the first choise but here is a solution, it might be a little push. Anyway it gives the result.
List<Departments> childParent = new List<Departments>();
// or basically get the child first
Departments child1 = entites.Departments.Where(x => x.ID == 7).FirstOrDefault();
// find parent with child object
Departments parent1 = entites.Departments.Where(x => x.ID == child1.DepartmentID).FirstOrDefault();
// create child object with level
Departments dep = new Departments(); // I add to department class a string level field
dep.DepartmentID = child1.DepartmentID;
dep.ID = child1.ID;
dep.Name = child1.Name;
dep.level = 0; // first item
childParent.Add(dep);
// create parent object with level
dep = new Departments();
dep.DepartmentID = parent1.DepartmentID;
dep.ID = parent1.ID;
dep.Name = parent1.Name;
dep.level = 1; // parent one
childParent.Add(dep);
while (childParent.Select(t => t.DepartmentID).Last() != null) // after added to list now we always check the last one if it's departmentID is null, if null we need to stop searching list for another parent
{
int? lastDepID = childParent.Last().DepartmentID; // get last departmentID
Departments tempDep = entites.Departments.Single(x => x.ID == lastDepID); // find as object
tempDep.level = childParent.Last().level + 1; // increase last level
childParent.Add(tempDep); // add to list
}
(Added another C1 to check 4th level)
Hope helps,
Below is the simple console project Program class code.
You can check with different IDs for the input parameter of the GetParentSet method.
class Program
{
static void Main(string[] args)
{
Program p = new Program();
var result= p.GetParentSet(6);
foreach(var a in result)
{
Console.WriteLine(string.Format("{0} {1} {2}",a.ID,a.Name,a.DepartmentId));
}
Console.Read();
}
private List<Department> GetParentSet(int id)
{
List<Department> result = new List<Department>(); //Result set
using (RamzDBEntities context = new RamzDBEntities())
{
var nodeList = context.Departments.Where(t=>t.ID<=id).ToList(); //Get All the the entries where ID is below or greater than the given to the list
var item = nodeList.Where(a => a.ID == id).SingleOrDefault(); //Get the default item for the given ID
result.Add(item); //Add it to the list. This will be the leaf of the tree
int size = nodeList.Count(); //Get the nodes count
for (int i = size; i >= 1;i--)
{
var newItem= nodeList.Where(j => j.ID == item.DepartmentId).SingleOrDefault(); //Get the immediate parent. This can be done by matching the leaf Department ID against the parent ID
if (item!=null && !result.Contains(newItem)) //If the selcted immediate parent item is not null and it is not alreday in the list
{
result.Add(newItem); //Add immediate parent item to the list
}
if (newItem.ID == 1) //If the immediate parent item ID is 1 that means we have reached the root of the tree and no need to iterate any more.
break;
item = newItem; //If the immediate parent item ID is not 1 that means there are more iterations. Se the immediate parent as the leaf and continue the loop to find its parent
}
}
return result; //return the result set
}
}
Code itself is self-explanatory. However below is the explanation. Hope this will help!
First all the entries with ID below or equal to the given ID is
assigned to a List
Then get the leaf of the tree and add it to the list named result. This is the first element of our result set
We iterate through the retrieved entries descending order. Get the immediate parent of the leaf by equating parent's ID to leaf's department ID
If this immediate parent is not null and its not already in the list add it to the list.
Make the immediate parent item as the leaf and continue the loop so that we can get the parent of the immediate parent.
continue this until we reach the root of the tree.
If the immediate parent ID is=1 that means we have reached the root of the tree and we can break the loop.
Since you generated the edmx, you have the code generated for your DbContext and for your Model Classes including Departments like on this screenshot.
You shouldn't modify them because they might (will) get overwritten by EF tools anyway on any model manipulation. Fortunately both classes are generated as partial so the creators thought about people wanting to customize it safely.
Example below is made for simplicity of implementation not for top performance. I assumed that the table containing Departments is not enormously big and the levels of nesting in hierarchy are not enormously deep.
Create a new Class (*.cs file) in your project and extend your auto-generated Departments class by your custom method or property:
using System;
using System.Collections.Generic;
using System.Linq;
namespace CustomEF.EFStuff
{
public partial class Departments
{
public List<Departments> Hierarchy {
get {
List<Departments> retVal = new List<Departments>();
retVal.Add(this);
using (YourAutoGeneratedContext ctx = new YourAutoGeneratedContext())
{
Departments tmp = this;
while(tmp.DepartmentID != null)
{
tmp = ctx.Departments.First(d => d.ID == tmp.DepartmentID);
retVal.Add(tmp);
}
}
return retVal;
}
private set { }
}
}
}
When you extend the partial class, make sure that you put it in the same namespace. In my case I named my project CustomEF and I've placed the edmx file in the EFStuff subfolder so the generator placed the auto generated class in the CustomEF.EFStuff namespace.
The example above will allow you to get the hierarchy for any Departments object e.g.
int level = 0;
foreach(Departments d in someDepartmentObject.Hierarchy)
{
Console.WriteLine(d.ID.ToString() + ", " + d.DepartmentID.ToString() + ", " + d.Name +", " +(level++).ToString());
}
If you also need to get the hierarchy from some code where you have an ID but not the object, you can additionally create another class (*.cs file) where you'll extend the auto-generated context.
using System.Collections.Generic;
using System.Linq;
namespace CustomEF.EFStuff
{
public partial class YourAutoGeneratedContext
{
public List<Departments> GetDepartmentHierarchy(int departmentId)
{
Departments mydep = this.Departments.FirstOrDefault(d => d.ID == departmentId);
if (mydep == null)
{
throw new System.Data.Entity.Core.ObjectNotFoundException("There is no department with ID = " + departmentId.ToString());
}
return mydep.Hierarchy;
}
}
}
Or in this case you might want to move the implementation to the Context class entirely, without extending the Departments class at all (and you wouldn't have to create an additional instance of your context, you'll have the this to use).
using System.Collections.Generic;
using System.Linq;
namespace CustomEF.EFStuff
{
public partial class YourAutoGeneratedContext
{
public List<Departments> GetDepartmentHierarchy(int departmentId)
{
Departments tmp = this.Departments.FirstOrDefault(d => d.ID == departmentId);
if (tmp == null)
{
throw new System.Data.Entity.Core.ObjectNotFoundException("There is no department with ID = " + departmentId.ToString());
}
List<Departments> retVal = new List<Departments>();
retVal.Add(tmp);
while (tmp.DepartmentID != null)
{
tmp = this.Departments.First(d => d.ID == tmp.DepartmentID);
retVal.Add(tmp);
}
return retVal;
}
}
}
As another unsophisticated use example:
YourAutoGeneratedContext ctx = new YourAutoGeneratedContext();
level = 0;
foreach (Departments currentHier in ctx.GetDepartmentHierarchy(10))
{
Console.WriteLine(currentHier.ID.ToString() + ", " + currentHier.DepartmentID.ToString() + ", " + currentHier.Name + ", " + (level++).ToString());
}
I don't know how much you can trust the data in the database. You might want to implement some checks including cross-referencing departments to prevent infinite loop.
Note that formally the term 'to extend a class' may apply to extension methods rather then to partial classes. I used this word from lack of better one. Extension methods would be something that you might want to use if, for some reason, you'd need your method/property returning EF native DbSet<> instead of the List<>. In such case you might want to take look at: https://shelakel.co.za/entity-framework-repository-pattern/
Example in EF6 to get all parents up to root node.
public class Department
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Department Parent { get; set; }
public virtual ICollection<Department> Children { get; set; }
private IList<Department> allParentsList = new List<Department>();
public IEnumerable<Department> AllParents()
{
var parent = Parent;
while (!(parent is null))
{
allParentsList.Add(parent);
parent = parent.Parent;
}
return allParentsList;
}
}
use include keyword.
_context.Invoices.Include(x => x.Users).Include(x => x.Food).ToList();

Handling hierarchical collection with Linq

I have a hierarchical menu. The menu items looks like this :
public struct MenuElement
{
public int Id {get; set;}
public int Position {get; set;}
public string Label {get; set;}
public int HierarchicalLevel {get; set;}
public int ParentLevelId {get; set;}
public bool IsMandatory {get; set;}
}
The structure of my menu is managed with a node class :
public class Node<T>
{
public T Item {get; set;}
public IEnumerable<Node<T>> ChildrenNodes {get; set;}
public int HierarchicalLevel {get; set;}
}
The menu items are retrieved from database. So, when I build my menu, and I browse my menu :
// Get the menu items from database
List<MenuElement> flattenMenuElements = GetMenuItemsFromDb();
// Build the hierarchical menu with relationships between nodes (parentId, ChildrenNodes...)
List<Node<MenuElement>> hierarchicalMenu = BuildMenu(flattenMenuElements);
foreach(var node in Browse(hierarchicalMenu))
{
string space = ""
for(int i=0; i<node.HierarchicalLevel; i++)
space = String.Concat(space, " ");
Console.Writeline("{0}{1}. {2}",space, node.Item.Position, node.Item.Label);
}
//Browse method
IEnumerable<Node<MenuElement>> Browse(IEnumerable<Node<MenuElement>> nodes)
{
foreach(var node in nodes)
{
yield return node;
foreach (var childNode in Browse(node.ChildrenNodes))
{
yield return childNode;
}
}
}
The Console output :
1. LabelMenu1
1. LabelMenu11
1. LabelMenu111
2. LabelMenu112
3. LabelMenu113
2. LabelMenu12
3. LabelMenu13
2. LabelMenu2
3. LabelMenu3
1. LabelMenu31
...
So the result is what I expect. But now I want to get only MenuElement with the property IsMandatory == false AND THEIR PARENTS (and their grand-parents etc). For example, In the menu above, only
the LabelMenu112 and LabelMenu31 has the property IsMandatory set to false. So I want if I browse my menu the output result will be like this :
1. LabelMenu1
1. LabelMenu11
2. LabelMenu112
3. LabelMenu3
1. LabelMenu31
Actually, to do this, I rebuild a second menu from the first hierarchical menu after filtering on menu elements I want to keep :
// Get the menu elements from database
List<MenuElement> flattenMenuElements = GetMenuItemsFromDb();
// Build the hierarchical menu with relationships between nodes (parentId, ChildrenNodes...)
List<Node<MenuElement>> hierarchicalMenu = BuildMenu(flattenMenuElements);
// Get a flat list of menu elements where the property IsMandatory is set to false
List<MenuElement> filteredMenuElements = flattenMenuElements.Where(m => m.IsMandatory == false).ToList();
// Get a flat list of filtered menu elements AND THEIR PARENTS, GRAND PARENTS etc
List<MenuElement> filteredMenuElementsWithParents = GetMenuElementsWithParents(hierarchicalMenu, filteredMenuElements).ToList();
List<MenuElement> GetMenuElementsWithParents(IEnumerable<Node<MenuElement>> hierarchicalMenu, IEnumerable<MenuElement> filteredMenuElements)
{
List<MenuElement> menu = new List<MenuElement>();
foreach (var item in filteredMenuElements)
{
menu.Add(item);
AddParentNode(item, menu, hierarchicalMenu);
}
}
void AddParentNode(MenuElement element, List<MenuElement> menu, IEnumerable<Node<MenuElement>> hierarchicalMenu)
{
if (element.ParentLevelId != default(int))
{
// Get the parent node of element
MenuElement menuEl = Browse(hierarchicalMenu)
.Where(node => node.Item.Id == element.ParentLevelId)
.Select(node => node.Item)
.First();
if(!menu.Contains(menuEl))
menu.Add(menuEl);
AddParentNode(menuEl, menu, hierarchicalMenu);
}
}
This solution works but I wonder if it's note possible to use the power of linq to retrieve this filtered menu elements and their parents rather than build a flat list, then build a second hierarchical menu from the flat list.
Is there a way to do this using Linq ?
Thank you !
Regards,
Florian
It would be easier to use this Node class.
Node<MenuElement> rootNode = <Any node of the collection>.Root;
var mandatoryNodes = rootNode.SelfAndDescendants.Where(n => n.Value.IsMandatory);
foreach (var mandatoryNode in mandatoryNodes)
{
foreach (var node in mandatoryNode.SelfAndAncestors.Reverse()) // Reverse because you want from root to node
{
var spaces = string.Empty.PadLeft(node.Level); // Replaces: for(int i=0; i<node.HierarchicalLevel; i++) space = String.Concat(space, " ");
Console.WriteLine("{0}{1}. {2}", spaces, node.Value.Position, node.Value.Label);
}
}

Counting descendants in a tree

I have the following class which recurs on itself to form a tree-like data structure:
public class chartObject
{
public string name { get; set; }
public int descendants { get; set; }
public List<chartObject> children { get; set; }
}
For each object in the tree I would like to populate the descendant property with the amount objects that exist underneath it.
Example structure:
chartObject1 (descendants: 4)
└-chartObject2 (descendants: 0)
└-chartObject3 (descendants: 2)
└--chartObject4 (descendants: 1)
└---chartObject5 (descendants: 0)
What would be the most efficient way of doing this?
How about the recursive formula:
children.Count + children.Sum(c => c.descendants)
This is suitable for eager-evaluation / caching if the tree is immutable (which it isn't from the class declaration). If you want efficiency even in the face of mutability, you'll find this a lot more difficult; you can consider marking parts of the tree "dirty" as it is mutated / eagerly force the re-evalutation of this metric to "bubble up" as part of a tree is mutated.
This works for me:
public void SetDescendants(chartObject current)
{
foreach (var child in current.children)
{
SetDescendants(child);
}
current.descendants = current.children.Sum(x => 1 + x.descendants);
}
I tested with this code:
var co = new chartObject()
{
name = "chartObject1",
children = new List<chartObject>()
{
new chartObject()
{
name = "chartObject2",
children = new List<chartObject>() { }
},
new chartObject()
{
name = "chartObject3",
children = new List<chartObject>()
{
new chartObject()
{
name = "chartObject4",
children = new List<chartObject>()
{
new chartObject()
{
name = "chartObject5",
children = new List<chartObject>() { }
}
}
}
}
}
}
};
And got this as the result:
For calculations to be most efficient, cache their result in the node itself. Otherwise, you'll be re-calculating the count every time the descendants property is looked up.
The cost of doing that is the need to invalidate the cache all the way up the parent chain, like this:
public class chartObject
{
private chartObject _parent;
private int? _descCache = null;
public string name { get; set; }
public int descendants {
get {
return _descCache ?? calcDescendents();
}
}
public List<chartObject> children { get; set; }
public void AddChild(chartObject child) {
child._parent = this;
children.Add(child);
chartObject tmp = this;
while (tmp != null) {
tmp._descCache = null;
tmp = tmp._parent;
}
}
private int calcDescendents() {
return children.Count+children.Sum(child => child.descendants);
}
}
Walk all nodes of the tree (depth first is ok) and when done with children set "descendants property to sum of children's descendants + child count. You have to do it on every change to the tree structure. You should be able to limit updates only to parents of element that is changed.
If nodes made immutable you can populate the field at creation time.
Side notes:
Your tree is mutable as it is now (one can easily add more child nodes anywhere), so it may be safer to have method that counts descendants instead of property on a node.
Having computed property int descendants { get; set; } to be read/write is confusing as anyone can set its value to whatever number. Consider if making it read only and updating when one of child nodes changes (requires some custom notification mechanism).
Code style - consider naming classes with upper case names for code that is intended to be public (follow Microsoft's C# coding guidelines). chartObject -> ChartObject

Treeview with parent/child issue loading by status

I have the following table structure it might not be a correct structure but unfortunatly that's what I was given.
id | Name | Parent | Status
1 First 0 Active
2 Child 1 Active
3 2Child 2 Inactive
Logic:
Load Root by Parent = 0 and Status
OnPopulate load child by parent ID and status for every levels after root
my issue is if the status is "Inactive" and I want to see what options are inactive I can't because the first 2 options are active. What I need is to be able to view in my treeview all the levels down to the option that is Inactive or Active.
I have tried the following sql statement
select distinct
m.Id,
m.Name,
m.Parent,
m.[Status]
from mytable m
where m.Parent = 3 and m.[Status] = 'I'
union
select
Id,
Name,
Parent,
[Status]
from mytable
where ID in(select distinct
o.ID
from mytable o
where o.ID = 3 and o.[Status] = 'I') and Parent = 3
I have ran out of ideas in sql and coding to figure this out..hope someone could guide me in the right direction..thanks
Also tried this in code:
protected void mytree_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
//this is just a class that loads the values from db
MYList templist = new ListSyFamily();
templist.LoadAll();//(ddlStatus.SelectedValue, Convert.ToInt32(e.Node.Value));
foreach (temp temp in templist)
{
if (temp.Status == ddlStatus.SelectedValue && temp.Parent == Convert.ToInt32(e.Node.Value))
{
TreeNode child = new TreeNode();
child.Text = temp.Description;
child.Value = temp.Id.ToString();
if (child.ChildNodes.Count == 0)
child.PopulateOnDemand = true;
child.ToolTip = "Ver sub-opciones";
//child.SelectAction = TreeNodeSelectAction.SelectExpand;
child.CollapseAll();
e.Node.ChildNodes.Add(child);
}
}
}
Here is how we handle this.
Assume that you have a class called MyRecord to hold each row of data from the DB:
public class MyRecord
{
public int Id {get; set; }
public int ParentId {get; set; }
public string Name { get; set; }
public string Status { get; set; }
// The children of this node
public MyRecordCollection Children = new MyRecordCollection();
}
Then you have a collection type to hold these records indexed by their id:
public class MyRecordCollection : System.Collections.Generic.Dictionary<int, MyRecord>
{
}
Here is the code (retrieval from the DB not shown) to preprocess the records and then add them to the tree:
MyRecordCollection cAllRecords;
MyRecordCollection cParentRecords = new MyRecordCollection();
// This is a method that just loads the records
cAllRecords = LoadAllRecords();
// Cycle through each of the records
foreach (MyRecord oRecord in cAllRecords.Values)
{
if (oRecord.Id == 0)
{
// If the record is a parent record, add it to the list of parents
cParentRecords.Add(oRecord.Id, oRecord);
}
else
{
// Otherwise, add the current record to its parent's list of children
cAllRecords[oRecord.ParentId].Children.Add(oRecord.Id, oRecord);
}
}
AddNodesToTree(cParentRecords, this.treeView1.Nodes);
And finally, the recursive method for adding the records to the tree:
/// <summary>
/// A recursive method to add all of the records to the specified collection of nodes
/// </summary>
/// <param name="cRecords"></param>
/// <param name="cNodes"></param>
private void AddNodesToTree(MyRecordCollection cRecords, TreeNodeCollection cNodes)
{
foreach (MyRecord oRecord in cRecords.Values)
{
TreeNode oNode = new TreeNode();
oNode.Text = oRecord.Name;
oNode.Tag = oRecord;
cNodes.Add(oNode);
// Now add the node's children if any
if (oRecord.Children.Count != 0)
{
AddNodesToTree(oRecord.Children, oNode.Nodes);
}
}
}
Well, If I were you I would just load the entire table into memory into a simple Collection of a light DTO class and work out your treeview in C#. That seems a lot easier than to try a lot of options in SQL.

Categories

Resources