interface Nameable
{
string Name { get; set; }
}
class Parent : Nameable
{
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
}
class Child
{
public string Name { get; set; }
public int Value { get; set; }
public string DataOne { get; set; }
public string DataTwo { get; set; }
public double DataThree { get; set; }
}
static async void MainAsync(string[] args)
{
for (int i = 0; i < random.Next(10000, 50000); i++)
{
Parents.Add(CreateParent());
}
Parents = Parents.GroupBy(g => g.Name).Select(grp => grp.First()).ToList();
foreach (var parent in Parents)
{
await Insert<Parent>(parent);
}
// update objects randomly;
foreach (var parent in Parents)
{
for (int i = 0; i < random.Next(10, 30); i++)
{
int decision = random.Next(0, 2);
if (decision == 0 && parent.Children.Count > 0)
{
parent.Children.RemoveAt(random.Next(0, parent.Children.Count));
}
else
{
var inner = CreateChild();
if (!parent.Children.Any(io => io.Name == inner.Name))
{
parent.Children.Add(inner);
}
}
await ReplaceOne<Parent>(parent);
}
}
}
I have a list of Parents and each one contains a list of Child elements. When using the c# Mongo driver to replace these parents after they have been updated by either removing or adding new Children It sometimes creates duplicates of the Child on the Mongo side despite there being no duplicates when the code calls the replace method.
I think this is something to do with the atomic sub document structure of Mongo and how it updates/replaces items. Is there a way to prevent this from creating duplicates? and if it is not happening due to the atomic nature what is causing this?
Edit:
static async Task ReplaceOne<T>(T obj)
where T : Nameable
{
await database.GetCollection<T>(typeof(T).Name).ReplaceOneAsync(Builders<T>.Filter.Where(t => t.Name == obj.Name), obj);
}
static async Task Insert<T>(T obj)
{
await database.GetCollection<T>(typeof(T).Name).InsertOneAsync(obj);
}
static Parent CreateParent()
{
var innerObjects = new List<Child>();
for (int i = 0; i > random.Next(1, 10); i++)
{
innerObjects.Add(CreateChild());
}
return new Parent()
{
Name = RandomString(),
Children = innerObjects
};
}
static Child CreateChild()
{
return new Child()
{
Name = RandomString(),
Value = RandomInt(),
DataOne = RandomString(),
DataTwo = RandomString(),
DataThree = RandomDouble()
};
}
Added the replace/Insert snippets, they are using the mongo c# driver to insert into the db. The CreateParent and CreateChild just fills the objects with random relevant data.
I tried to guess your RandomString(), RandomInt() and RandomDouble() methods and I ran your project several times without cleaning the database. I could not detect any duplicates whatsoever based on the two "Name" properties (on parent and child).
I suspect your observation is somehow incorrect. In order to check if you do actually have duplicate children within the same parent you can use the following query:
collection.aggregate(
{
$unwind: "$Children"
},
{
$group:
{
_id:
{
"Name": "$Name",
"ChildName": "$Children.Name"
}
, "count": { $sum: 1 }
}
},
{
$match:
{
"count": { $ne: 1 } }
}
)
Related
This question already has answers here:
How to flatten tree via LINQ?
(15 answers)
Closed 6 months ago.
I've the following class, which contains some info about the object it also has a list of same object and hierarchy goes on. This is my class:
public class Category
{
public List<Category>? children { get; set; }
public bool var { get; set; }
public string? name { get; set; }
public bool leaf { get; set; }
public int category_id { get; set; }
}
I have a list List<Category> categories; I want to loop over the list and go deep down in every children and create this new object:
public class DBCategory
{
public string? CategoryId { get; set; }
public string? CategoryName { get; set; }
public string? CategoryParentId { get; set; }
}
I have tried to loop over my list and then call function recursively but I'm also stuck there because children isn't a category class but a list of categories so the function fails to accept parameter in if clause:
foreach (var category in categories)
{
CreateDBCategory(category);
}
DBCategory CreateDBCategory(Category category)
{
DBCategory dBCategory = new DBCategory();
if (category.children.Count > 0)
{
return CreateDBCategory(category.children);
}
return dBCategory;
}
I have also tried to reach most bottom child by this, but this code says not all paths return a value.
DBCategory testFunction(List<Category> categories)
{
foreach (var category in categories)
{
if (category.children.Count > 0)
{
return testFunction(category.children);
}
else
{
return category;
}
}
}
One of the common ways to handle such cases is to have the List to be filled passed as an argument to the method. E.g.:
List<DBCategory> dbCategories = new();
foreach (var category in categories)
{
CreateDBCategory(category, dbCategories);
}
void CreateDBCategory(Category category, List<DBCategory> dbCategories)
{
DBCategory dbCategory = new DBCategory();
// Fill dbCategory
dBCategories.Add(dbCategory);
if (category.children != null)
{
// recurse over all children categories and add them to the list
foreach (var child in category.children)
{
CreateDBCategory(child, dbCategories);
}
}
}
It could be argued that this solution does not fit the functional paradigm as it has side effects (modifying the passed in List), so an alternative, more functional approach would be to return a list from the recursive method, e.g.:
List<DBCategory> dbCategories = new();
foreach (var category in categories)
{
dbCategories.AddRange(CreateDBCategory(category));
}
IEnumerable<DBCategory> CreateDBCategory(Category category)
{
List<DBCategory> dbCategories = new();
DBCategory dbCategory = new DBCategory();
// Fill dbCategory
dbCategories.Add(dbCategory);
if (category.children != null)
{
// recurse over all children categories and add them to the list
foreach (var child in category.children)
{
dbCategories.AddRange(CreateDBCategory(child));
}
}
return dbCategories;
}
This does however perform a lot more allocations, so in some cases it can perform slower than the first approach
Noted that this is untested, but it should work.
IEnumerable<DBCategory> FlattenCategories(IEnumerable<Category> categories, int parentId)
{
DBCategory selector(Category cat, int pid) =>
return categories
.Select(c => new DBCategory {
CategoryId = cat.category_id,
CategoryName = cat.name,
CategoryParentId = pid,
})
.Concat(categories.SelectMany(
c => FlattenCategories(c.children, c.category_id)
);
}
Just call FlattenCategories(categories).ToList(); to get List<DBCategory>
From here, A generic solution.
public static IEnumerable<T> Traverse<T>(
this T root,
Func<T, IEnumerable<T>> childrenSelector)
{
ArgumentNullException.ThrowIfNull(childrenSelector);
var stack = new Stack<T>();
stack.Push(root);
while(stack.Count > 0)
{
var current = stack.Pop();
yield return current;
foreach(var child in childrenSelector(current))
{
stack.Push(child);
}
}
}
So you can do this,
foreach(var category in root.Traverse(c => c.Children))
{
...
}
or some LINQ. The beauty is, it won't allocate more memory than your biggest leaf collection and won't have a stack overflow for deep trees.
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication40
{
class Program
{
static void Main(string[] args)
{
Category root = new Category()
{
children = new List<Category>() {
new Category() {
children = new List<Category>() {
new Category() {
var = true,
name = "2A",
leaf = true,
category_id = 21
},
new Category() {
var = true,
name = "2B",
leaf = true,
category_id = 22
}
},
var = true,
name = "1A",
leaf = false,
category_id = 1
},
new Category() {
children = new List<Category>() {
new Category() {
var = true,
name = "2C",
leaf = true,
category_id = 23
},
new Category() {
var = true,
name = "2D",
leaf = true,
category_id = 24
}
},
var = true,
name = "1B",
leaf = false,
category_id = 2
},
},
category_id = 0,
name = "root",
leaf = false,
var = true
};
List<DBCategory> children = DBCategory.GetChildren(root,null);
}
}
public class Category
{
public List<Category> children { get; set; }
public bool var { get; set; }
public string name { get; set; }
public bool leaf { get; set; }
public int category_id { get; set; }
}
public class DBCategory
{
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
public int? CategoryParentId { get; set; }
public static List<DBCategory> GetChildren(Category catgory, int? parentId)
{
List<DBCategory> results = new List<DBCategory>() { new DBCategory() {
CategoryId = catgory.category_id,
CategoryName = catgory.name,
CategoryParentId = parentId
}};
if (catgory.children != null)
{
foreach (Category child in catgory.children)
{
results.AddRange(GetChildren(child, catgory.category_id));
}
}
return results;
}
}
}
I have a self-referencing entity Comment in my project:
public class Comment
{
public Guid CommentId { get; set; }
public string Content { get; set; }
public Guid? ParentCommentId { get; set; }
public virtual Comment ParentComment { get; set; }
public virtual ICollection<Comment> Children { get; set; }
}
I am trying to do the following:
await context.Comment
.Where(c => c.ParentCommentId == null)
.Include(c => c.Children)
.ToListAsync();
I want to get all root comments (comments that don't have parents) and load the entire hierarchy of comments.
Want do I want to see in the result?
I have the following comments in the database:
To make it more readable, I represent it in hierarchically order (only Content property):
Hello World!
Are you a programmer?
Sure
What?
I wanna go to Mars too!
See you on the Moon :)
When executing the query, shown above, I want to get something like this (in JSON):
[
{
"commentId":"be02742a-9170-4335-afe7-3c7c22684424",
"content":"Hello World!",
"parentCommentId":null,
"children":[
{
"commentId":"59656765-d1ed-4648-8696-7d576ab7419f",
"content":"Are you a programmer?",
"parentCommentId":"be02742a-9170-4335-afe7-3c7c22684424",
"children":[
{
"commentId":"0bb77a43-c7bb-482f-9bf8-55c4050974da",
"content":"Sure",
"parentCommentId":"59656765-d1ed-4648-8696-7d576ab7419f",
"children":[
]
},
{
"commentId":"b8d61cfd-d274-4dae-a2be-72e08cfa9066",
"content":"What?",
"parentCommentId":"59656765-d1ed-4648-8696-7d576ab7419f",
"children":[
]
}
]
}
]
},
{
"commentId":"cfe126b3-4601-4432-8c87-445c1362a225",
"content":"I wanna go to Mars too!",
"parentCommentId":null,
"children":[
{
"commentId":"ab6d6b49-d772-48cd-9477-8d40f133c37a",
"content":"See you on the Moon :)",
"parentCommentId":"cfe126b3-4601-4432-8c87-445c1362a225",
"children":[
]
}
]
}
]
But what do I get?
When I execute this query I have the following result:
[
{
"commentId":"be02742a-9170-4335-afe7-3c7c22684424",
"content":"Hello World!",
"postId":"69f3ca3a-66fc-4142-873d-01e950d83adf",
"post":null,
"parentCommentId":null,
"parentComment":null,
"commentRates":[
],
"inverseParentComment":[
{
"commentId":"59656765-d1ed-4648-8696-7d576ab7419f",
"content":"Are you a programmer?",
"postId":"69f3ca3a-66fc-4142-873d-01e950d83adf",
"post":null,
"parentCommentId":"be02742a-9170-4335-afe7-3c7c22684424",
"commentRates":[
],
"inverseParentComment":[
]
}
]
},
{
"commentId":"cfe126b3-4601-4432-8c87-445c1362a225",
"content":"I wanna go to Mars too!",
"postId":"69f3ca3a-66fc-4142-873d-01e950d83adf",
"post":null,
"parentCommentId":null,
"parentComment":null,
"commentRates":[
],
"inverseParentComment":[
{
"commentId":"ab6d6b49-d772-48cd-9477-8d40f133c37a",
"content":"See you on the Moon :)",
"postId":"69f3ca3a-66fc-4142-873d-01e950d83adf",
"post":null,
"parentCommentId":"cfe126b3-4601-4432-8c87-445c1362a225",
"commentRates":[
],
"inverseParentComment":[
]
}
]
}
]
So, I get the following hierarchy:
Hello World!
Are you a programmer?
I wanna go to Mars too!
See you on the Moon :)
Instead of this:
Hello World!
Are you a programmer?
Sure
What?
I wanna go to Mars too!
See you on the Moon :)
Why does it work so? How can I fix this problem?
Working, but dirty solution
List<Comment> allComments = await context.Comment
.Include(c => c.Children)
.ToListAsync();
List<Comment> filteredComments = allComments.Where(c => c.ParentCommentId == null);
This works, but it's ugly - we load the full hierarchy of comments from the database into the memory and than filter them. It'll work really slow if the database contains, e.g., 1 million comments.
Update
In my case there is no need to use recursive CTE. See update for this question. It looks like the idea introduced by Greg Ogle, but more simplified.
If you simply load the flat data from the Comment table, it is reasonably fast. To assist in lookups, you can use a Dictionary<{Guid or whatever type makes sense}, {Comment object type}>. The lookup on Dictionary uses a HashTable (I'm pretty sure) and is fast. So, once you have your root Comments, the subsequent lookups will be fast.
Here is code for a Console app to demonstrate. It does take a few seconds for it to run. Perhaps further optimization is possible.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Timers;
namespace DictionaryHierarchyTest
{
internal class Program
{
private static void Main(string[] args)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var commentsFlat = new List<CommentFlat>();
var parentLookup = new Dictionary<Guid, List<CommentFlat>>();
for (var i = 0; i < 100000; i++)
{
var id = Guid.NewGuid();
commentsFlat.Add(new CommentFlat
{
Id = id,
ParentId = Guid.Empty,
Text = $"Some text for ID:{id}"
});
for (var j = 0; j < 5; j++)
{
var childId = Guid.NewGuid();
commentsFlat.Add(new CommentFlat
{
Id = childId,
ParentId = id,
Text = $"Some text for ID:{childId} Parent ID:{id}"
});
for (var k = 0; k < 5; k++)
{
var grandChildId = Guid.NewGuid();
commentsFlat.Add(new CommentFlat
{
Id = grandChildId,
ParentId = childId,
Text = $"Some text for ID:{grandChildId} Parent ID:{childId}"
});
}
}
}
foreach (var cf in commentsFlat)
{
if (!parentLookup.ContainsKey(cf.ParentId))
{
parentLookup.Add(cf.ParentId, new List<CommentFlat>());
}
parentLookup[cf.ParentId].Add(cf);
}
stopwatch.Stop();
var ts = stopwatch.Elapsed;
Console.WriteLine($"There are {commentsFlat.Count} comments");
Console.WriteLine($"{String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10)} to randomly populated.");
stopwatch.Reset();
stopwatch.Start();
var commentHierarchy = new List<Comment>();
foreach (var cf in commentsFlat)
{
if (cf.ParentId == Guid.Empty)
{
var comment = new Comment
{
Id = cf.Id,
ParentId = cf.ParentId,
Children = BuildHierarchy(cf.Id, parentLookup),
Text = cf.Text
};
commentHierarchy.Add(comment);
}
}
stopwatch.Stop();
ts = stopwatch.Elapsed;
Console.WriteLine($"{String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10)} to make hierarchical");
//foreach (var c in commentHierarchy) {
// PrintHiearchy(c, 1);
//}
}
static string Tabs(int n)
{
return new String('\t', n);
}
private static void PrintHiearchy(Comment comment, int level) {
Console.WriteLine($"{Tabs(level)}{comment.Text}");
foreach(var child in comment.Children){ PrintHiearchy(child, level + 1); }
}
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
e.SignalTime);
}
private static List<Comment> BuildHierarchy(Guid parentId, Dictionary<Guid, List<CommentFlat>> flatComments)
{
if (flatComments.ContainsKey(parentId))
{
return flatComments[parentId].Select(c => new Comment
{
Id = c.Id,
ParentId = c.ParentId,
Text = c.Text,
Children = BuildHierarchy(c.Id, flatComments)
}).ToList();
}
else
{
return new List<Comment>();
}
}
}
public class Comment
{
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public string Text { get; set; }
public List<Comment> Children { get; set; }
}
public class CommentFlat
{
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public string Text { get; set; }
}
}
I have a C# object which may parent itself in order to create a tree-like data structure.
public class AssetType
{
public long? Id { get; set; }
public string Definition { get; set; }
public int AssetCount { get; set; }
public long? ParentId { get; set; }
public ICollection<AssetType> Children { get; set; }
}
I succesfully populated the data and I have a collection of root AssetTypeTree objects with children. Now, what I need is to sum the AssetCount value of every children to their parent iteratively. For example my initial data may be as follows:
-ELECTRONICS -> AssetCount: 0
+COMPUTERS -> AssetCount: 0
*PERSONAL COMPUTERS -> AssetCount: 5
*SERVERS -> AssetCount: 3
+PRINTERS -> AssetCount: 2
-FURNITURES -> AssetCount: 0
+TABLES -> AssetCount: 4
Here, the AssetCount values are correct in the leaf nodes. But I need to also populate the values of the parent nodes. Here, COMPUTERS should have a count of 8, ELECTRONICS should be 10 and FURNITURES should be 4. Depth of nodes may differ and also non-leaf nodes may also have a initial count value other than 0.
It should be possible to write a recursive iteration of some sort but I couldn't wrap my head around this really.
Try following :
class Program
{
static void Main(string[] args)
{
AssetType root = new AssetType();
AssetType.AddCount(root);
}
}
public class AssetType
{
public long? Id { get; set; }
public string Definition { get; set; }
public int AssetCount { get; set; }
public long? ParentId { get; set; }
public ICollection<AssetType> Children { get; set; }
public static int AddCount(AssetType parent)
{
foreach (AssetType child in parent.Children)
{
parent.AssetCount += AddCount(child);
}
return parent.AssetCount;
}
}
A recursive function should do the trick nicely. Here is something i typed up real quick. Make sure there is no circular loops, as i did not check for that.
private int GetAssetCount(AssetType asset)
{
var assetCount = 0;
if (asset.Children != null)
{
foreach (var child in asset.Children)
{
child.AssetCount = GetAssetCount(child);
assetCount += child.AssetCount;
}
}
else
{
assetCount = asset.AssetCount;
}
return assetCount;
}
Sanity Check
private void TestAssetRecursion()
{
var asset0 = new AssetType()
{
AssetCount = 0
};
var asset1 = new AssetType()
{
AssetCount = 0
};
var asset2 = new AssetType()
{
AssetCount = 0
};
var asset3 = new AssetType()
{
AssetCount = 3
};
var asset4 = new AssetType()
{
AssetCount = 4
};
asset0.Children = new AssetType[] { asset1 };
asset1.Children = new AssetType[] { asset2 };
asset2.Children = new AssetType[] { asset3, asset4 };
asset0.AssetCount = GetAssetCount(asset0);
}
I have the following Model, which can be a child of the same type, and/or have many children of the same type.
public class SystemGroupModel
{
public int Id { get; set; }
public int? ParentSystemGroupModelId { get; set; }
[ForeignKey("ParentSystemGroupModelId")]
public virtual SystemGroupModel ParentSystemGroupModel { get; set; }
[InverseProperty("ParentSystemGroupModel")]
public virtual ICollection<SystemGroupModel> SubSystemGroupModels { get; set; }
}
How can I get a specific SystemGroupModel by Id that is at an unknown depth, and include all of it's children, grandchildren, great-grandchildren, etc.?
This appears to be working, which means it will build a list of any given parent, as well as children at an unlimited depth of grandchildren, great-grandchildren, and so on.
Is this the right approach?
First I created this new class
public class SystemGroupWorkingClass
{
public SystemGroupModel SystemGroupModel { get; set; }
public bool WasChecked { get; set; }
}
Then I added this code
EDIT: Updated to include the loop that builds List<SystemGroupWorkingClass>
List<SystemGroupWorkingClass> tempListSystemGroups = new List<SystemGroupWorkingClass>();
//Get the SystemGroups that were selected in the View via checkbox
foreach (var systemGroupVM in viewModel.SystemGroups)
{
if (systemGroupVM.Selected == true)
{
SystemGroupModel systemGroupModel = await db.SystemGroupModels.FindAsync(systemGroupVM.Id);
SystemGroupWorkingClass systemGroupWorkingClass = new SystemGroupWorkingClass();
systemGroupWorkingClass.SystemGroupModel = systemGroupModel;
systemGroupWorkingClass.WasChecked = false;
systemGroupWorkingClass.Selected = true;
//Make sure tempListSystemGroups does not already have this systemGroupWorkingClass object
var alreadyAddedCheck = tempListSystemGroups
.FirstOrDefault(s => s.SystemGroupModel.Id == systemGroupVM.Id);
if (alreadyAddedCheck == null)
{
tempListSystemGroups.Add(systemGroupWorkingClass);
}
}
}
for (int i = 0; i < tempListSystemGroups.Count; i++)
{
if (tempListSystemGroups[i].WasChecked == false)
{
SystemGroupModel systemGroupModel2 = await db.SystemGroupModels.FindAsync(tempListSystemGroups[i].SystemGroupModel.Id);
//Get the children, if there are any, for the current parent
var subSystemGroupModels = systemGroupModel2.SubSystemGroupModels
.ToList();
//Loop through the children and add to tempListSystemGroups
//The children are added to tempListSystemGroups as it is being iterated over
foreach (var subSystemGroupModel in subSystemGroupModels)
{
SystemGroupModel newSystemGroupModel = await db.SystemGroupModels.FindAsync(subSystemGroupModel.Id);
SystemGroupWorkingClass subSystemGroupWorkingClass = new SystemGroupWorkingClass();
subSystemGroupWorkingClass.SystemGroupModel = newSystemGroupModel;
subSystemGroupWorkingClass.WasChecked = false;
tempListSystemGroups.Add(subSystemGroupWorkingClass);
}
}
//Mark the parent as having been checked for children
tempListSystemGroups[i].WasChecked = true;
}
I have a list and I need to sort it to this hierarchy
{ Id=1, ParentId = null, Name = "Item1", Type="0"}
{ Id=2, ParentId = 1, Name = "ItemChild1", Type="1"}
{ Id=3, ParentId = 1, Name = "ItemChild2", Type="1"}
{ Id=4, ParentId = 3, Name = "ItemGrandChild1", Type="2"}
{ Id=5, **ParentId = 1**, Name = "ItemGrandChild2", Type="2"}
{ Id=6, ParentId = null, Name = "Item7", Type="0"}
...
Unlike normal parent child relationships, here
Items of Type2 can be child of either Type1 or Type0
All the Id's are guids
I have seen may stack answers on child parent sorting using Linq. But my case is different.
Any elegant way using Linq ?
If I were you, I would give up trying to sort this with either linq or sql. Abstractions can be helpful, but in a complex case like this they will just get in your way.
You will save a lot of time if you just write your own sorter.
class MyCompare : IComparer<MyObject>
{
public int Compare(x1, x2)
{
if (x1.parent == parent1 && x2.parent == parent2)
return 1;
if (x1.parent == x2.parent)
return 0;
//ect
}
}
List<MyObject> list = GetWeirdObjects();
list.Sort(new MyCompare());
Create a lookup to find fast the children and project them to the parent collection. As far as i see it does not depend at all if your children have different types as long as they know which element is their parent.
public class TreeItem
{
public int Id { get; set; }
public int? ParentId { get; set; }
public IEnumerable<TreeItem> Children { get; set; }
public void PrintAllChildren()
{
this.PrintAllChildren(0);
}
private void PrintAllChildren(int indent)
{
Debug.WriteLine("{1}Item id: {0}", this.Id, string.Concat(Enumerable.Repeat<int>(0, indent).Select(i => " ")));
if (this.Children != null)
foreach (var item in this.Children)
item.PrintAllChildren(indent + 1);
}
}
public static class TreeItemExtension
{
public static IEnumerable<TreeItem> GetAsTree(this IEnumerable<TreeItem> data)
{
var lookup = data.ToLookup(i => i.ParentId);
return lookup[null].Select(i => {
i.FillChildren(lookup);
return i;
});
}
private static TreeItem FillChildren(this TreeItem item, ILookup<int?, TreeItem> lookup)
{
item.Children = lookup[item.Id].Select(i => i.FillChildren(lookup));
return item;
}
}