LINQ query output groups and subgroups in JSON - c#

I have a table like this (Groups):
ID Name ParentID
1 Group 1 null
2 Group 2 null
3 SubGr 1-1 1
4 SubGr 1-2 1
5 SubGr 2-1 2
6 Group 3 null
7 SubGr 1-2-1 4
..... and so on
I want to serialize this to JSON looking like this:
[{"id":1,
"name":"Group 1",
"children": [
{
"id":3,
"name":"SubGr 1-1",
"children":null
},{
"id":4,
"name":"SubGr 1-2",
"children": [
{
"id":7,
"name":"SubGr 1-2-1",
"children": null
}
]
}
]
},
{"id":2,
"name":"Group 2",
"children": [
{
"id":5,
"name":"SubGr 2-1",
"children":null
}
]
},
{"id":6,
"name": "Group 3",
"children": null
}
]
As you can see, you can have indefinite subgroups.
How can I make such a query in LINQ and output it in JSON like the example above?
I have no problem outputting the JSON as seperated elements, with ParentID, but I need to have the structure as mentioned above.
This is the code that I am currently working with, after trying different things around, but with no luck still (this version only gives two levels):
public ActionResult GetGroups()
{
var groupobjs = db.GroupObjs.ToList();
var items = groupobjs.Where(p => p.ParentID == null).Select(p => new
{
id = p.ID,
name = p.Name,
children = groupobjs.Where(c => c.ParentID == p.ID).Select(c => new {
id = c.ID,
name = c.Name
})
});
return Json(items, JsonRequestBehavior.AllowGet);
}

I was working on some code similar to what #Hunter-974 recommended.
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public List<Group> Subgroups { get; set; }
public Group()
{
this.Subgroups = new List<Group>();
}
}
class Program
{
static void Main()
{
Group[] groups = new Group[]
{
new Group { Id = 1, Name = "Group 1", ParentId = null },
new Group { Id = 2, Name = "Group 2", ParentId = null },
new Group { Id = 3, Name = "SubGr 1-1", ParentId = 1 },
new Group { Id = 4, Name = "SubGr 1-2", ParentId = 1 },
new Group { Id = 5, Name = "SubGr 2-1", ParentId = 2 },
new Group { Id = 6, Name = "Group 3", ParentId = null },
new Group { Id = 7, Name = "SubGr 1-2-1", ParentId = 4 }
};
foreach (Group g in groups)
if (g.ParentId.HasValue)
groups.Single(group => group.Id == g.ParentId.Value).Subgroups.Add(g);
var rootgroups = groups.Where(g => g.ParentId == null);
JavaScriptSerializer js = new JavaScriptSerializer();
Console.WriteLine(js.Serialize(rootgroups));
}
}

I think that you should use a recursive methode instead of LINQ to do this.
1) Define a class which represent a group, with a property ID (int), a property Name (string), a property Children (List), a property Parent (int?, not serialized) and a method Serialize() (who calls Serialize for each Group in Children)
2) Define a List that will contain the "root" groups, and a List that will contain all the groups.
3) For each row of your datatable, create an object Group. Define all its properties. obviously, the list of children will be empty.
4) For each Group, if the parent's ID is not null, add it to his parent. This way, you will fill the Children list, for all the Groups.
5) For each Group, if the parent's ID is null, add the Group to the Roots list.
6) For each Group in the Roots list, call the method Serialize.
I hope this will help you. Ask me if you want more explanation or code instead of them.

Related

List of descendant ID - children, grandchildren etc

CREATE TABLE [dbo].[Topic] (
[Id] SMALLINT NOT NULL,
[ParentId] SMALLINT NULL
);
I have a simple table (above) with a parent/child hierarchy. I'm using Entity Framework to extract the data. The number of rows is less than 100.
I want to get a list of descendant ID, consisting of the children, grandchildren and so on (possibly with the option of including the original root parent). As the table only contains a small number of rows, it's probably easier to extract all the data to a List<Topic> first and work on that, but I stand to be corrected.
The preferred output would be: Dictionary<int, List<int>>
Where the key would be the ID and the list would contain child/grandchild ID's
I've looked at tens and tens of solutions online but I can't find a solution that meets my needs. Can anyone help?
You could populate a dictionary with the ParentId->Id relations and use that to resolve sub-topics:
// prepare dictionary
var dictionary = new Dictionary<short, List<Topic>>();
// in real life this would get replaced by your database query
var topics = new List<Topic>
{
new Topic { Id = 1 },
new Topic { Id = 2, ParentId = 1 },
new Topic { Id = 3, ParentId = 1 },
new Topic { Id = 4, ParentId = 1 },
new Topic { Id = 5, ParentId = 1 },
new Topic { Id = 6, ParentId = 2 },
new Topic { Id = 7, ParentId = 2 },
new Topic { Id = 8, ParentId = 3 },
new Topic { Id = 9, ParentId = 4 },
new Topic { Id = 10, ParentId = 4 },
new Topic { Id = 11, ParentId = 8 },
new Topic { Id = 12, ParentId = 8 }
};
// populate dictionary with relations from DB
foreach(var topic in topics)
{
var key = topic.ParentId ?? -1;
if(!dictionary.ContainsKey(key))
{
dictionary.Add(key, new List<Topic>());
}
dictionary[key].Add(topic);
}
Now that you have the mappings available, you can write a simple recursive iterator method to resolve the descendants of a given id:
IEnumerable<short> GetDescendantIDs(short from)
{
if(dictionary.ContainsKey(from))
{
foreach(var topic in dictionary[from])
{
yield return topic.Id;
foreach(var child in GetDescendants(topic.Id))
yield return child;
}
}
}
// resolves to [1, 2, 6, 7, 3, 8, 11, 12, 4, 9, 10, 5]
var descendantsOfRoot = GetDescendantIDs(-1);
// resolves to [8, 11, 12]
var descendantsOfThree = GetDescendantIDs(3);
The Topic class used in the example above is just:
class Topic
{
internal short Id { get; set; }
internal short? ParentId { get; set; }
}
Consider that result has to be stored in tree:
public class TopicModel
{
public int Id { get; set; }
public int? ParentId{ get; set; }
public List<TopicModel> Children { get; set; };
}
Building tree:
// retrieveing from database
var plainResult = context.Topic
.Select(t => new TopicModel
{
Id = x.Id
ParentId = x.ParentId
})
.ToList();
var lookup = plainResult.Where(x => x.ParentId != null).ToLookup(x => x.ParentId);
foreach (c in plainResult)
{
if (lookup.Contains(c.Id))
{
c.Children = lookup[c.Id].ToList();
}
}
// here we have all root items with children intialized
var rootItems = plainResult.Where(x => x.ParentId == null).ToList();
And searching for children:
public static IEnumerable<TopicModel> GetAllChildren(TopicModel model)
{
if (model.Children != null)
{
foreach (var c in model.Children)
{
yield return c;
foreach (sc in GetAllChildren(c))
yield return sc;
}
}
}

How can I remove an item from an array nested in another array with Mongo/C#

I've found plenty of examples showing how to remove an item from an array within the root of a collection of documents and I can successfully add items to nested arrays. But I can't seem to make headway on removing or updating an item in a nested array.
An example would be where I have a collection of groups. In that group, there is an array(list) of Members. Each of those members could have multiple nicknames that they go by. I need to know how to go about deleting/updating a Nickname for a specific member.
Ex:
I wanted to update Member: Robert James' Nickname "Not Rick James" to "Rick"
Delete the Nickname "Smithy" from Member John Smith
// example mongo data
[{
"_id": 482232389408781300,
"Name": "Group A",
"Members": [
{
"_id": "X4cLx72J9",
"Name": "John Smith",
"NickNames: [
{
"_id": "V2889Jw8",
"Name": "Smithy"
},
{
"_id": "V82lvi2",
"Name": "Ol John"
}
]
},
{
"_id": "X4c7872J9",
"Name": "Robert James",
"NickNames: [
{
"_id": "V2Bl9Jw8",
"Name": "Not Rick James"
},
{
"_id": "V8Qrvi2",
"Name": "Slick"
}
]
}
}
]
// c# classes
class Group{
public ulong Id {get;set;} // Unique Id
public string Name {get;set;}
public List<Member> Members{get;set;}
}
class Member{
public string Id {get;set;} // Unique Id
public string Name {get;set;}
public List<NickName> NickNames{get;set;}
}
public NickName{
public string Id {get;set;} // Unique Id
public string Name {get;set;}
}
Editing for clarification
I'm needing to know how to complete this request using the MongoDB driver for C#
var guilds = db.GetCollection<Group>("groups");
var filter = Builders<Group>.Filter;
/// At this point I'm unsure how the filter/update values should be coded in order to update the mongo db.
Assuming this is your data,
List<NickName> nickNameList = new List<NickName>();
nickNameList.Add(new NickName() { Id = "V2889Jw8", Name = "Smithy" });
nickNameList.Add(new NickName() { Id = "V82lvi2", Name = "Ol John" });
List<NickName> nickNameList2 = new List<NickName>();
nickNameList2.Add(new NickName() { Id = "V2Bl9Jw8", Name = "Not Rick James" });
nickNameList2.Add(new NickName() { Id = "V8Qrvi2", Name = "Slick" });
List<Member> members = new List<Member>();
members.Add(new Member() { Id = "X4cLx72J9", Name = "John Smith", NickNames = nickNameList });
members.Add(new Member() { Id = "X4c7872J9", Name = "Robert James", NickNames = nickNameList2 });
List<Group> group = new List<Group>();
group.Add(new Group(){
Id = 482232389408781300,
Name = "Group A",
Members = members
});
We can use the .Find() method of List to find the specific record in the list and update or delete it.
For example:
//Update
group.Find(x => x.Id == 482232389408781300).Members.Find(x => x.Id == "X4c7872J9").NickNames.Find(n => n.Name == "Not Rick James").Name = "Rick";
//Delete
List<NickName> nickNames = group.Find(x => x.Id == 482232389408781300).Members.Find(x => x.Id == "X4cLx72J9").NickNames;
nickNames.Remove(nickNames.Find(n => n.Name == "Smithy"));
I think I've worked out two methods that work for me. They might not be the most efficient so I will continue reading more into. But here is what I have thus far.
The following deletion method uses the positional all '$[]' option as each Nickname has a unique ID.
// Delete An array item
var groupId = 482232389408781300;
var memberId = "X4cLx72J9";
var nickId = "V2889Jw8";
var collection = _db.GetCollection<Group>("groups");
var filter = Builders<Group>.Filter.Eq(group => group.Id, groupId);
var update = Builders<Group>.Update.PullFilter("Members.$[].NickNames", Builders<BsonDocument>.Filter.Eq("_id", nickId));
collection.UpdateOne(filter, update);
The update method is using the ArrayFilters option. I imagine you could use this for the deletion method as well.
// Update An array item
var groupId = 482232389408781300;
var memberId = "X4cLx72J9";
var nickId = "V2889Jw8";
var newNick = "Loon";
var collection = _db.GetCollection<Group>("groups");
var filter = Builders<Group>.Filter.Eq(group => group.Id, groupId);
var update = Builders<Group>.Update.Set("Members.$[m].NickNames.$[n].Name", newNick);
var filterArray = new List<ArrayFilterDefinition>{
new JsonArrayFilterDefinition<Group>(string.Format("{{'m._id': '{0}'}}", memberId)),
new JsonArrayFilterDefinition<Group>(string.Format("{{'n._id': '{0}'}}", nickId))
};
var updateOptions = new UpdateOptions { ArrayFilters = filterArray };
collection.UpdateOne(filter, update, updateOptions);

Linq Groupby return original object

I need some help filtering some data. I've got an object class with three properties. The collection of objects I've got can have many matches of the first property, Point3d. From that collection of matches I need to see if the second property has unique values, Tag. Finally, I need to be able to identify the objects whos Point3d match, and Tags are different, using the third property, it's Id (which is always unique).
class pmatch
{
public string Point3d { get; set; }
public string Tag { get; set; }
public string Id { get; set; }
}
An example of what i'm looking for would be:
List<pmatch> dataset = new List<pmatch>
{
new pmatch { Point3d = "1, 1, 1", Tag = "5", Id = "123" },
new pmatch { Point3d = "1, 1, 1", Tag = "6", Id = "124" },
new pmatch { Point3d = "1, 1, 2", Tag = "7", Id = "125" },
new pmatch { Point3d = "1, 1, 2", Tag = "7", Id = "126" }
};
I need to be able to identify Id's 123 and 124, as their Point3ds match, but their Tags do not. I've been able to identify these instances using LINQ:
var result = datalist.GroupBy(item => item.Point3d, item => item.Tag);
foreach (var group in result)
{
Console.WriteLine(group.Key);
var uniqueTags = group.Distinct().ToList();
if (uniqueTags.Count > 1)
{
Console.WriteLine("Found mismatched tags");
foreach (string Tag in group)
{
Console.WriteLine(" {0}", Tag);
}
}
}
However these results do not give me the Id, so I can not access the object I have identified. How do I get these results along with the Id, or the pmatch object itself?
You can accomplish the desired result like so:
var resultSet =
dataset.GroupBy(item => item.Point3d)
.Where(group => group.Select(item => item.Tag)
.Distinct()
.Count() > 1)
.ToDictionary(item => item.Key, item => item.ToList());
This will identify Id's 123 and 124, as their Point3ds match, but their Tags do not and also resultSet is of type Dictionary<string, List<pmatch>> so you have access to all the properties of the pmatch object.

How to flatten nested dictionaries within Class using LINQ

The closest solution to what I was looking for is this thread How to flatten nested objects with linq expression
But I get an error trying that approach
The type arguments for method 'System.Linq.Enumerable.SelectMany(System.Collections.Generic.IEnumerable, System.Func>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
My code:
var aa = t.data.SelectMany(x =>
x.Value.innerData.SelectMany(y => new { /*Error at this SelectMany*/
url = x.Key,
disp = x.Value.disp,
date = y.Key,
count = y.Value.count,
rank = y.Value.rank,
}));
My classes:
public class TData {
public Dictionary<string, TDetail> data { get; set; }
}
public class TDetail {
public string disp { get; set; }
[Newtonsoft.Json.JsonProperty("data")]
public Dictionary<string, Metrics> innerData { get; set; }
}
public class Metrics {
public string count { get; set; }
public string rank { get; set; }
}
The JSON I get from a 3rd party API looks like below:
{
"data": {
"abc.com": {
"disp": "#712176",
"data": {
"2015-02-08": {
"count": 4,
"rank": 5.8
},
"2015-02-23": {
"count": 3,
"rank": 8.3
},
"2015-03-14": {
"count": 5,
"rank": 3.7
}
}
},
"nbc.com": {
"disp": "#822176",
"data": {
"2015-02-08": {
"count": 3,
"rank": 5.5
},
"2015-02-23": {
"count": 5,
"rank": 8.4
},
"2015-03-14": {
"count": 7,
"rank": 4.7
}
}
}
}
}
How do I specify the type arguments explicitly in this case? Thanks.
Too many SelectMany:
var t = new TData(); // your TData
var aa = t.data.SelectMany(x =>
x.Value.innerData.Select(y => new
{
url = x.Key,
disp = x.Value.disp,
date = y.Key,
count = y.Value.count,
rank = y.Value.rank,
}));
The inner one must be a Select.
SelectMany projects every individual item into a sequence of items (and then flattens it). Your outer SelectMany is projecting each item into a sequence, but your inner SelectMany is projecting each item into single items that aren't sequences. If you want to project each item in a sequence into a single item then you need to use Select, not SelectMany.

Flattening a parent/child object-chain, merging values along the way

We have been staring at this problem for ages, losing valuable time.
We have these objects, lets call them Components. Our application allows you to create a new Component, based on an existing Component.
All values of the parent are 'inherited' (read: accessible via a component.Parent property) unless you override a value -- e.g. you want to give it a new name, but keep the rest of the parent values.
// Exiting parent-child object chain
BaseObject {Id = 1, Name = "Base", Description = "Base description", Notes = "Awesome object", ParentId = null}
ChildObject1 {Id = 2, Name = "", Description = "", Notes = "", ParentId = 1}
ChildObject2 {Id = 3, Name = "Custom Name", Description = "", Notes = "", ParentId = 2}
ChildObject3 {Id = 4, Name = "", Description = "Final object", Notes = "", ParentId = 3}
Now I want to flatten this from the TOP DOWN, using existing values of the parent for any empty values on the child.
// End result after flattening/merging
ChildObject3 {Id = 4, Name = "Custom Name", Description = "Final object", Notes = "Awesome object", ParentId = 3}
Noteworthy: there is no child property, only the child knows about a parent. The parent is unaware of the child.
How to solve this without ugly while(child.Parent != null) constructions and creating previousComponent, currentComponent, etc. objects to keep track of set values.
How about a recursive DeepCopy method, something like:
public class ComponentClass()
{
public ComponentClass DeepCopy(ComponentClass comp)
{
CopyAllNonEmptyPropertiesTo(comp);
if (comp.StillHasEmptyProperties())
return Parent.DeepCopy(comp);
else
return comp;
}
}
Create a Dictionary of your data objects so you lookup your parents.
For each of the items you have, do a recursive lookup of parent values when:
You don't have a value for one of the target properties
You have a parent
E.g. (the Dump() command is from LinqPad)
var data = new List<Bob>
{
new Bob { Id = 1, Name = "Base", Description = "Base description", Notes = "Awesome object", ParentId = null },
new Bob { Id = 2, Name = "", Description = "", Notes = "", ParentId = 1 },
new Bob { Id = 3, Name = "Custom Name", Description = "", Notes = "", ParentId = 2 },
new Bob { Id = 4, Name = "", Description = "Final object", Notes = "", ParentId = 3 },
};
var map = data.ToDictionary(d => d.Id, d => d);
data.ForEach(row => CopyFromParent(map, row, row.ParentId.HasValue ? map[row.ParentId.Value] : null));
data.Dump();
}
void CopyFromParent(Dictionary<int, Bob> map, Bob target, Bob current)
{
// set properties
if (current != null && string.IsNullOrEmpty(target.Name) && !string.IsNullOrEmpty(current.Name)) target.Name = current.Name;
if (current != null && string.IsNullOrEmpty(target.Description) && !string.IsNullOrEmpty(current.Description)) target.Description = current.Description;
if (current != null && string.IsNullOrEmpty(target.Notes) && !string.IsNullOrEmpty(current.Notes)) target.Notes = current.Notes;
// dive deeper if we need to, and if we can
var needToDive = string.IsNullOrEmpty(target.Name) || string.IsNullOrEmpty(target.Description) || string.IsNullOrEmpty(target.Notes);
var canDive = current != null && current.ParentId.HasValue && map.ContainsKey(current.ParentId.Value);
if (needToDive && canDive)
{
CopyFromParent(map, target, map[current.ParentId.Value]);
}
}
class Bob
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
public int? ParentId { get; set; }
}

Categories

Resources