Generating a directory folder structure from database - c#

Finding myself in loopie loops. I have a database table that defines a directory folder structure with potentially infinite number of subfolders.
The end result folder structure should look like this, but the logic should allow this structure requirement to change:
Given this data for the above folder structure:
The most important fields are id and pid. pid of NULL represents the top level folders (Email, TM Applications, TM Disputes). All other folders are subfolders which go down 3 levels stored in level_count field. Not sure I really need level_count field though. I've been trying to make the logic as "flexible" as possible. pid defines a folder's immediate parent:
My current solution is not good enough because it doesn't handle the "infinite" number of levels, it only supports three levels. I prefer not to have to know the number of levels.
I want to be able to keep the core logic if possible and I do not want to change this in a way that creates all parent folders first, and then goes back to create subfolders. Instead I want to go down the deepest level, create those folders, then back up to parents. I think this code represents that idea, if I'm making sense.
foreach (DataRow r in dtParentFolders.Rows) // these are the 3 parent rows with null pid
{
int parentFolderId = Convert.ToInt32(r["id"]);
string parentFolderName = r["folder_name"].ToString();
//Create folder
Console.WriteLine(parentFolderName);
DataTable dt = GetFolders(parentFolderId);
foreach (DataRow r2 in dt.Rows)
{
parentFolderId = Convert.ToInt32(r2["id"]);
CreateFolder(r2);
dt = GetFolders(parentFolderId);
foreach (DataRow r3 in dt.Rows)
{
parentFolderId = Convert.ToInt32(r3["id"]);
CreateFolder(r3);
dt = GetFolders(parentFolderId);
}
}
}

I hope this can help you in some way.
public class Record
{
public int Id { get; set; }
public int PId { get; set; }
public string Name { get; set; }
}
public static void Main()
{
var records = new List<Record>()
{
new Record { Id = 1, Name = "MainDir1", PId = 0 },
new Record { Id = 2, Name = "MainDir2", PId = 0 },
new Record { Id = 3, Name = "MainDir3", PId = 0 },
new Record { Id = 4, Name = "SubDir1", PId = 1 },
new Record { Id = 5, Name = "SubDir2", PId = 2 },
new Record { Id = 6, Name = "SubSubDir1", PId = 5 },
new Record { Id = 7, Name = "SubSubDir2", PId = 5 },
new Record { Id = 8, Name = "SubSubDir3", PId = 5 },
new Record { Id = 9, Name = "SubSubDir4", PId = 5 },
new Record { Id = 10, Name = "SubSubDir5", PId = 5 },
};
var node = new Directory(0, null, null);
records
.OrderBy(x => x.PId)
.ThenBy(x => x.Id)
.ThenBy(x => x.Name)
.ToList()
.ForEach(x => node.AddChild(x.Name, x.Id, x.PId));
node.Print();
}
public class Directory
{
public Directory(int id, string name, Directory parent)
{
this.Id = id;
this.Name = name;
this.Parent = parent;
this.Indentation = parent is null ? 0 : parent.Indentation + 1;
this.Children = new HashSet<Directory>();
}
public int Id { get; set; }
public int Indentation { get; set; }
public string Name { get; set; }
public Directory Parent { get; set; }
public ICollection<Directory> Children { get; set; }
public void AddChild (string name, int id, int parentId)
{
if (this.Id == parentId)
{
this.Children.Add(new Directory(id, name, this));
return;
}
foreach (var child in this.Children)
{
child.AddChild(name, id, parentId);
}
}
public void Print()
{
Console.WriteLine($"{new string(' ', this.Indentation * 4)}{this.Name}");
foreach (var child in this.Children)
{
child.Print();
}
}
}

Related

How to search Hierarchical Data with Linq in list

Distributor Registration.
I want to fill list with following information about the distributor
Id
ParentId
Name
For each distributor, it must be determined on whose recommendation the distributor is registered in the system. This can be anyone already registered in the system - in which case they must be selected from a list of already registered distributors, or the referrer information can be blank, which means that the distributor registers in the system without a recommender. Each distributor can bring up to three people on its recommendation, the system should ensure that no more than three people are registered "under" the same distributor. Also, the depth of the hierarchy of the mentioned distributors should be maximum 5 - that is, the distributor can bring a person with a recommendation, who will also bring a person with his recommendation, etc. Maximum 5 levels. Accordingly, in such a group there can be a total of 1 + 3 + 9 + 27 + 81 = 121 people. The system must provide control over the given levels.
You can use recursion to find the depth of any element in the list and a plain old count to find the number of referrers.
The following code is an implementation of that idea.
void Main()
{
var distributors = new List<Distributor> {
new Distributor { Id = 1, ParentId = 0, Name = "A" },
new Distributor { Id = 7, ParentId = 1, Name = "B" },
new Distributor { Id = 9, ParentId = 7, Name = "C" },
new Distributor { Id = 13, ParentId = 9, Name = "D" },
new Distributor { Id = 28, ParentId = 13, Name = "E" },
};
var valid = IsValidToAdd(distributors, 9);
Console.WriteLine(valid);
}
public bool IsValidToAdd(List<Distributor> distributors, int parentId)
{
var referCount = distributors.Count(d => d.ParentId == parentId);
Console.WriteLine($"refer:{referCount}");
if (referCount == 3)
{
Console.WriteLine("There are already 3 referals for this parent");
return false;
}
var level = GetLevel(distributors, parentId);
Console.WriteLine($"level: {level}");
if (level > 5)
{
Console.WriteLine("There are already 5 levels of referals");
return false;
}
return true;
}
public int GetLevel(List<Distributor> distributors, int parentId)
{
var parent = distributors.FirstOrDefault(d => d.Id == parentId);
if (parent == null)
{
return 1;
}
return 1 + GetLevel(distributors, parent.ParentId);
}
public class Distributor
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}

Best way to remove elements from list field of list objects

I have the objects model the File and User:
public class File
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public uint Size { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<File> Files { get; set; }
}
I have created the list of objects of the model:
var users = new List<User> { new User
{
Id = 1,
Name = "Alex",
Files = new List<File>
{
new File
{
Id = 1,
Name = "123.txt",
Size = 20000
}
new File
{
Id = 3,
Name = "111.txt",
Size = 10
}
}
},
new User
{
Id = 2,
Name = "Andry",
Files = new List<File>
{
new File
{
Id = 1,
Name = "file.txt",
Size = 3
},
new File
{
Id = 2,
Name = "file.mp3",
Size = 4342
}
}
},
new User
{
Id = 3,
Name = "Jon",
Files = new List<File>
{
new File
{
Id = 1,
Name = "site.txt",
Size = 3324
}
}
},
};
The field with name - Files of object user list contains list of objects File.
How do remove files of users with Size <= 10 ?
This way is not working:
users.SelectMany(u => u.Files).ToList().RemoveAll(f => f.Size <= 10);
LINQ is a technology to query sources, not to modify them. So you should not use a query and change something in this query. Instead you can filter what you need to modify and then use a loop to actually apply the changes.
IEnumerable<User> usersWithSmallFiles = users
.Where(u => u.Files.Any(f => f.Size <= 10));
foreach (User u in usersWithSmallFiles)
u.Files = u.Files.Where(f => f.Size > 10).ToList();
You also can't use RemoveAll on IEnumerable<T>, it's a List<T>-method. That's why you have used ToList() in the query. But on this way you create a new list which is not related to your Files-list, it just contains the same files but the collection is different, so RemoveAll is pointless.

How would I search through all nested children of a custom class?

I have a table in a database which looks like this:
| id | parentID | name |
|----------+----------+-------------|
|ABCD-12345| | Top |
|----------+----------+-------------|
|ABCD-23456|ABCD-12345| Middle |
|----------+----------+-------------|
|ABCD-34567|ABCD-23456| Bottom |
|----------+----------+-------------|
|ABCD-45678|ABCD-23456| Bottom |
etc. - Basically, a hierarchical structure of N depth. I've taken this and shoved it into a datatable.
I have the following class built to hold this data:
public class TreeNode
{
public string id { get; set; }
public string name { get; set; }
public string parentID { get; set; }
public List<TreeNode> children { get; set; }
}
My goal is to go through each of these DataTable rows and insert them into the appropriate location in the TreeNode structure, but I'm super confused as to how I should approach this.
The main point of confusion for me is how I search through the entire existing structure of TreeNodes to see if a node with the parentID exists. Can anyone point me in the right direction?
I tried the following code, but it doesn't work:
public List<TreeNode> BuildTree(int currNode, List<TreeNode> treeList, DataTable dt)
{
foreach(DataRow row in dt.Rows)
{
if(row[1].ToString() == treeList[currNode].id)
{
treeList[currNode].children.Add(new TreeNode
{
id = row[0].ToString(),
name = row[2].ToString(),
parentID = row[1].ToString()
});
dt.Rows.Remove(row);
if(dt.Rows.Count > 0)
{
currNode++;
BuildTree(currNode, treeList, dt);
}
else
{
return treeList;
}
}
}
return null;
}
The problem is this line:
if(row[1].ToString() == treeList[currNode].id)
which gets an out of range exception, because I have a root at index 0, so on the second run (when currNode is 1), it breaks. I need to traverse to treeList[0].Children[int], followed by treeList[0].Children[int].Children[int] and so on and so forth.
So how do I accomplish this goal?
First I'm going to modify the TreeNode class for our convenience. It's not necessary, but just a nice to have. Also I'm going to assume that in your datatable you've done your error checking and there's only one node with ParentId = "".
public class TreeNode
{
public string Id { get; set; }
public string Name { get; set; }
public string ParentID { get; set; }
public List<TreeNode> Children { get; set; }
public TreeNode()
{
Id = Name = ParentID = string.Empty;
Children = new List<TreeNode>();
}
public bool IsRoot { get { return ParentID == string.Empty; } }
public bool IsChild { get { return Children == null || Children.Count == 0; } }
}
First, I'd convert your datatable data into a list of TreeNode objects. Forget about relationships, just create a list with each objects Children being empty. I wrote a method to simulate data retrival from datatable. Instead of that you can use your actual datatable.
static List<DataTableData> GetDataTableData()
{
var data = new List<DataTableData>
{
new DataTableData() { Id = "23456", ParentID = "12345", Name = "Middle" },
new DataTableData() { Id = "55555", ParentID = "12345", Name = "Middle" },
new DataTableData() { Id = "34567", ParentID = "23456", Name = "Bottom" },
new DataTableData() { Id = "12345", ParentID = string.Empty, Name = "Top" },
new DataTableData() { Id = "45678", ParentID = "23456", Name = "Bottom" },
new DataTableData() { Id = "66666", ParentID = "55555", Name = "Bottom" }
};
return data;
}
And this is what your Main() would look like:
static void Main(string[] args)
{
var treeNodes = new List<TreeNode>();
var dataTable = GetDataTableData();
foreach (var data in dataTable)
{
treeNodes.Add(new TreeNode() { Id = data.Id, Name = data.Name, ParentID = data.ParentID });
}
var root = BuildTree(treeNodes);
Console.ReadLine();
}
Now, in my BuildTree() method, instead of passing the datatable I can pass my TreeNode list, and return just the root node.
public static TreeNode BuildTree(List<TreeNode> nodes)
{
foreach (var node in nodes)
{
node.Children = nodes.Where(x => x.ParentID == node.Id).ToList();
}
return nodes.Find(x => x.IsRoot);
}
BuildTree() Breakdown
The nodes list already have all the nodes corresponding to data in your datatable. The BuildTree() is merely going to create the parent-child relations and fill in each object's Children list.
So I iterate through the list, and see what other elements in the list are supposed to be its children. When you have iterated through the list you'd created all the parent-child relationships. Finally, I pick the root node (the one who's ParentId is empty) and return it.
Edit
Here's an easy method to print and verify your tree.
static void PrintTree(TreeNode node, int indents)
{
for (int tab = 0; tab < indents; tab++)
{
Console.Write("\t");
}
Console.WriteLine("{0} - {1}", node.Id, node.Name);
if (node.Children != null && node.Children.Count > 0)
{
indents++;
foreach (var child in node.Children)
{
PrintTree(child, indents);
}
}
}
My output looks like this:
If you are building a class structure then you need a class with a recursive method. Not sure how efficient this will be if it gets too big. Execute the method from the top of the tree.
public class TreeNode
{
public string id { get; set; }
public string name { get; set; }
public string parentID { get; set; }
public List<TreeNode> children { get; set; }
public TreeNode() {
children = new List<TreeNode>();
}
public TreeNode FindParentWithID(string ID)
{
TreeNode ParentWithID = null;
//check my parentID if i am the one being looked for then return
if (id == ID) return this;
//search children
foreach (TreeNode treeNode in children)
{
ParentWithID = treeNode.FindParentWithID(ID);
if (ParentWithID != null)
{
break;
}
}
return ParentWithID;
}
}
You would load your data into the classes from the database. I had to hard code the values for the example to work:
TreeNode treeNode5 = new TreeNode() { id = "ABCD-12345", parentID = null, name = "Top" };
TreeNode treeNode6 = new TreeNode() { id = "ABCD-12346", parentID = "ABCD-12345", name = "Middle" };
treeNode5.children.Add(treeNode6);
TreeNode treeNode7 = new TreeNode() { id = "ABCD-12347", parentID = "ABCD-12346", name = "Bottom" };
TreeNode treeNode8 = new TreeNode() { id = "ABCD-12348", parentID = "ABCD-12346", name = "Bottom" };
treeNode6.children.Add(treeNode7);
treeNode6.children.Add(treeNode8);
TreeNode topOne = treeNode5.FindParentWithID("ABCD-12346");
topOne will be end up being treeNode6 name="Middle" in this example.
try this code, i have same issue and it works perfectly
this method used to build tree from list of items, by looping through all items, and add each item to its parent's child list. and return only the root item with its nested child.
public TreeNode BuildTree(List<TreeNode> source)
{
// build the children list for each item
foreach (var item in source)
{
var itm = source.Where(i => i.parentID == item.Id).ToList();
item.ChildItems = itm;
}
// return only the root parents with its child inside
return source.Where(i => i.parentID == null).FirstOrDefault();
}
noting that this method return only on TreeNode Object with its child, you can return List by changing .FirstOrDefault() to .ToList() in return line

Create a nested list of items from objects with a parent reference

I am using C# and I have a list of items that have a nullable parent ID property.
I need to convert this to a list of items that have a list of their children and keep going down generations until there are no items left.
My existing class
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
}
My first thoughts...
Create a class
public class ItemWithChildren
{
public Item Item { get; set; }
public List<ItemWithChildren> Children { get; set; }
}
Now I need some way to get a List<ItemWithChildren> that has all the top level Item objects and their children into the Children property.
Note that the nesting is not a set number of levels.
I was hoping there was an elegant LINQ query that would work. So far I just have this...
var itemsWithChildren = items.Select(a => new ItemWithChildren{ Item = a });
It is more readable to not use pure Linq for this task, but a mixture of Linq and looping.
Given the following container:
class Node
{
public int Id { get; set; }
public int? ParentId { get; set; }
public List<Node> Children { get; set; }
}
Then you can make the tree with the following code.
var nodes = new List<Node>
{
new Node{ Id = 1 },
new Node{ Id = 2 },
new Node{ Id = 3, ParentId = 1 },
new Node{ Id = 4, ParentId = 1 },
new Node{ Id = 5, ParentId = 3 }
};
foreach (var item in nodes)
{
item.Children = nodes.Where(x => x.ParentId.HasValue && x.ParentId == item.Id).ToList();
}
var tree = nodes.Where(x => !x.ParentId.HasValue).ToList();
This will handle any level of depth and return a proper tree.
Given the following method to print the tree:
private void PrintTree(IEnumerable<Node> nodes, int indent = 0)
{
foreach(var root in nodes)
{
Console.WriteLine(string.Format("{0}{1}", new String('-', indent), root.Id));
PrintTree(root.Children, indent + 1);
}
}
The output of this call is:
1
-3
--5
-4
2
If however you want to use pure Linq for this, you can do the following, however to me it is harder to read:
var tree = nodes.Select(item =>
{
item.Children = nodes.Where(child => child.ParentId.HasValue && child.ParentId == item.Id).ToList();
return item;
})
.Where(item => !item.ParentId.HasValue)
.ToList();
This might help ?
var itemsWithChildren = items.Select(a => new ItemWithChildren{
Item = a,
Children = items.Where(b => b.ParentId==a.Id)
.ToList()
});
Then update model to achieve it as
public string Name { get; set; }
[DisplayName("Parent Category")]
public virtual Guid? CategoryUID { get; set; }
public virtual ICollection<Category> Categories { get; set; }
I think you need to do it in two steps...
I've tested and it definitely works...
var items = new List<Item>();
items.Add(new Item { Id = 1, ParentId = null });
items.Add(new Item { Id = 2, ParentId = 1 });
items.Add(new Item { Id = 3, ParentId = 1 });
items.Add(new Item { Id = 4, ParentId = 3 });
items.Add(new Item { Id = 5, ParentId = 3 });
var itemsWithChildren = items.Select(a =>
new ItemWithChildren { Item = a }).ToList();
itemsWithChildren.ForEach(a =>
a.Children = itemsWithChildren.Where(b =>
b.Item.ParentId == a.Item.Id).ToList());
var root = itemsWithChildren.Single(a => !a.Item.ParentId.HasValue);
Console.WriteLine(root.Item.Id);
Console.WriteLine(root.Children.Count);
Console.WriteLine(root.Children[0].Children.Count);
Console.WriteLine(root.Children[1].Children.Count);
Output...
1
2
0
2

compare properties in classes of list in class

What I've got are two classes which each contain Lists of Classes with propperties of different types. The first list is an updated version of the second and i need to find all differences (deleted/added classes in lists and updated classes).
public class ClassOfKb
{
public List<Data> KbData {get;set;}
public List<Info> KbInfo {get;set;}
}
class Data
{
public Guid ID {get;set}
public byte[] file {get;set}
public string name {get;set}
}
class Info
{
public Guid ID {get;set}
public string text {get;set}
public DateTime date {get;set}
}
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
first KbA and KbB will be filled from the same DataSet, then i delete, add and modify some of KbA Child-Classes.
now i need to compare KbA with KbB to find out where the differences are. i need the ID of deleted or added classes in KbA and the exact changes of modified Child-Classes properties. How would i do this? Preffered with Linq.
I suggest that create two comparers one for Data and one for Info
class DataComparer : IEqualityComparer<Data>
{
public bool Equals(Data x, Data y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Data d)
{
//logic to return a hash code
}
}
class InfoComparer : IEqualityComparer<Info>
{
public bool Equals(Info x, Info y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Info i)
{
//logic to return a hash code
}
}
The you can use Intersect and Except LINQ methods
IEnumerable<Data> DataInAandNotInB = KbA.KbData.Except(KbB.KbData,new DataComparer());
IEnumerable<Info> InfoInAandInB = KbA.KbInfo.Intersect(KbB.KbInfo,new InfoComparer ());
For simplicity, I skipped comparison of the byte array and DateTime data membes, only left the IDs and the string data members, but to add them you will need some small modification.
The test is very-very basic, but shows all three of the changes options:
static void Main(string[] args)
{
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
// Test data --------
Data data1 = new Data() { ID = Guid.NewGuid(), name = "111" };
Data data2 = new Data() { ID = Guid.NewGuid(), name = "222" };
Data data2_changed = new Data() { ID = data2.ID, name = "222_changed" };
Data data3 = new Data() { ID = Guid.NewGuid(), name = "333" };
Info info1 = new Info() { ID = Guid.NewGuid(), text = "aaa" };
Info info2 = new Info() { ID = Guid.NewGuid(), text = "bbb" };
Info info2_changed = new Info() { ID = info2.ID, text = "bbb_changed" };
Info info3 = new Info() { ID = Guid.NewGuid(), text = "ccc" };
KbA.KbData.Add(data1);
KbA.KbData.Add(data2);
KbA.KbInfo.Add(info1);
KbA.KbInfo.Add(info2);
KbB.KbData.Add(data2_changed);
KbB.KbData.Add(data3);
KbB.KbInfo.Add(info2_changed);
KbB.KbInfo.Add(info3);
// end of test data ---------
// here is the solution:
var indexes = Enumerable.Range(0, KbA.KbData.Count);
var deleted = from i in indexes
where !KbB.KbData.Select((n) => n.ID).Contains(KbA.KbData[i].ID)
select new
{
Name = KbA.KbData[i].name,
KbDataID = KbA.KbData[i].ID,
KbInfoID = KbA.KbInfo[i].ID
};
Console.WriteLine("deleted:");
foreach (var val in deleted)
{
Console.WriteLine(val.Name);
}
var added = from i in indexes
where !KbA.KbData.Select((n) => n.ID).Contains(KbB.KbData[i].ID)
select new
{
Name = KbB.KbData[i].name,
KbDataID = KbB.KbData[i].ID,
KbInfoID = KbB.KbInfo[i].ID
};
Console.WriteLine("added:");
foreach (var val in added)
{
Console.WriteLine(val.Name);
}
var changed = from i in indexes
from j in indexes
where KbB.KbData[i].ID == KbA.KbData[j].ID &&
(//KbB.KbData[i].file != KbA.KbData[j].file ||
KbB.KbData[i].name != KbA.KbData[j].name ||
//KbB.KbInfo[i].date != KbA.KbInfo[j].date ||
KbB.KbInfo[i].text != KbA.KbInfo[j].text
)
select new
{
Name = KbA.KbData[j].name,
KbDataID = KbA.KbData[j].ID,
KbInfoID = KbA.KbInfo[j].ID
};
Console.WriteLine("changed:");
foreach (var val in changed)
{
Console.WriteLine(val.Name);
}
Console.ReadLine();
}
}
public class ClassOfKb
{
public List<Data> KbData = new List<Data>();
public List<Info> KbInfo = new List<Info>();
}
public class Data
{
public Guid ID { get; set; }
public byte[] file { get; set; }
public string name { get; set; }
}
public class Info
{
public Guid ID { get; set; }
public string text { get; set; }
public DateTime date { get; set; }
}

Categories

Resources