What's the proper way to update parent/child entities in EF? - c#

I'm using Ef 4 code first and have a parent table with a child
When I update the parent, I notice that the child is not updated, rather new entries are created (old ones are not deleted).
My update method looks like so:
IEFRepository<Parent> repo = new EFRepository<Parent>( context );
var parent = repo.GetSingle(m => m.parentId.Equals(updatedParent.parentId));
parent.Child = updatedParent.Child; //this is creating a new record in the db, not overwriting the existing
repo.Update(parent);
If I break out the child properties in the update method like below it solves the duplicate entry problem, but creates other issues elsewhere (primarily with validating null entries).
parent.Child.property = updatedParent.Child.property;
I also tried creating an UpdateChild(), and calling that from UpdateParent(), but got essentially the same result as breaking out the individual child properties
Is there a proper way to control this behavior and force EF to overwrite the child entity instead of creating a new one?

Have a list of childs in a context and remove old child from your context when you don't need it anymore.

Related

EF 6 Saving multiple levels of child entities and multiple parents

Given this model:
I would like to be able to save in one SaveChange call the relations. Which means, I either have a new or updated ContainerParent, and multiple first level children and each of those can have 1 or 2 levels deeper.
The thing is, the children both have a key to themselves, for finding its parent, and a key to the container, for the container to get all its Children independently of their hierarchical level.
With this pseudo code (in the case of all entities are created, not updated)
var newContainerParent = context.ContainerParents.Add(new ContainerParent());
var rootChild = context.Children.Add(new Child());
var secondLevelChild = new Child();
var thirdLevelChild = new Child();
secondLevelChild.Children.Add(thirdLevelChild);
rootChild.Children.Add(secondLevelChild);
newContainerParent.Children.Add(rootChild);
context.SaveChanges();
Problem with this code, is that only the rootchild will have the FK for the container set. I also tried to add the children to they child parent AND the container:
rootChild.Children.Add(secondLevelChild);
newContainerParent.Children.Add(rootChild);
newContainerParent.Children.Add(secondLevelChild);
newContainerParent.Children.Add(thirdLevelChild);
I have the same problem while updating an existing container with new children. I set all the children with the already existing key of the parent, but when SaveChanges is called the key is not saved, its reverted to null.
I fixed it by doing all this in 2 steps, saving once and then getting all the newly created children and updating them with the parent key, the calling SaveChanges again.
I have a feeling I'm missing something, that I should not need to save twice.
The number or frequence of SaveChange calls have no implication on anything, not on performance or so. So why do you want to minimize it ?
Actually, storing such a self referencing table with one SaveChanges is not possible,
cause the ID of an new entity, is generated, when it is saved. So you first need to save it, and then you get the ID, that you can store in another entity. This might require further update-Commands, to the entity you just stored.
You have two chances to solve this.
1) manually generated ID's, handle it all yourself and you know the ID before your store it.
2) In case you have no circularity in your dependency, so a perfect tree structure, you save the items top-down, level by level. I assume you have the childs having a reference to it's parents, so the root has no reference to any other items, you save that first, than the 1st level children, and so on.
This requires multiple SaveChanges, but this is not a disadvantage. It is one Insert-SQL-Command per entity anyway, no matter if you do it in 1 SaveChanges or in 100 SaveChanges.
Both solutions avoid "Update" Commands to the entities, they do Inserts only.
Entity Framework could actually find out this dependencies itself and create an order for new entities to insert, but this is not implemented today, or not perfect, especially on self-referenced tables. The order of saving items is kind of random. So you have to enforce the order with intermediate SaveChanges.

EF 6 SaveChanges with Transaction

I have the following issue when trying to save a brand new object tree.
I have Created an object with a number of child objects. The child objects are all 1 level deep from the initial object created. My problem is that one of the child objects that I am attempting to create also has a FK into one of the other child objects. So on the initial creation of these objects the FK's are of course 0 because the child object that hasn't yet been created has it's PK currently at 0. When I call SaveChanges on the context it errors because the key's are indeterminate on the child object that is reliant on the existence of the other child object. This is quite a large object tree and so it seems I will need to save the parent first and then other objects in order so that I can obtain the new PK value to use against those tables that are reliant on others. The trouble is I want an all or nothing situation, so I want all objects to save or none. SaveChanges in EF 6 removed the ability to pass false as a parameter and then call SaveAllChanges(). What's the best approach for me now to get this to work?
You should use transaction. In transaction, you should first add parent object to dbset and use Context.SaveChanges() the entity will not be created on db until you commit the transaction. However it will reserve id(primary key). So you can use this id on child objects. You can do the same thing for child objects, if you need id(primary key) use Context.SaveChanges().
using (var myTransaction = Context.Database.BeginTransaction())
{
try
{
//Crate Parent Object
Context.SaveChanges();
//Create Child Object;
Context.SaveChanges();
//Other Childs
Context.SaveChanges();
//If everything goes well
myTransaction.Commit();
}
catch (Exception)
{
myTransaction.Rollback();
}
}

Workflow for data from Backbone through NHibernate

This question isn't code-centric, it's a question about idioms. I'm using Backbone/Marionette on the front and C#, NHibernate in the back.
I've got a few tables mapped and working for creates and updates. For simplicity, say I've got two tables, a parent and child, and the child has potentially many rows. The child reference to the parent is not-nullable, so I've got an Inverse relationship, and that's all working. The flow of data from Backbone to the Controller to NHibernate is pretty simple, and I'm using ISession.SaveOrUpdate() at the end. I can post mappings and so forth if necessary. I will say that the mappings Fluent NHibernate generates uses a Bag on the parent.
Here's a concrete example of the situation I'm trying to understand. Say I've got a parent entry with two rows in the child table. I manipulate the data so that one of the children is removed, but no other changes are made. Javascript sends over an object "tree" with the parent entry and the one child row that's left. The mappings are all handled fine, but the sql that's generated is a bunch of (unnecessary, but whatever) update statements. What I would like to happen instead is that NHibernate notices that there is only one child relationship in this new object, but there are two children in the actual database, and then NHibernate deletes that other child. The 'cascade-delete-orphans' option is not working, because the other child isn't actually being orphaned. It still has a reference to the parent in the fk column, and that column is non-nullable anyway, which is why I used the Inverse mapping option.
Is that possible to setup in the mappings? If not, what is a good way to tackle this situation?
Since you are sending an object from the client side, and then create the entity from that object and try to persist, NHibernate will not automatically delete the child entity since it does not know the child object is deleted (it only see you are only try to update one parent entity and a child entity), which is correct in my opinion. For example if you want to just update the parent entity field, then you have to load entire object graph to do it, otherwise NHibernate will delete all children since they are not loaded.
What you should do here is to load the parent entity, and remove missing child entity(s) deleted from it and then persist (instead of mapping the entity), code should look like following,
void Update(ParentDto parentDto){
Parent parent = _session.Get<Parent>(parentDto.Id);
//update parent fields
var childRemoved = //find removed child from parent;
parent.Children.Remove(childRemoved);
_session.SaveOrUpdate(parent);
}

Illegal attempt to associate a collection with two open sessions error when deleting via services

There are several posts related to this error but I'm running into something different.
Very simple NHibernate scenario. Parent and child tables with one to many relationship. One parent can have multiple children.
I need to delete a Parent record with child records so I put together very basic code which works fine:
var childRecordList = new List<ChildRecord>();
var parentRecord = ParentRecordRepository.Get(parentRecordId);
childRecordList = ChildRecordRepository.GetAll().Where(c=>c.ParentRecord.Id==parentRecord.Id);
foreach(var childRecord in childRecordList)
{
ChildRecordRepository.Delete(childRecord);
}
ParentRecordRepository.Delete(parentRecord);
Works. Deletes child and the parent records.
If I take the logic above and turn it into a Services method as "DeleteRecord(ParentRecord parentRecord)" it starts failing with the Illegal attempt to associate a collection with two open sessions error on ParentRecordRepository.Delete(parentRecord);
Services are called by instantiating a service class and then calling the DeleteRecord method:
var parentRecord = ParentRecordRepository.Get(id);
var recordService = new RecordService();
recordService.DeleteRecord(parentRecord);
Can't figure out why. Help ?
Based on your working example I'm a bit suspicious about what your ParentRepository is doing to populate it's children. If you have cascade options set up correctly and the mapping includes the child object definitions with the parent, then you shouldn't be deleting children independently, and deleting the parent would work, including deleting the children as expected. If I had to guess, I'd be expecting to see something like:
ChildRecordRepository.GetAll().Where(c=>c.ParentId == Id);
somewhere in the parent Repository.Get callstack where the parent and child repositories are using different Session instances.
Perhaps provide the mapping configuration for parent and child, and the contents of the parent's Get() method.
I tried creating a session by instantiating an instance of the repository and then doing operation on an object pulled from it.
Then I would open a new session of the repository within the Service layer and try to delete the object passed from the controller created session with it. That was the problem.
The bottom line is the same session has to used to both get and delete an object.

Removing an item from a collection(NHibernate)

I have parent child relationship between two entities(Parent and Child).
My Parent mapping is as follows:
<class name="Parent" table="Parents">
...
<bag name="Children" cascade="all">
<key column="ParentID"></key>
<one-to-many class="Child"></one-to-many>
</bag>
</class>
I would like to execute the following:
someParent.Children.Remove(someChild);
The Child class has a reference to another parent class, Type. The relationship looks like
Note: I apologize for the non-linked url above, I couldn't seem to get past the asterisk in the url string using Markup
Due to this relationship, when the above code is called, instead of a DELETE query, an UPDATE query is generated which removes the ParentID from the Child table(sets to null).
Is it possible to force NHibernate to delete the child record completely, when removed from the Parent.Children collection?
UPDATE
#Spencer's Solution
Very attractive solution as this is something that can be implemented in future classes. However, due to the way sessions are handled(in my particular case) in the repository pattern, this is near impossible as we would have to pass session types(CallSessionContext/WebSessionContext) depending on the application.
#Jamie's Solution
Simple and quick to implement, however I've hit another road block. My child entity looks as follows:
When using the new method, NHibernate generates an update statement setting the TypeID and ParentID to null, as opposed to a single delete outright. If I missed something within the implementation, let me know as this method would be painless to move forward with.
#The One-Shot-Delete solution described here, outlines an idea dereferencing the collection to force a single delete. Same results as above however, an update statement is issued.
//Instantiate new collection and add persisted items
List<Child> children = new List<Child>();
children.AddRange(parent.Children);
//Find and remove requested items from new collection
var childrenToRemove = children
.Where(c => c.Type.TypeID == 1)
.ToList();
foreach (var c in childrenToRemove) { children.Remove(m); }
parent.Children = null;
//Set persisted collection to new list
parent.Children = Children;
Solution
Took a bit of digging, but Jamie's solution came through with some additional modifications. For future readers, based on my class model above:
Type mapping - Inverse = true, Cascade = all
Parent mapping - Inverse = true, Cascade = all-delete-orphan
Remove methods as described in Jamie's solution works. This does produce a single delete statement per orphaned item, so there is the possibility for tuning in the future, however the end result is successful.
Instead of exposing the IList<Child>, control access to the collection through a method:
RemoveChild(Child child)
{
Children.Remove(child);
child.Parent = null;
child.Type.RemoveChild(child);
}
Type.RemoveChild would look similar but you would have to be careful to not put it into an infinite loop calling each other's RemoveChild methods.
I don't think this is exactly possible because Hibernate has no way of knowing if the record has been orphaned. It can check if any other classes relate to the child class but then it would be assuming that it's aware of the entire DB structure which may not be the case.
You're not completely out of luck however. By using the IList interface in conjunction with a custom ICascadeDeleteChild interface you'd create you can come up with a rather seamless solution. Here are the basic steps.
Create a class that inheirits IList and IList<> and call it CascadeDeleteList or something along those lines.
Create a private .Net List inside this class and simply proxy the various method calls to this List.
Create an Interface called ICascadeDeleteChild and give it a method Delete()
Under the Delete method for your CascadeDeleteList check the type of the object that is to be deleted. If it is of type ICascadeDeleteChild then call it's Delete method.
Change your Child class to implement the ICascadeDeleteChild interface.
I know it seems like a pain but once this is done these interfaces should be simple to port around.

Categories

Resources