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.
Related
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);
Recently found out about AutoMapper's ProjectTo<> method, so I've been playing around with it.
So far so good until I came upon a class that had multiple properties of the same type, such as:
public class RandomDto
{
public int Id {get;set;}
public ChildDto FirstChild {get;set;}
public ChildDto SecondChild {get;set;}
}
It seems like it generates SQL for a single Child relationship, and not for both:
SELECT CASE
WHEN [dtoRandom].[FirstChild_FK] IS NULL
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END, [t0].[Child_Description]
END, [dtoRandom].[Id]
FROM [Randoms] AS [dtoRandom]
INNER JOIN (
SELECT [s].*
FROM [Childs] AS [s]
) AS [t0] ON [dtoRandom].[FirstChild_FK] = [t0].[Id]
I've tried
.ProjectTo<RandomDto>(null, "FirstChild", "SecondChild")
.ProjectTo<RandomDto>(x=>x.FirstChild, x=>x.SecondChild)
and both return with the first one being filled in, second being null
Not sure if I need to set custom aliases or something for this to work?
I experienced the same behaviour and tried to work around the problem by using an other class for the second navigation property that only derived from the original one.
public class ChildDtoTmp : ChildDto { }
public class RandomDto
{
public int Id { get; set; }
public ChildDto FirstChild { get; set; }
public ChildDtoTmp SecondChild { get; set; }
}
This worked fine, but because the type of the navigation proerty has another navigation property the same strange behaviour shows up with the nested navigation property.
public class ChildDto
{
public int Id { get; set; }
public InnerChildDto InnerChild { get; set; }
}
This leads to the properties FirstChild and SecondChild being mapped, but only the InnerChild property of the FirstChild gets mapped.
Maybe this helps someone to figure out how to solve this.
Your issue might be related to a slightly confusing option called MaxDepth. I've managed to reproduce that exact problem by calling MaxDepth(1), which in my understanding should only affect self-referencing entities such as:
class Foo
{
public Foo InnerFoo { get; set; }
}
In that case, a MaxDepth(1) should only map the first Foo found in a object graph. Which is exactly what it happens, but it also affects the following structure (quite wrongly, IMHO):
class Bar
{
public Foo Foo1 { get; set; }
public Foo Foo2 { get; set; }
}
A MaxDepth(1) in the above scenario will map only the Foo1 property, keeping Foo2 as null.
Oh, by the way: to set MaxDepth, one may apply to all their mappings:
var mapperCfg = new MapperConfiguration(cfg =>
{
cfg.AddProfile<SomeProfile>();
cfg.ForAllMaps(SetMaxDepth);
});
private static void SetMaxDepth(TypeMap typeMap, IMappingExpression expression) => expression.MaxDepth(1);
Or to each map individually:
var mapperCfg = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SomeObject, SomeObjectDto>().MaxDepth(1);
});
I have two tables in my database that are linked with 2x 1 to many relations to the same object.
Since we added the second DBLot2 to the database the list in DBLot is not filled with objects anymore.
Is there something we did wrong here?
public class DBNesting
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long DBNestingID { get; set; }
public DBLot DBLot { get; set; }
[ForeignKey("DBLot")]
public long DBLotID { get; set; }
public DBLot DBLot2 { get; set; }
[ForeignKey("DBLot2")]
public long? DBLot2ID { get; set; }
}
public class DBLot
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long DBLotID { get; set; }
public List<DBNesting> Nestingen { get; set; }
}
This is how we get the objects:
DatabaseContext dc = new DatabaseContext();
dc.DBNesting
.include("DBLot")
.include("DBLot2")
.where(...)
.ToList();
However the other side is not working:
dc.DBLot
.include("Nestingen")
.where(...)
.ToList()
I would expect that all the DBNesting where we used a DBLot in property
DBLot ore DBLot2 shoud be in Nestingen. But the collections are empty.
dc.DBLot
.include("Nestingen")
.where(...)
.ToList()
will not include the DBLot on the Nestingen only the direct object.
So it will have DBLot and a list of Nestingen but that list will not have the DBLot of each of the Nestingen in the list.
So basically you should be able to see... that you have recursion here, object has reference to object which reference itself.
dc.DBLot.include("Nestingen")
.include("Nestingen.DBLot")
.include("Nestingen.DBLot2")
.where(...)
.ToList()
may work, again will only now bring one level deeper, but if that all you need then awesome.
you could enable lazy loading... but also come with "responsibility" wouldn't recommend
ef 6 is not very efficient with include. also there is an extension which allows you to use typed version so include(x=>x.Nestingen), just take the string names out.
is the goal to have nested object relations.. nth levels. something like
Tree data structure in C#
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);
}
}
I have a viewmodel that needs to display a certain IEnumerable field as semicolon-separated textbox. At first I thought of using DefaultModelBinder to transform it, but I had trouble thinking how to achieve it in both directions (dto <-> viewmodel).
Nicknames is the field I'm trying to display as one textbox separated by semicolon.
public class Parent
{
public IEnumerable<Child> Children { get; set; }
}
public class Child
{
public IEnumerable<string> Nicknames { get; set; }
}
So I decided to try AutoMapper, I created two ViewModels:
public class ParentViewModel
{
public IEnumerable<ChildViewModel> Children { get; set; }
}
public class ChildViewModel
{
public string Nicknames { get; set; }
}
Then, I created mappings, like this for the children (omitted the other-way conversion for brevity)
Mapper.CreateMap<Child, ChildViewModel>().ForMember(
d => d.Nicknames, o => o.ResolveUsing<ListToStringConverter>().FromMember(s => s.Nicknames);
Then, for the parent, created a naive map (again, omitted the other-way)
Mapper.CreateMap<Parent, ParentViewModel>();
I truly expected the child mappings occur automatically, but they don't, I've already created too much "proper" code to solve a really simple problem which in any other simpler/older non-MVC environment, I'd be done with a long time ago :) How can I proceed and tell AutoMapper to transform the children without writing another "children member resolver".
Have I overthought this and there's a simpler way?
Thank you!
try
Mapper.CreateMap<Parent, ParentViewModel>();
Mapper.CreateMap<Child, ChildViewModel>();
var v = Mapper.Map<Parent, ParentViewModel>(parent);
Found this solution https://stackoverflow.com/a/7555977/1586498, that works for me:
Mapper.CreateMap<ParentDto, Parent>()
.ForMember(m => m.Children, o => o.Ignore()) // To avoid automapping attempt
.AfterMap((p,o) => { o.Children = ToISet<ChildDto, Child>(p.Children); });
The ToISet function is defined in the above link.
Simpler examples 'just work' in LinqPad - so more investigation is required.
A complete listing of a working program:
public class Child{ public string Name {get; set; }}
public class ChildDto{ public string NickName {get; set; }}
public class Parent{ public virtual IEnumerable<Child> Children {get; set; }}
public class ParentDto{ public IEnumerable<ChildDto> Kids {get; set; }}
private static void Main()
{
AutoMapper.Mapper.CreateMap<Parent, ParentDto>().ForMember(d=>d.Kids, opt=>opt.MapFrom(src=>src.Children));
AutoMapper.Mapper.CreateMap<Child, ChildDto>().ForMember(d=>d.NickName, opt=>opt.MapFrom(src=>src.Name));
var pList = new HashSet<Parent>{
new Parent{ Children = new HashSet<Child>{new Child{Name="1"}, new Child{Name="2"}}},
new Parent{ Children = new HashSet<Child>{new Child{Name="3"}, new Child{Name="4"}}},
};
var parentVm = AutoMapper.Mapper.Map<IEnumerable<Parent>, IEnumerable<ParentDto>>(pList);
parentVm.Dump();
}