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

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; }
}

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;
}
}
}

Create complex JSON using C# object

I am trying to create the json from c# object which holds the data.
Data is in simple table format. Consist of Columns: Name, InputCode, DisplayID, CodeID, ParentID.
If Parent(Net) then ParentID is null. If Children(Subnet), has ParentID that holds CodeID of Parent(Net).
CodeID is Unique.
I am facing the issue with iternation under subnet using foreach. It dosen't allow.
public class CodeFrameJson
{
public string Name { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int? InputCodeValue { get; set; }
public int DisplayId { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<CodeFrameJson> Subnet { get; set; }
}
List<CodeFrameJson> cfj = new List<CodeFrameJson>();
IEnumerable<CodeDTO> _cfd = new List<CodeDTO>();
_cfd = codeFrameJson.GetCodeFrame(questionId, projectName);
_cfd = _cfd.OrderBy(a => a.DisplayOrderNo).ToList();
foreach (var a in _cfd)
{
int CodesID = 0;
CodeFrameJson obj = new CodeFrameJson();
if (a.InputCodeValue == null)
{
var root = new CodeFrameJson()
{
Name = a.CodeName,
DisplayId = a.DisplayOrderNo,
InputCodeValue = null,
Subnet = new List<CodeFrameJson>()
{
//Start: Not allowing foreach
foreach (var x in _cfd)
{
if (x.ParentId == CodesID)
{
new CodeFrameJson()
{
Name = x.CodeName,
InputCodeValue = x.InputCodeValue,
DisplayId = x.DisplayOrderNo
};
}
}
//End: Not allowing foreach
}
};
obj = root;
}
else {
var root = new CodeFrameJson()
{
Name = a.CodeName,
DisplayId = a.DisplayOrderNo,
InputCodeValue = a.InputCodeValue,
Subnet = null
};
obj = root;
}
cfj.Add(obj);
}
var json = JsonConvert.SerializeObject(cfj, Formatting.Indented);
Final Output Something like this which can be distinguished easily
{
"Site": {
"Name": "Site",
"DisplayID": 1,
"Subnet": [
{
"Name": "Full Site",
"InputCodeValue": 1,
"DisplayId": 2
},
{
"Name": "Partial Site",
"InputCodeValue": 2,
"DisplayId": 3
}
]
},
"Test": {
"Name": "Test1",
"InputCodeValue": 3,
"DisplayId": 4
}
}
This doesn't really have to do anything with JSON, but with object (collection) initialization. You can only assign values there, but LinQ comes to the rescue:
Simply filter your list and create the new objects in the select statement:
Subnet = _cfd.Where(x => x.ParentId == CodesID).Select(x => new CodeFrameJson
{
Name = x.CodeName,
InputCodeValue = x.InputCodeValue,
DisplayId = x.DisplayOrderNo
}).ToList()

How can I convert a list of items with ParentID into a tree?

I'm using SQL Server and Entity Framework. On my database I have the following data:
ID | Name | ParentID
1 | Fire | null
2 | Fire2 | 1
3 | Fire3 | 2
4 | Blast | 2
5 | Water | null
6 | Water2 | 5
7 | WaterX | 5
I won't have massive data, so retrieving everything at once from the database is perfectly acceptable.
I want to retrieve this data and display on screen as a "tree".
Fire
Fire2
Fire3 Blast
Water
Water2 WaterX
How should I do this? Should I create some sort of recursion to render it? Should I somehow convert the list to an IGrouping?
I'm having trouble converting the flat list into something that I can render hierarchically on the screen, how could I do that?
This is the easiest way I know:
var things = new []
{
new { Id = 1, Name = "Fire", ParentId = (int?)null },
new { Id = 2, Name = "Fire2", ParentId = (int?)1 },
new { Id = 3, Name = "Fire3", ParentId = (int?)2 },
new { Id = 4, Name = "Blast", ParentId = (int?)2 },
new { Id = 5, Name = "Water", ParentId = (int?)null },
new { Id = 6, Name = "Water2", ParentId = (int?)5 },
new { Id = 7, Name = "Waterx", ParentId = (int?)5 }
};
var tree = things.ToLookup(x => x.ParentId, x => new { x.Id, x.Name });
The tree looks like this:
That should be fairly easy to render now.
If you can add another property to your class that has the child items like this:
public class Thing
{
public Thing()
{
Things = new List<Thing>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public List<Thing> Things { get; set; }
}
Then you can easily group the items to their parents like this:
var things = new List<Thing>
{
new Thing { Id = 1, Name = "Fire", ParentId = null },
new Thing { Id = 2, Name = "Fire2", ParentId = 1 },
new Thing { Id = 3, Name = "Fire3", ParentId = 2 },
new Thing { Id = 4, Name = "Blast", ParentId = 2},
new Thing { Id = 5, Name = "Water", ParentId = null },
new Thing { Id = 6, Name = "Water2", ParentId = 5 },
new Thing { Id = 7, Name = "Waterx", ParentId = 6 }
};
var groupedThings = new List<Thing>();
foreach (var thing in things)
{
if (thing.ParentId != null)
{
things.First(t => t.Id == thing.ParentId).Things.Add(thing);
}
else
{
groupedThings.Add(thing);
}
}
groupedThings.Dump();

LINQ query output groups and subgroups in JSON

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.

Need help on developing LINQ Lambda expresssion to filter on a Schedule

I am having a hard time finding the lambda expression to call on a list to correctly filter an object structure. I was hoping someone here could help out. I am using .NET 3.5, and LINQ and the object domain is set up from Linq to SQL DBML. I specifically want to use lambda expressions.
The object structure is one where persons have settings, and one setting is a Schedule name, which corresponds to another schedule class with a StartDate and End Date.
public class MyPerson
{
public int ID { get; set; }
public List<MySetting> Settings { get; set; }
}
public class MySetting
{
public int ID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
public class MySchedule
{
public string ID { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Schedules have Start and End Dates, and a person's State can be Active or Scheduled.
List<MySchedule> schedules = new List<MySchedule>();
MySchedule scheduleA = new MySchedule { ID = "ScheduleA", StartDate = Convert.ToDateTime("1/1/2008"), EndDate = Convert.ToDateTime("12/31/2008") };
MySchedule scheduleB = new MySchedule { ID = "ScheduleB", StartDate = Convert.ToDateTime("1/1/2009"), EndDate = Convert.ToDateTime("12/31/2009") };
schedules.Add(scheduleA);
schedules.Add(scheduleB);
List<MySetting> settingsJill = new List<MySetting>();
MySetting settingFirstName = new MySetting { ID = 1, Name = "FirstName", Value = "Jill" };
MySetting settingScheduleName = new MySetting { ID = 2, Name = "ScheduleName", Value = "ScheduleB" };
MySetting settingState = new MySetting { ID = 3, Name = "State", Value = "Scheduled" }; // Jill uses ScheduleB
settingsJill.Add(settingFirstName);
settingsJill.Add(settingScheduleName);
settingsJill.Add(settingState);
List<MySetting> settingsBill = new List<MySetting>();
settingFirstName = new MySetting { ID = 1, Name = "FirstName", Value = "Bill" };
settingScheduleName = new MySetting { ID = 2, Name = "ScheduleName", Value = "ScheduleA" };
settingState = new MySetting { ID = 3, Name = "State", Value = "Scheduled" }; // Bill is Scheduled last year
settingsBill.Add(settingFirstName);
settingsBill.Add(settingScheduleName);
settingsBill.Add(settingState);
List<MySetting> settingsJane = new List<MySetting>();
settingFirstName = new MySetting { ID = 1, Name = "FirstName", Value = "Jane" };
settingScheduleName = new MySetting { ID = 2, Name = "ScheduleName", Value = "ScheduleA" };
settingState = new MySetting { ID = 3, Name = "State", Value = "Active" }; // Jane is Active
settingsJane.Add(settingFirstName);
settingsJane.Add(settingScheduleName);
settingsJane.Add(settingState);
List<MyPerson> persons = new List<MyPerson>();
MyPerson Jane = new MyPerson { ID = 1, Settings = settingsJane };
MyPerson Jill = new MyPerson { ID = 2, Settings = settingsJill };
persons.Add(Jane);
persons.Add(Jill);
persons.Add(Bill);
I want to filter the persons List by creating a lambda expression that will return persons with State = "Active" OR with a ScheduleName containing a current Date between the Start and End Date. In other words, Jill should appear in the filtered list since she is Scheduled, and she uses ScheduleB, and ScheduleB is for 2009, and today is 6/17/2009.
I am starting with
List<MyPerson> filter = persons.Select ( // and this is where I get stuck.
Thanks in advance for any help.
You need to rethink how you are storing the information. .NET Collectionns aren't SQL tables, and you shouldn't use them like so. That being said, here's the query you wanted:
Persons.Where(person =>
person.Settings.Any(setting=>
(setting.Name == "State" && setting.Value=="Active")) ||
person.Settings
.Where(setting=> setting.Name == "ScheduleName")
.Any(setting => { var sch = schedules
.First(schedule=>schedule.ID == setting.Value);
return sch.StartDate < DateTime.Now && sch.EndDate > DateTime.Now;
})
)
Imagine if you had a data structure like:
class Person {
Dictionary<string, string> Settings;
List<MySchedule> Schedules;
}
then your query would be
Persons.Where(person =>
Person.Settings["State"] == "Active" ||
Person.Schedules.Any(schedule =>
Datetime.Now > schedule.StartTime &&
DateTime.Now < schedule.EndTime
)
)
One problem is that you're starting with a Select. That's for projection - you're just trying to filter, so you only need Where.
Another problem is that your MyPerson type doesn't include a ScheduleName or State property. It's really not clear how it's all meant to hang together. If you could correct your example, I'm sure we can come up with the filter.

Categories

Resources