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);
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.
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);
});
Example structure
public class Page
{
public int PageId { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public virtual List<Section> Sections { get; set; }
}
public class Section
{
public int SectionId { get; set; }
public int PageId { get; set; }
public virtual Page Page { get; set; }
public virtual List<Heading> Headings { get; set; }
}
public class Heading
{
public int HeadingId { get; set; }
public int SectionId { get; set; }
public virtual Section Section { get; set; }
}
It's worth noting that my actual structure has more levels than this but this should be enough to explain what I'm trying to achieve.
So I load my Page object I then Clone that object and make some minor changes to the properties of Page i.e. Prop1, Prop2
Page pageFromDb = getPageMethod();
Page clonedPage = pageFromDb.Clone();
clonedPage.Prop1 = clonedPage.Prop1 + " Cloned";
addPageMethod(clonedPage); //Adds the page to db
In the example above clonedPage structure is fine and a new Page is added to the database. However I believe because the Id's of the child objects are set and the relationship of the children is always one to many. The original object pageFromDb will lose all it children as entity framework instead of creating new Section objects for the cloned Page will update the Section.PageId to the newly inserted page.
I believe a fix for this would be to foreach, foreach, etc. and set all the Id's to 0 before inserting then entity framework will create new records foreach object. Is there any easier/better way to clone an object in an entity framework environment.?
In order for Entity Framework to treat the clone as an entire new object graph when persisting the graph, all entities in the graph need to be disconnected from the context in which the root entity was retrieved.
This can be done using the AsNoTracking method on the context.
For example, this will pull a page and associated sections graph from the database and turn off tracking. Effectively this is a clone as if you add this to your Page DbSet and save it will create an entirely new object graph in the database. I.e. a new Page entity and new Section entities accordingly. Note, you wont need to call your Clone method.
var clone = context.Pages
.AsNoTracking()
.Including(pages => pages.Sections)
.Single(...);
context.Pages.Add(clone);
context.SaveChanges(); // creates an entirely new object graph in the database
Try this!
public Page CopyPage(int pageID)
{
using(Context context = new Context())
{
context.Configuration.LazyLoadingEnabled = false;
Page dbPage = context.Pages.Where(p => p.PageId == pageID).Include(s => s.Sections.Select(s => s.Section)).First();
Page page = dbPage.Clone();
page.PageId = 0;
for (int i = 0; i < dbPage .Sections.Count; i++)
page.Sections[i] = new Section
{
SectionId = 0,
PageId = 0,
Page = null,
Headings = dbPage[i].Headings
};
return page;
}
}
public Page Clone()
{
Object page = this.GetType().InvokeMember("", BindingFlags.CreateInstance, null, this, null);
foreach(PropertyInfo propertyInfo in this.GetType().GetProperties())
{
if(propertyInfo.CanWrite)
{
propertyInfo.SetValue(page, propertyInfo.GetValue(this, null), null);
}
}
return page;
}
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.
I am fairly certain that this is a lazy loading issue, but after reading about lazy loading, I am still no closer to a solution. I have tried turning lazy loading on and off with no success. When I pull a Node from the database, the Parent and Children are null, even though such objects exist in the database.
I also added the 'IsReference = true' as an attempt to solve this issue.
I made the data members that are object references virtual at one point but I had serialization issues with the proxies.
DatabaseContext.cs
public class DatabaseContext : DbContext
{
public DatabaseContext() : base("DatabaseName")
{
Configuration.LazyLoadingEnabled = false;
}
public DbSet<Node> Nodes { get; set; }
}
Node.cs
[DataContract(IsReference = true)]
public partial class Node
{
[DataMember]
public long ID { get; private set; }
[DataMember]
public Node Parent { get; set; }
[DataMember]
public ICollection<Node> Children { get; set; }
}
see Loading Related Objects
the simplest way is to use .Include("Parent").Include("Children") in your query
I ended up passing the includes I wanted as parameters (you can use actual types or strings) to the method, and then I looped the includes since they return DbQuery objects and don't actually query the database. Thanks for the input #moi_meme (+1).
foreach (var include in includes)
{
query = query.Include(include);
}