I am trying to use the "sandbox editor" approach with exportEntities and importEntities as detailed in the Cool Breezes section (and in many SO posts).
I have a main/parent entity which has one or more child entities. I have an HTML view (using Aurelia but that doesn't matter) that displays the 4 properties from the parent entity and then 3 or 4 properties from each of the child entities. In this example the parent has just one child entity.
The view uses a new entityManager and the parent/child entities have been imported from the 'master' entityManager in the usual export/import way. So far so good.
I edit one of the properties on the child entity and save it. I pass the saveResult to a copy of the updateMasterWithSaveResult. Everything works fine up to the line masterEm.importEntities(exported);
imports[] contains just one updated child entity - correct.
deletes[] is empty - correct.
exported contains the updated child entity info - correct.
masterEm contains the original parent and the original (non-updated) child - correct.
The problem
After it has called masterEm.importEntities(exported), masterEm now contains the original parent BUT the child property array now has 2 elements in it and both of them are the same, updated, child element. They are identical - same key, same everything.
Analysis
After much tracing I've narrowed this down to _linkRelatedEntities in a50_entityManager.js. At the bottom of this function it handles the foreign keys of the child and calls parent.getProperty(invNp.name).push(entity) or parent.getProperty(invNp.name)._push(entity) but
doesn't appear to check if the child is already connected. So in this case the child entity is
already in the array but gets added a second time.
Question: Is this the expected outcome? And, if so, am I missing something or is there a 'proper' way around it so that I end up with only one child element?
My temporary solution is to run a de-duplication on the child array after running importEntities but that seems hacky to say the least.
Related
When you mark an entity as deleted by setting the entityAspect it marks its children as deleted as well. Is there a way to only mark the parent deleted? Or a way to go through and mark the children as unchanged after the fact?
You don't want to mess with the navigation properties - I will tell you that straight away. My suggestion is to model your question as if you had to ask it using T-SQL.
In T-SQL, can you delete a parent record, but leave the children? No. I mean you could, but why? You just created orphaned child records in the database. Are you going to delete the foreign key but leave the data? What a mess.
The only reason you are able to map a parent child relationship in Breeze is because of the navigation properties that were created based on the parent / child relationships defined in the database. If you can't do it in the database, you can't do it in Breeze.
If the model refuses to budge and you decide to go forward with this anyway, you need to return data that is not linked via relationships. You can create a view for the parent and children... but you will need to manually manage the load. If your entities are based on a view, they probably are not going to be updatable.
Sorry, no code to post. I gave up on this a long time ago.
Breeze does not mark child entities as deleted if you delete the parent. We haven't implemented cascading deletes in Breeze. You must have code in your application that is doing this. Breeze disconnects the child entities from the deleted parent by clearing the foreign key properties, so the child entities will be in modified state. However, you won't be able to successfully save w/o violating FK constraints in your DB. You either have to implement cascading deletes on the server or manually delete the child entities.
I know that one of the benefits of the one to many relationships is cascading behavior such as the deletion of a parent(owning) object resulting in
the deletion of the children(objects the parent owned) but what if the objects owned by it are also owned by another parent do they still get deleted if only one parent gets deleted?
GORM/Grails is smart enough to figure out in this case that the child instances should not be deleted when they are in use by another instance. (As indicated in my comments to the original question)
Each time I think I have it nailed, it rears its head again!
So simply if there's a master table with navigation property to a collection of children and I use "createEntity" to create a new empty version of the entity I am able to see the navigation property in the entity returned, but I am unable to access/set any properties. I suspect this is because the parent key does not yet exist in the child entity?
To clarify. I can put in a breakpoint at the point where the new entity is returned and if I inspect the master() entity I can see the children property right there. If I then try and do:
master().children.childProperty("this is a new value")
...then I simply get "object function observable()..." (then loads of code from knockout I think) and ending "has no method 'childProperty'"
I think I've tried every combination of parentheses known to man and I get slightly different errors but nothing works. Do I need to have a [0] in there somewhere as "children" is effectively an array?
Added info for clarification.
As detailed in comments below, retrieving an existing entity with "expand" specified in the query for eager loading returns a master entity with a related child entity, properties of which I can set with the method I was trying to for the "new" example above which doesn't work. The specified length of the "children" collection when I return an existing master/child is 1, when I initialize a new master/child the array length is zero.
This isn't, I suppose, a traditional master/child relationship - it's more an associated entity than a child. To explain, "master" will always have one child record - think of it as master being a bottle and child being the related record that determines the colour of the bottle, the contents and the label details on the bottle. There will only ever be one bottle but there could be dozens of variations of colour, content and label.
When I retrieve an existing master record, I also specify the specific child I want so in this page I will only ever retrieve one master and one child record as I'm editing the specific variation and may want to change the label text.
In another page, I list all the variations for a bottle, so there I retrieve a master and all the associated children in a classic "one to many" example.
The model is:
Public Class bMaster
Public Property ID() As Integer
...other properties
Public Overridable Property bChildren() As ICollection(Of bChild)
End Class
Public Class bChild
Public Property ID() As Integer
Public Property bMasterID() As Integer
...other properties
Public Overridable Property bMaster() As bMaster
End Class
Carl - Kick yourself. You are so close that it hurts to write this for you. If children is a collection you can't simply set a value for each one of them like that. Since they are an array you must select one of them to set a value on.
var child = master().children()[0]; // get the first child
child.childProperty(someValue);
This would also work this way -
ko.utils.arrayForEach(master().children(), function (child) {
child.childProperty(someValue);
});
Which would loop through your array and set the value for each of the entities in the collection.
Edit
If it is a new entity then it doesn't yet have a child entity. If there is always only going to be one child entity of the parent, I am not sure why you are using a collection instead of a complex type or something. You need to create a parent and then create a child on that parent as well -
var parentEntity = manager.createEntity('Parent');
var childEntity = manager.createEntity('Child');
childEntity.parent(parentEntity);
return childEntity;
Then if you wanted to set a property on a parent's child you would do so as I mentioned above -
var child = master().children()[0]; // get the first child
child.childProperty(someValue);
or you could still use
master().children()[0].childProperty(someValue);
Last, if you created the relationship properly (one to one instead of one to many) you could use this -
master().children().childProperty(someValue);
I'm sure I've created my own pain here, but I'm struggling to understand the correct sequence of events to manage creating new entities in my scenario.
In my model I have two objects, ObjectA and ObjectB that both inherit from BaseObject, obviously each with their own additional properties.
In my view, as most of the information is the same, I want the user to be able to just select an option as to which one to create. So they fill out SharedProperty1 and SharedProperty2 (which is a collection navigation property), select an option as to if they want an A or B object, and then fill in a final page which has the last object specific properties.
As I didn't know which entity to create until the user had selected this option, I built a an object in the viewmodel to handle this temporary data. As part of that while they are filling out SharedProperty2 (the collection), as they add new ChildObjects, I create them with entityManager.createEntity('ChildObject'). Then when they reach the end, I create either ObjectA or ObjectB entity and add the child entitites (and other properties) and then try and save.
The problem is it never saves correctly, but I get different results depending on which approach I take. So because the user could just abort the new object process, I was creating the ChildObjects with EntityState.Detached because I thought that would be easier if they got thrown away. I realised though that all the entities created in this way get the id key 0. So then I fixed the keys while I was adding the ChildEntites to the parent (either ObjectA or ObjectB), by assigning them decreasing negative numbers (ie: -1, -2, etc). This resulted in some crazy server-side behaviour with only some entities being saved to the db and complaints of conflicting foreign keys.
This also had a bad smell that I hadn't understood this correctly, and I'd made a mess of it. So now I tried just creating the entities normally (ie: without the Detached flag), and they all get their own unique keys (again breeze appears to follow -1, -2, etc), but now when I try to copy them from my temporary viewmodel collection to the parent object collection, I get the error that an entity with this key is already attached. So now I can't even build up the correct model to save.
I still think I've not understood quite correctly how to handle this, so some pointers would be deeply appreciated.
To head off what I suspect will be a question, why I didn't use RejectChanges to handle the entities being thrown away. Basically a user can add a ChildObject (object gets created by breeze entityManager, added to viewmodel collection, bound to UI), and then decide to just remove it again (currently just gets removed from viewmodel collection) before they save their data. If I used reject changes I would throw away other important entites. I think I'm now going to be a good boy and use the proper detach method if someone removes the ChildObject in the view.
If I understand your question correctly, you are trying to create some properties and then add them to a parent objects collection when saving. Correct me if I am wrong, but Breeze not only supports this, but does so very efficiently. Having come from .NET and C# it was very difficult for me to grasp how easy this can be, but this is what I would do if I were you -
var childA = ko.observable();
var childB = ko.observable();
childA(entityManager.createEntity('ChildObject')); // populate your children
childB(entityManager.createEntity('ChildObject')); // populate your children
Then you can edit them in your view, and when you are ready to save simply add them to the collection.
var save = function() {
isSaving(true);
var parent = ko.observable();
return entityManager.getParent(parent, parentId)
.then(setParents)
.fail(catchError);
function setParents() {
childA().parent(parent());
childB().parent(parent());
entityManager.saveChanges()
.then(complete)
.fail(catchError);
function complete() {
isSaving(false);
return Q.resolve(); // Don't know if you have any unresolved conflicts
}
}
};
Basically in this manner we are -
A : Creating the entities
B : Editing them without performing any changes
C : When we call save we are setting their parent navigation property. In my prior ways (be it right or wrong) I would have simply set ParentId(parentId) and let EF figure out how to navigate but (pardon the pun) this is a breeze with Breeze. We could also just as easily pass in a parent observable and not have to go get it from the manager, it just depends on whether we have it already or not.
Another way you could do this if you want to manage the entities separately is to save a single entity at a time with entityManager.saveChanges([childA]) as they are ready. Just pass in an array with a single entity that you want to save. This may be useful if you are working on multiple entities but they aren't all ready for saving and you need to navigate around your app. Unless you call cancelChanges() Breeze will just keep the entity in cache until you are ready to use it again. In this manner, just make a call for entities in the isAdded() state and you can pull 'em back in and edit again.
I'm using MVC.net and I have 2 classes (case and accident) with a many to many relationship, I'm also using auto mapper to copy View Models to EF and vice versa. Now the problem i've come across is when i do this:
Case theCase = Mapper.Map<CaseEditVM, Case>(theCaseEditVM);
theCase.Accidents.Clear();
UOW.Cases.Update(theCase);
The changes to the case are saved but the link table for accidents is not. Ef totally ignores the Accidents changes.
However when i do:
Case theCase = UOW.Cases.GetByID(someid).Include("Accidents");
theCase.accidents.Clear();
UOW.Cases.Update(theCase);
EF correctly saves the accidents property.
So from what i can tell EF ignores the accident property as its not mapped inside EF yet. Make sense however how do i tell it when mapping the View model i want EF to update the linked properties as well?
The simplest way in your case is first attach case to context and clear changes after you attached it. Otherwise you will have a lot of work. There is no magic which would make this for you. EF doesn't know about changes to relations you did on detached entity and what is even worse once you attach the entity to context you already don't know what records were included in the navigation property so you cannot configure context to reflect that (it must be done per every single related entity) without reloading the whole entity and merging changes between detached and attached one.