There are all view locations (let's say 100, these are locations for the TreeView):
Id, Name, ParentId
1 Root Null
2 Semi-root 1
3 Semi-semi-root 2
4 ..... ....
And there is data in which we received only those locations that correspond to our values from another request:
Id, Name, ParentId
22 Location1 12
36 Location38 21
99 Location38 3
Need to get all parent hierarchy for data which we get from request.
There is the class:
public class TreeViewNode {
public Guid Id {get; set;}
public string Name {get; set;}
public Guid ParentId {get; set;}
}
Test data:
private IEnumerable<TreeViewNode> SeedData()
{
return new List<TreeViewNode>
{
new()
{
Id = 1,
Name = "Root",
ParentId = null
},
new()
{
Id = 2,
Name = "Semi-root",
ParentId = 1
},
new()
{
Id = 3,
Name = "Semi te",
ParentId = 2
},
new()
{
Id = 4,
Name = "Semi oi",
ParentId = 2
},
new()
{
Id = 5,
Name = "Child",
ParentId = 3
},
new()
{
Id = 6,
Name = "Child 1",
ParentId = 4
},
new()
{
Id = 7,
Name = "Child 2",
ParentId = 1
},
new()
{
Id = 8,
Name = "Child 3",
ParentId = 1
},
new()
{
Id = 9,
Name = "Child 4",
ParentId = 1
},
new()
{
Id = 10,
Name = "Child 6",
ParentId = 2
}
};
}
Example data got from request:
var dataFromRequest = new List<TreeViewNode>
{
new()
{
Id = 8,
Name = "Child 3",
ParentId = 1
},
new()
{
Id = 10,
Name = "Child 6",
ParentId = 2
},
new()
{
Id = 33,
Name = "Child",
ParentId = 3
},
new()
{
Id = 4,
Name = "Semi oi",
ParentId = 2
}
};
And as result need to get list of parents like this:
Id Name ParentId
1 Root Null
2 Semi-root 1
3 Semi te 2
And here can be 5 or more levels of parents
Something like this
// create Dictionary for lookups
var lookup = dataFromRequest.ToDictionary(x => x.Id.Value, x => x);
var lookFor = new TreeViewNode()
{
Id = 6,
Name = "Child 1",
ParentId = 4
};
// get all the parents
GetParents(lookup, lookFor);
Helper method to get all parents
private void GetParents(Dictionary<int, TreeViewNode> lookup,
TreeViewNode lookFor)
{
while (lookFor != null)
{
// Alternative: Add lookFor to List<TreeViewNode> here and return at
// the end of the method
Debug.WriteLine($"{lookFor.Id} {lookFor.Name} {lookFor.ParentId}");
if (lookFor.ParentId == null)
break;
// cast ParentId to corrent dataType here Guid or int
lookup.TryGetValue((int)lookFor.ParentId, out var parentNode);
lookFor = parentNode;
}
}
Remember to keep your lookup Dict up to date when you get more IEnumerable<TreeViewNode>
Another thing you could do is set the parent node inside the TreeViewNode like this:
public class TreeViewNode {
...
// set ParentNode by lookup in the Dictionary once
public TreeViewNode ParentNode {get; set;}
}
Related
I have a c# class that looks something like this:
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
}
and i would like to convert it to json where json property name is item name and its value is item description. if some item has any children then i would like the json property name to stay as item name and the children to be added like item name:item description(the parent item description and type become empty string if it has any children, except when the type is array). The type has following values: array, string, int, object. if the item type is an array then each time a child is added to the type array item, the child is item description. so i would like those values to be added to the json array as well.
if the type is string or int the json property value should be int or string.
I am trying to write custom JsonSerializer but i am getting nowhere.
so if i have a list with items:
List<Item> MyItems = new List<Item>()
{
new Item { Id = 1, ParentId = null, Name = "Name1", Description = "", Type = "" },
new Item { Id = 2, ParentId = 1, Name = "Name2", Description = "Description1", Type = "String" },
new Item { Id = 3, ParentId = 1, Name = "Name3", Description = "", Type = "Array" },
new Item { Id = 4, ParentId = 3, Name = "", Description = "ArrayItem1", Type = "" },
new Item { Id = 5, ParentId = 3, Name = "", Description = "ArrayItem2", Type = "" },
new Item { Id = 6, ParentId = 3, Name = "", Description = "ArrayItem3", Type = "" },
new Item { Id = 7, ParentId = null, Name = "Name4", Description = "5", Type = "Int" },
};
then the json should like this:
{
"name1":{
"name2":"description1",
"name3":[
"ArrayItem1",
"ArrayItem2",
"ArrayItem1"
]
},
"name4":5
}
Here's an extension method that I think does what you want:
public static class Ex
{
public static string ToJson(this List<Item> items)
{
var lookup = items.ToLookup(x => x.ParentId);
JObject ToJson(int? parentId)
{
JProperty ToProperty(Item item)
{
switch (item.Type)
{
case "":
return new JProperty(item.Name, ToJson(item.Id));
case "String":
return new JProperty(item.Name, item.Description);
case "Array":
return new JProperty(item.Name, lookup[item.Id].Select(x => x.Description).ToArray());
case "Int":
return new JProperty(item.Name, int.Parse(item.Description));
default:
return new JProperty(item.Name);
}
}
return new JObject(lookup[parentId].Select(x => ToProperty(x)));
}
var output = ToJson(null);
var text = Newtonsoft.Json.JsonConvert.SerializeObject(output, Newtonsoft.Json.Formatting.Indented);
return text;
}
}
I run it like this:
List<Item> MyItems = new List<Item>()
{
new Item { Id = 1, ParentId = null, Name = "Name1", Description = "", Type = "" },
new Item { Id = 2, ParentId = 1, Name = "Name2", Description = "Description1", Type = "String" },
new Item { Id = 3, ParentId = 1, Name = "Name3", Description = "", Type = "Array" },
new Item { Id = 4, ParentId = 3, Name = "", Description = "ArrayItem1", Type = "" },
new Item { Id = 5, ParentId = 3, Name = "", Description = "ArrayItem2", Type = "" },
new Item { Id = 6, ParentId = 3, Name = "", Description = "ArrayItem3", Type = "" },
new Item { Id = 7, ParentId = null, Name = "Name4", Description = "5", Type = "Int" },
};
Console.WriteLine(MyItems.ToJson());
And I get this out:
{
"Name1": {
"Name2": "Description1",
"Name3": [
"ArrayItem1",
"ArrayItem2",
"ArrayItem3"
]
},
"Name4": 5
}
I am new to C# development, I have a "Comments" Class as below.
class Comments
{
public Id {get; set;}
public Text {get;set;}
public ParentId {get;set;}
public List<Comments> childComments {get;set;}
}
We have child comments field within main comments. I am saving each comment object as a Document in NoSQL DB. I need to fetch these all comments and convert them into single comment object by placing all of its child comments inside 'childComments' field. ParentId will be null if comment is at level 0(top most level or first level comment).
I wrote the below code to retrieve it.
List<Comments> parentcomments = <from DB>.Where(t => t.ParentId == ObjectId.Empty).ToList();
List<Comments> childcomments = <from DB>.Where(t => t.ParentId != ObjectId.Empty).ToList();
foreach(comment t in parentcomments)
{
finalCommentTree = AggregateComment(childcomments, t.Id);
}
public List<Comments> AggregateComment(List<Comments> childcomments, ObjectId parentId)
{
List<Comments> recursiveObjects = new List<Comments>();
foreach (Comments item in childcomments.Where(x => x.ParentId.Equals(t.ParentId)))
{
recursiveObjects.Add(new Comments
{
Id = item.Id,
Text = item.Text,
childComments = AggregateComment(childcomments, item.Id)
});
}
return recursiveObjects;
}
Code works good without any issues, but problem is with time complexity. Is there a way to reduce time complexity and improve performance?
Another approach:
List<Comments> parentList = new List<Comments>()
{ new Comments() { Id = 1, Text = "Parent1", ParentId = -1 },
new Comments() { Id = 2, Text = "Parent2", ParentId = -1 },
new Comments() { Id = 3, Text = "Parent3", ParentId = -1 },
};
List<Comments> childList = new List<Comments>()
{
new Comments() { Id = 91, Text = "child1", ParentId = 3 },
new Comments() { Id = 92, Text = "child2", ParentId = 2 },
new Comments() { Id = 93, Text = "child3", ParentId = 1 },
new Comments() { Id = 94, Text = "child4", ParentId = 2 },
new Comments() { Id = 95, Text = "child5", ParentId = 2 },
new Comments() { Id = 96, Text = "child6", ParentId = 1 },
new Comments() { Id = 97, Text = "child7", ParentId = 2 }
};
List<Comments> k = ( from c in childList
join p in parentList
on c.ParentId equals p.Id
group c by new
{
c.ParentId
,p.Text
} into stdGrp
select new Comments
{
Id = stdGrp.Key.ParentId,
Text = stdGrp.Key.Text,
ParentId = -1,
childComments = stdGrp.OrderBy(j => j.Id).ToList(),
}
).ToList();
Let's say I have two tables (entities):
class Person
{
public int Id { get; set; } // primary key
public string City { get; set; } // the attribute to group by
}
class JoinTable
{
public int Id { get; set; } // primary key
public int Person_Id { get; set; } // foreign key referencing a Person entity/row
public int SomeOther_Id { get; set; } // foreign key referencing some other irrelevant entity/row
}
I want to group all Person entities by their "City" attribute and count how many people are referenced in the JoinTable by each city.
How do I query that in LINQ?
I'm not quite sure, what you want to acchieve. But i think something like this:
// Example Data (would be great if you could write some in your questions..)
List<Person> ps = new List<Person>()
{
new Person() { Id = 1, City = "Cologne" },
new Person() { Id = 2, City = "Cologne" },
new Person() { Id = 3, City = "Berlin" },
new Person() { Id = 4, City = "Berlin" },
};
List<JoinTable> join = new List<JoinTable>()
{
new JoinTable() { Id = 1, Person_Id = 1, SomeOther_Id = 1000 },
new JoinTable() { Id = 1, Person_Id = 1, SomeOther_Id = 2000 },
new JoinTable() { Id = 1, Person_Id = 2, SomeOther_Id = 1000 },
new JoinTable() { Id = 1, Person_Id = 2, SomeOther_Id = 2000 },
new JoinTable() { Id = 1, Person_Id = 3, SomeOther_Id = 3000 },
new JoinTable() { Id = 1, Person_Id = 3, SomeOther_Id = 4000 },
};
// Join the Table and select a new object.
var tmp = ps.Join(
join, // which table/list should be joined
o => o.Id, // key of outer list
i => i.Person_Id, // key of inner list
(o, i) => new { City = o.City, Id = i.Id, SomeOtherId = i.SomeOther_Id}); // form a new object with three properties..
// now we can group out new objects
var groupingByCity = tmp.GroupBy(g => g.City);
// let's see what we got..
foreach (var g in groupingByCity)
{
Console.WriteLine(g.Key + ": " + g.Count());
foreach (var g2 in g.GroupBy(a => a.SomeOtherId)) // And yes we can group out values again..
{
Console.WriteLine(" " + g2.Key + ": " + g2.Count());
}
}
I have the following class
public class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Content { get; set; }
public bool IsLastItem { get; set; }
}
Let say I have the following model and I want to remove items which IsLastItem = false and don't have child. In this scenario item4 and item7 should remove from list.
I get the list of my model from database and I simulated it in the code block like this
var items = new List<Item>
{
new Item
{
Id = 1,
ParentId = 0,
Content = "item1",
IsLastItem = false
},
new Item
{
Id = 2,
ParentId = 1,
Content = "item2",
IsLastItem = false
},
new Item
{
Id = 3,
ParentId = 1,
Content = "item3",
IsLastItem = true
},
new Item
{
Id = 4,
ParentId = 1,
Content = "item4",
IsLastItem = false
},
new Item
{
Id = 5,
ParentId = 2,
Content = "item5",
IsLastItem = false
},
new Item
{
Id = 6,
ParentId = 5,
Content = "item6",
IsLastItem = false
},
new Item
{
Id = 7,
ParentId = 5,
Content = "item7",
IsLastItem = false
},
new Item
{
Id = 8,
ParentId = 6,
Content = "item8",
IsLastItem = true
},
new Item
{
Id = 9,
ParentId = 8,
Content = "item9",
IsLastItem = true
}
};
A flat list like this is not optimal for these kinds of operations - it might be nice if you could get the list back in some kind of tree structure (maybe return it from SQL using FOR XML or JSON if you're on 2016) to begin with, where you'd have an easier time traversing the tree.
Also note that, as is, your sample data isn't setting IsLastItem...
As is, you have to iterate at least twice, something like this:
items.RemoveAll(x => x.IsLastItem == false &&
items.Any(y => y.ParentId == x.Id) == false);
You're saying to remove all the items where IsLastItem is false and where there's not at least one item whose parent id is that item's id.
You forgot to set IsLastItem in your mocked up data, FYI. You should be able to accomplish this with RemoveAll.
public static void Main()
{
var items = init();
items.RemoveAll(x => !items.Any(y => y.ParentId == x.Id) == true && x.IsLastItem == false);
}
public static List<Item> init()
{
return new List<Item>
{
new Item
{
Id = 1,
ParentId = 0,
Content = "item1"
},
new Item
{
Id = 2,
ParentId = 1,
Content = "item2"
},
new Item
{
Id = 3,
ParentId = 1,
Content = "item3",
IsLastItem = true
},
new Item
{
Id = 4,
ParentId = 1,
Content = "item4"
},
new Item
{
Id = 5,
ParentId = 2,
Content = "item5"
},
new Item
{
Id = 6,
ParentId = 5,
Content = "item6"
},
new Item
{
Id = 7,
ParentId = 5,
Content = "item7"
},
new Item
{
Id = 8,
ParentId = 6,
Content = "item8"
},
new Item
{
Id = 9,
ParentId = 8,
Content = "item9",
IsLastItem = true
}
};
}
Find the ParentIds. Compare each item to the list of ParentId collection and check IsLastitem.
var parents = items.Select(x => x.ParentId);
items.RemoveAll(x => !parents.Contains(x.Id) && !x.IsLastItem);
I would suggest you use a tree-like structure, and make IsLastItem a property that is calculated:
public class Item
{
public int Id { get; set; }
public string Content { get; set; }
public List<Item> SubItems { get; set; }
public bool IsLastItem { get { return SubItems.Count == 0; } }
}
Since the items stored in the database will have a flat structure, you'll have to write a function to create the tree from the database (and write the tree out to the database if necessary), but once that's done, the tree will be easier to manipulate.
You'd then write a recursive function to remove all last nodes, something like this:
List<Item> RemoveNodes(List<Item> tree)
{
var ret = tree.Where(item => !item.IsLastItem);
foreach (Item item in ret)
{
item.SubItems = RemoveNodes(item.SubItems);
}
return ret;
}
That may not be exactly the best way to do it but you get the idea.
I have a list of data structures:
public List<Personal> Personals()
{
return new List<Personal>
{
new Personal
{
Id = 0,
Name = "Name 0"
},
new Personal
{
Id = 1,
Name = "Name 1",
ParentId = 0
},
new Personal
{
Id = 2,
Name = "Name 2",
ParentId = 0
},
new Personal
{
Id = 3,
Name = "Name 3",
ParentId = 0
},
new Personal
{
Id = 4,
Name = "Name 4",
ParentId = 1
},
new Personal
{
Id = 5,
Name = "Name 5",
ParentId = 1
},
new Personal
{
Id = 6,
Name = "Name 6",
ParentId = 2
},
new Personal
{
Id = 7,
Name = "Name 7",
ParentId = 2
},
new Personal
{
Id = 8,
Name = "Name 8",
ParentId = 4
},
new Personal
{
Id = 9,
Name = "Name 9",
ParentId = 4
},
};
}
and I want to build a tree:
public List<Tree> Trees()
{
return new List<Tree>
{
new Tree
{
Id = 0,
Name = "Name 0",
List = new List<Tree>
{
new Tree
{
Id = 1,
Name = "Name 1",
List = new List<Tree>
{
new Tree
{
Id = 4,
Name = "Name 4"
},
new Tree
{
Id = 5,
Name = "Name 5"
}
}
}
}
}
};
}
How do you build a tree with LinQ to object? I have to use but it doesn't work exactly, see below:
public List<Tree> GetTree(List<Personal> list)
{
var listFormat = list.Select(x => new Tree
{
Id = x.Id,
Name = x.Name,
ParentId = x.ParentId
}).ToList();
var lookup = listFormat.ToLookup(f => f.ParentId);
foreach (var tree in listFormat)
{
tree.List = lookup[tree.Id].ToList();
}
return listFormat;
}
You should use recursion:
public void SomeMethod() {
// here you get your `list`
var tree = GetTree(list, 0);
}
public List<Tree> GetTree(List<Personal> list, int parent) {
return list.Where(x => x.ParentId == parent).Select(x => new Tree {
Id = x.Id,
Name = x.Name,
List = GetTree(list, x.Id)
}).ToList();
}
Same as above only this code checks for the case that your root node has a ParentID that matches its own ID.
public void SomeMethod()
{
// here you get your `list`
var tree = GetTree(list, 0);
}
public List<Tree> GetTree(List<Personal> list, int parent)
{
return list.Where(x => x.ParentId == parent).Select(x => new Tree
{
Id = x.Id,
Name = x.Name,
List = x.ParentId != x.Id ? GetTree(list, x.Id) : new List<Tree>()
}).ToList();
}