I have a one to many relationship between two core data entities.
As Below A->>B
A ->> B : Delete Deny (i.e Deny the deletion of A while their are objects of B)
B -> A : Nullify
In multiple context, one context is unaware of other changes, so in that case i get a validation error while saving the parent context, because in one context a child is inserted to a parent which am deleting in another context.
Their is little to no information provided on how to handle validation error, most of the examples just show error to user and discard the changes. If i need to undo delete of that particular parent object can i?
Is there any option how i can handle the validation error.
NSValidationRelationshipDeniedDeleteError
I didn't find a way to undo the delete for only a certain object without losing changes. So if you have deleted a parent Entity in a childContext, then saved the childContext, the deleted entity is automatically added to parentContext.deletedObjects. I tried different approaches but the only way I was able to cancel the delete operation from the parentContext is to call parentContext.refreshObject(deletedParent, mergeChanges: false). (for sure you can try to rollback or reset the context)
It really depends on what you are going to do (so the business has a great influence here!). But I think you should work optimistically: https://en.wikipedia.org/wiki/Optimistic_concurrency_control
Sometimes, in case of conflicts, it's ok to rollback and restart the transactions.
Usually, the childContext overrides the changes of the parentContext(the child has higher priority). So the architecture of you app should work the same way. When you delete an entity in the childContext, you should also delete it in the parentContext, even if the deny rule stops you because you have inserted a new child entity. What you should do is you should remove the inserted entities then delete the parent entity. Otherwise you should restudy the architecture of the app.
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 have a grid which shows a list of entities.
Each row has a delete button.
Once the user clicked on the delete button for a given entity I want to change the css of the row and to replace the delete button with a cancel button.
So on the delete button event handler, I do :
myEntity.entityAspect.setDeleted();
But as soon as I do that, the entity is removed from the collection and the row disappear from the grid.
Is there a way to prevent that ? I just want to mark the entity as 'deleted', and postpone any change until user clicks on save button.
The only alternative I see, is to add a property isDeleted to my client-side model and to base my logic on that. But it means I have to handle change tracking myself and loop through the entities on save to call setDeleted for entities which isDeleted property is true. I'm not fond of this solution. Is there something better I'm missing with breeze ?
My guess is that your collection is the value of one of Breeze's navigation properties. These collections are 'live' in that if you delete an entity, the entity becomes removed from all live collections to which it belongs.
So the best answer would be to copy the entities into your own collection and bind to that. Your own collection will not be 'live' and deleting entities will not remove them from the collection.
The problem with 1) is that when you delete an entity we need to sever its relationships with any other entities. This really is the essence of deleting, we want the environment to look like it will after the delete is committed ( via EntityManager.saveChanges())
When you say that you do not want the navigation properties to be set to null after a delete I am assuming that you are talking about a scalar (1->1) or (n->1) navigation. (nonscalar navigations simply remove the entity from the collection) The first issue is whether you are talking about navigations 'To' the deleted entity or 'From' the deleted entity. In the case of 'NOT" removing the navigation 'to' a deleted entity we would be causing many if not most existing breeze apps to fail. Most apps expect to see entities disappear when you delete them.
In the case of 'NOT' removing the navigation 'from' a deleted entity, the concept make more sense except that you cause two more issues. The first is that this breaks any 'bidirectional' navigations, i.e. you can no longer roundtrip between the 'from' and 'to' navigations. But more importantly, we view a 'delete' as a precursor to an eventual 'detach' operation ( which will occur after a successful save). The primary value of the 'detach' is that it recovers memory because we have effectively removed any references to the 'deleted' object, which means that it can be garbage collected. If we leave any navigation properties intact this garbage collection will never occur, and we end up with a memory leak.
We could get around this by having the 'detach' operation also remove navigation properties but the rules begin to get harder to explain.
If you still feel strongly about this, please post your suggestion to the Breeze User Voice. If we see some substantial interest in this, we will try to come up with a solution, but right now we don't have a good answer to this that doesn't add real conceptual complexity. (Something we really try to avoid).
Can you give more detail about the 'validations' that you are seeing? Are these validations on the 'deleted' entity or on the still alive entities that were pointing to the deleted entity? Not having the first occur makes a lot of sense (and we should fix it if this is the case), not having the second occur does not make sense because you really are causing a real validation failure.
Ok, I have tried Jay's solution. First I do a deep copy using lodash.js:
$scope.sourceMaterials = _.clone($scope.request.sourceMaterials, true);
then I bind my grid to $scope.sourceMaterials. User clicks on delete button for a given row and I do:
var document = $scope.sourceMaterials[index];
document.entityAspect.setDeleted();
The problem is that breeze sets to null all the navigation properties of my entity (see removeFromRelationsCore function in breeze.js).
Doing so affects the values shown on screen to the user. For instance one of the required field gets nulled by breeze. So when user clicks on the save button, the validation fails.
To sum up I have two issues:
navigation properties should not be set to null when deleting my entity
the validation should not be triggered for an entity that has its state set to deleted
Could you shed some light on this please ?
EDIT
Following Jay's answer, I can confirm that:
1) navigation property is nulled FROM the deleted entity and I'd like that to be prevented somehow
ex: I have an entity Document with a 1 -> 1 np named DocumentType. If I set deleted on Document, then DocumentType is nulled.
2) validation rules occur on properties of the deleted Entity (those that were nulled)
which one of these problems should I report on user voice ?
I found a question very similar to mine here, but it was un-replied to and unanswered so I will try again.
I may be missing something in regards to cascade deletes and core data. I am used to cascade deletes working as designed in RDBMS apps but having issues with core data.
I have an object record that get's inserted into entity via an "add form" called modally from a table view. No problem.
In another session I will insert objects into a related details entity (many) where there is a common loadID attribute in both. No problem.
In another session, I will call up the original table view to view the parent "loads", and swipe-delete, save the context, and the parent load gets deleted from the one side entity. No problem, (except apparently details objects in a many side entity do not get removed)
When I open the sqlite data source with a db manager, I see the child records (pay detail items) are being orphaned. I have double and triple checked the cascade delete settings in the relationships. Have tried different entity relationship settings combinations, but nothing seems to get the many records to automatically delete along with the parent record.
If you can't define corresponding keys in core data, how does core data know what belongs to what when you operate in multiple sessions (contexts) adding child objects to the many-side entity?
I don't know if I'm doing something wrong or missing a vital step prior to inserting new child objects in the many table, or doing something wrong or missing a step when deleting the parent object.
But I would suggest all with cascade deletes set to open your data file and be sure there are not orphaned objects (records)
I was looking for a good way to organize validation rules within BeforeSaveEntity method and I have found this comment in the file: TodoContextProvider.cs within the project: BreezeMvcSPATemplate:
// A second DbContext for db access during custom save validation.
// "this.Context" is reserved for Breeze save only!
Why this.Context can not be used?
Excellent question. The answer isn't obvious and it's not easy to cover briefly. I will try.
The EFContextProvider takes the save data from the client and (ultimately) turns these data into entities within the EFContextProvider.Context. When the save is approved, the EFContextProvider calls the SaveChanges method on this EF Context and all of its contents are saved as a single transaction.
There are two potential problems.
1. Data integrity and security
Client data can never be fully trusted. If you have business rules that limit what an authorized user can see or change, you must compare the client-derived entity to the corresponding entity from the database.
An EF Context cannot contain two copies of the "same entity". It can't hold two entities with the same key. So you can't use the EFContextProvider.Context both to fetch the clean copy from the database and to hold the copy with changes.
You'll need a second Context to get the clean copy and you'll have to write logic to compare the critical values of the entity-to-save in the EFContextProvider.Context with the values of the clean entity in the second Context.
2. Cross-entity validation
Many validation do not require comparison of values with a clean entity.
For example, the out-of-the-box System.ComponentModel.DataAnnotations attributes, such as Required and MaxLength are simple data validations to determine if an entity is self-consistent. Either there is a value or there is not. The value is less than the maximum length or it is not. You don't need a comparison entity for such tests.
You could write your own custom System.ComponentModel.DataAnnotations attributes that compare data values within a single entity. You might have a rule that says that order.InvoiceDate must be on-or-before order.ShipDate. that is also a self-consistency test and you won't need a comparison entity for that one either.
If these are the only kinds of validation you care about - and you're using an EF DbContext - you can let EF run them for you during its save processing. You won't need a second Context.
But cross entity validations are another story. In a cross-entity validation, entity 'A' is valid only when some condition is true for entity 'B' (and perhaps 'C', 'D', 'E', ...). For example, you may require that an order item have a parent order that is already in the database.
There is an excellent chance that the parent order is not in the EFContextProvider.Context at the time you are validating the order item.
"No problem," you say. "I'll just navigate to the parent with someItem.Order."
No you cannot. First, it won't work because lazy loading is disabled for the EFContextProvider.Context. The EFContextProvider disables lazy loading mostly to break circular references during serialization but also to prevent performance killing "n+1" bugs on the server.
You can get around that by loading any entity or related entities at will. But then you hit the second problem: the entity you load for validation could conflict with another entity that you are trying to save in this batch.
The EFContextProvider doesn't populate its Context all at once. It starts validating the entities one-by-one, adding them to the Context as it goes.
Continuing our example, suppose we had loaded the parent order for someItem during validation. That order is now in EFContextProvider.Context.
The save process continues to the next entity and ... surprise, surprise ... the next entity happens to be the very same parent order. The EFContextProvider tries to attach this copy to the Context which already has a copy (the one we just loaded) ... it can't.
There's a conflict. Which of the two orders belongs in the EFContextProvider? The clean copy we just loaded for validation purposes ... or the one that came from the client with modifications to be saved?
Maybe you think you know the answer. Maybe I agree. But the fact is, the EFContextProvider throws an exception because there is already an order with that key in the Context.
Conclusion
If all your validations are self-consistency checks, the EFContextProvider.Context is all you need. You won't have to create a second Context
But if you have data security concerns and/or business logic that involves other entities, you need a second Context ... and you'll need sufficient EF skills to use that Context.
This is not a limitation of Breeze or the Entity Framework. Non-trivial business logic demands comparable server-side complexity no matter what technology you choose. That's the nature of the beast.
I have a program in which I'm using CoreData to manage both permanent and temporary data.
In one instance, I abort the addition of some data by deleting the object when the user presses cancel. But when I hit the next view, the data is still there, attached to it's parent core data object.
[self.bar removeFoosObject:self.foo];//Why do I need this line?
[self.foo.managedObjectContext deleteObject:self.foo];
I eventually solved this by manually removing the child object from it's parent -- but isn't that something core data handles automatically? Why should I need the first line?
I ran some test code, and Foo is definitely deleted -- the code that it was mucking up let me check, and it's MOC has been set to nil. The memory exists, but it should be very, very dead...
You have to manually do this because you have your deletion rules set wrong. Check the Relationship Delete Rules in the following apple documentation.
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html
Or it could also be that the relationship is NOT set as optional between the parent and child object.
And also after you delete the object you should save the database to synchronize it.