Let's say we have an entity with the following properties:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
}
A child is any Foo that has a non-null ParentId. There can be several levels of parent/child relationships, up to 5 at this point.
Is there a simple LINQ way or general methodology to begin with a Foo and get all of its children, and its children's children, and it's children's children's children, etc?
Currently, I'm looping through each child of the beginning parent, then looping again to get all of that child's children, etc. This is tedious and doesn't appear to be the correct way of accomplishing what I want.
I prefer to maintain a string property that represents a place in the hierarchy. For example, if the value was "/99/42" then you know the item belongs to parent 42 which belongs to 99. The reason this is nice is because then you can flatten out the whole collection of Foos and just query that field for those beginning with "/99" and you'll get the entire branch of the hierarchy. So your class would look like this:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public string Path { get; set; } //eg: /99/42
}
Hope that helps.
So if all you have is one Foo object then no, you cannot get all of it's descendants. At the very least, you'd need to have a sequence of all Foo objects as well in order to find the nodes that have that one object as a parent, since your Foo object doesn't already have a reference to it's children. If you do have that sequence, then it's not particularly hard.
You can use ToLookup on the sequence of all foos to create a lookup of ID to all children of that ID value:
var allFoos = new List<Foo>();
var childrenLookup = allFoos.ToLookup(foo => foo.ParentId);
To get a sequence of all descendants for a particular child you now have simple tree traversal:
public static IEnumerable<Foo> Descendants(Foo foo, ILookup<int?, Foo> childrenLookup)
{
var stack = new Stack<Foo>();
stack.Push(foo);
while (stack.Any())
{
var next = stack.Pop();
yield return next;
foreach (var child in childrenLookup[next.Id])
stack.Push(child);
}
}
Related
I have a class which is self-referenced.
public class MyPerfectClass
{
public bool IsActive { get; set; }
public Guid? ParentId { get; set; }
public MyPerfectClass? Parent { get; set; }
private readonly List<MyPerfectClass> _children = new();
public IReadOnlyCollection<MyPerfectClass> Children => _children.AsReadOnly();
}
I need to get all 'MyPerfectClass' where ChildItem's IsActive is true. I can write two seperate queries but can't get parents & childrens based on childrens filtering. Any suggestion?
Is this what you are looking for?
List<MyPerfectClass> parentsList = new List<MyPerfectClass>();
var results = parentsList.Where(p => p.Children.Any(c => c.IsActive));
ALL, ANY and other queries can be used on nestled objects.
MyPerfectClass having an instance of MyPerfectClass to define parent seems a little off. The Parent object instance on the MyPerfectClass may not share the children defined on MyPerfectClass where parent is defined. May lead to confusion.
I'm re-asking this from a question a couple of days ago now I've whittled the problem down.
Two simple objects:
public class Parent
{
public int Id { get; set; }
public virtual Child Child { get; set; }
public string Name { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
I find a Parent object using a DbContext method e.g.
Parent parentToUpdate = _context.Parent.Find(1);
This object comes equipped with a populated child already, say with Id 22, generated as a System.Data.Entity.DynamicProxy
I then have a new child object which becomes a null because it wasn't found in the database, using the same DbContext:
Child newChild = _context.Child.Find(999); // returns null
I then try to overwrite the parentToUpdate object's child with the newChild object:
parentToUpdate.Child = newChild;
I expect .Child to become null - This doesn't work unless I step through the code - The parentToUpdate.Child doesn't become null!
WHY? and How can I nullify my parentToUpdate.Child object? before I do _context.SaveChanges()
Ok so thanks to the breadcrumb of lazy loading I ended up circling back on Include which I'd glossed over earlier looking for a solution.
It's as simple as changing the Find in context statement from
Parent parentToUpdate = _context.Parent.Find(1);
To
Parent parentToUpdate = _context.Parent.Include(x => x.Child).Where(x => x.Id == 1);
There are a lot of related questions in SO, but no one helped me.
I have an entity like this:
class TaskObject
{
public int Id { get; set; }
public string Title { get; set; }
public int? ParentId { get; set; }
public TaskObject Parent { get; set; }
public ICollection<TaskObject> Children { get; set; }
}
And I want to get all the elements of the specific branch as a list.
For example, I have a tree like this:
Task1
Task2
Task4
Task5
Task3
Task6
The result of my query would be Task2 Task4 Task5.
I wrote a function that makes it, but I think there is a better solution:
private void getSubtree(TaskObject parent, List<TaskObject> items)
{
items.Add(parent);
context.Entry(parent).Collection(t => t.Children).Load();
foreach (var child in parent.Children)
{
getSubtree(child, items);
}
}
Is there smth like context.Entry(parent).GetAllChildren() in EF Core?
No there is no any method like that (GetAllChildren()) in EntityFramework.
When you are working with tree, you can cache some paths to improve you searches and navigation.
If you are going to search some nodes then load their path until top of tree:
define a property for tree's node and save all nodes until top of tree. when you move a node you should update that path property too.
I have some data modeled as a simple set of nested c# objects, that I am trying to create/retrieve from a neo4j database using the .Net Neo4jClient.
My classes are in the form:
public class Foo {
public int ID { get; set; }
public List<Bar> bar { get; set;}
}
public class Bar {
public int ID { get; set; }
public List<Baz> baz { get; set;}
}
public class Baz {
public int ID { get; set; }
}
Once the data has been stored in the correct form in the database:
(f:Foo)-[h:HASBAR]->(b:Bar)-[hb:HASBAZ]->(bz:Baz)
I am able to retrieve the data into my class structure using collect and optional match using the following query:
List<Foo> foolist = WebApiConfig.GraphClient.Cypher
.Match("(f:Foo)-[h:HASBAR]->(b:Bar)")
.OptionalMatch("(b)-[hb:HASBAZ]->(bz:Baz)")
.With("f, { ID: b.ID, baz: collect(bz) } as Bar")
.With("{ ID:f.ID, bar:collect(Bar) } as Foo")
.Return<Foo>("Foo")
.Results
.ToList();
This all works perfectly and the data is correctly serialized into the proper classes.
My question is how should I perform the reverse?
As in given a single Foo class, containing multiple bar and baz classes nested, can I create the above data structure in the database in a single query?
Or do I have to write a query per each level of nesting?
I know I will probably have to list properties when creating, as if I give the client a Foo class it will create a node with "bar" as a property.
My problem mostly comes from the third level of nesting, if I treat the second level (bar) as an array (passing in Foo.bar) as a variable, I can create multiple [:HASBAR] relationships. But within the same query I have not found a way to relate the correct Baz node with the Bar node.
Am I approaching this in the correct way?
Any responses are appreciated, thanks in advance...
Well, it is possible to do it in one query - unfortunately I don't think you can use the tasty UNWIND or FOREACH due to the secondary nesting, and you'll need to do some funky things with the classes, but well, here goes:
First, we need to define the classes, so we can deserialize the properties, but not serialize them, to that end
public class Foo
{
public int ID { get; set; }
[JsonIgnore]
public List<Bar> bar { get; set; }
[JsonProperty("bar")]
private List<Bar> barSetter { set { bar = value;} }
}
public class Bar
{
public int ID { get; set; }
[JsonIgnore]
public List<Baz> baz { get; set; }
[JsonProperty("baz")]
private List<Baz> bazSetter { set { baz = value; } }
}
public class Baz
{
public int ID { get; set; }
}
What is this craziness??!?! Well - By using [JsonIgnore] we tell Json not to serialize or deserialize a given property - but we want to deserialize so your retrieval query will work - so having the private setter with JsonProperty allows us to achieve this.
The added bonus of this approach is that you don't need to specify properties to serialize in the Cypher generation bit. And here it is in all it's glory:
var query = gc.Cypher
.Create("(f:Foo {fooParam})")
.WithParam("fooParam", foo);
for (int barIndex = 0; barIndex < foo.bar.Count; barIndex++)
{
var barIdentifier = $"bar{barIndex}";
var barParam = $"{barIdentifier}Param";
query = query
.With("f")
.Create($"(f)-[:HASBAR]->({barIdentifier}:Bar {{{barParam}}})")
.WithParam(barParam, foo.bar[barIndex]);
for (int bazIndex = 0; bazIndex < foo.bar[barIndex].baz.Count; bazIndex++)
{
var bazIdentifier = $"baz{barIndex}{bazIndex}";
var bazParam = $"{bazIdentifier}Param";
query = query
.With($"f, {barIdentifier}")
.Create($"({barIdentifier})-[:HASBAZ]->({bazIdentifier}:Baz {{{bazParam}}})")
.WithParam(bazParam, foo.bar[barIndex].baz[bazIndex]);
}
}
The f:Foo bit is as per normal, the subsequent for loops allow you to define each identifier and set the parameters.
I don't think this is an ideal solution, but it will work, and will execute in 1 query. Obvs, this could get unwieldy with lots of nested values.
Suppose we have these two classes:
public class Parent
{
public int ID { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public int ParentID { get; set; }
public virtual Parent Parent { get; set; }
}
Suppose I create one of each with the following methods:
//Create a parent with new children
public void CreateParent(MyDbContext context)
{
context.Parents.Add(new Parent
{
Children = new List<Child>()
{
new Child(),
new Child(),
new Child()
}
});
context.SaveChanges();
}
//Create a child with a new parent
public void CreateChild(MyDbContext context)
{
context.Children.Add(new Child
{
Parent = new Parent()
});
context.SaveChanges();
}
Will either of these methods create both Parent and Child objects with foreign keys appropriately assigned? My gut tells me that CreateParent would work, but CreateChild would not.
Thank you!
Yes, both of these methods will create the Parent and Child records as well with the ParentID foreign key set. If you run e.g. the first one, the result you will get will be the following:
And as another answer states you don't need the ParentID however Entity Framework will recognize it as the Foreign Key used for the Parent association and you don't have to use the virtual keyword neither at the Children nor at the Parent property to make your code work, however if you wan't Lazy Loading to work with these properties you will need them.
Yes both functions work correctly, adding both Parent and Child records with the correct association between them.
Running CreateParent will yield one new Parent record and three Child records associated with it (through ParentID).
Running CreateChild will create one new Child and one new Parent record, associated correctly.