I want to put a "Delete" button and a "Cancel" button on each row of a list of Customers. The "Cancel" button is disabled when a customer is "Unchanged". But when a customer transitions to a changed state ("Added", "Modified", "Deleted"), I want to enable the "Cancel" button so the user can reverse the changes -- whatever they are -- before saving.
I can almost do this by subscribing to customer.entityAspect.propertyChanged
. A property change signals a potential change in the EntityState
. I can subscribe to that event and have my handler update an isChanged
observable that I've added to my Customer entities. Then I bind the "Cancel" button enable to the isChanged
and I'm good to go.
But the propertyChanged
event is only raised when a data property changes, e.g., customer.Name("New Co.");
. It isn't raised when the user clicks the "Delete" button. "Delete" triggers customer.entityAspect.setDelete();
which doesn't touch a data property; it simply changes the customer's EntityState
.
(1) Why doesn't a change to the customer's EntityState
raise propertyChanged
and (2) how can I listen for a change to the EntityState
so I can control the "Cancel" button?
P.S.: I'm using Knockout.
P.P.S: This question was inspired by a previous SO question "entityAspect.setDeleted doesn't fire the subscribed propertyChanged event".
EF Core change tracking works best when the same DbContext instance is used to both query for entities and update them by calling SaveChanges. This is because EF Core automatically tracks the state of queried entities and then detects any changes made to these entities when SaveChanges is called.
EntityState.Added : EntityState.Modified; context.SaveChanges(); } } Note that when you change the state to Modified all the properties of the entity will be marked as modified and all the property values will be sent to the database when SaveChanges is called.
This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.
You are correct that Breeze does not raise propertyChanged
whent the EntityState
changes. Maybe it should. We will consider that.
Nor does Breeze have a separate event on the entity - no entityStateChanged
event - to notify you when the EntityState
changes. We've considered that several times. We keep talking ourselves out of it.
There is a perfectly good solution that performs better than a dedicated entityStateChanged
event. Right now you'll have to code it yourself.
The trick is to listen to the EntityManager
, not to the entity. You'll find one variant of this solution in the DocCode "Teach Tests" sample; look for "can control custom ko entityState property via entityManager.entityChanged" in the entityTest.js module.
I'll tweak that to fit your example. The essence of it is as follows:
Subscribe to the entityManager.entityChanged
event; when it is raised and the cause is that an entity's EntityState
changed, you update that entity's isChanged
boolean KO observable (if that property exists).
Add the isChanged
observable to entity types that should be watched in this way.
Here's an example of step #1: listening for state changes
// subscribe with handler watching for EntityState changes addEntityStateChangeTracking(manager); function addEntityStateChangeTracking(entityManager) { if (entityManager._entityStateChangeTrackingToken) { return; } // already tracking it // remember the change tracking subscription with a token; // might unsubscribe with that token in future entityManager._entityStateChangeTrackingToken = entityManager.entityChanged.subscribe(entityChanged); var entityStateChangeAction = breeze.EntityAction.EntityStateChange; function entityChanged(changeArgs) { if (changeArgs.entityAction === entityStateChangeAction) { var entity = changeArgs.entity; if (entity && entity.isChanged) { // entity has the observable var isUnchanged = entity.entityAspect.entityState.isUnchanged(); entity.isChanged(!isUnchanged); } } } }
Let's talk about step #2: adding the isChanged
observable to the type. You seem to have tackled that one but I'm not sure how. Perhaps the best place to do add it to the type is in the type's initializer so you can be sure the property will be there, whether the entity is created or materialized by a query. Here's an example:
var store = manager.metadataStore; function customerInit(entity) { var isUnchanged = entity.entityAspect.entityState.isUnchanged(); entity.isChanged = ko.observable(!isUnchanged); } store.registerEntityTypeCtor('Customer', null, customerInit);
This all seems like a lot of work. It would be easier if Breeze raised the propertyChanged
event when the EntityState
changes. We'll give that more consideration ... there may be some good counter arguments. Meanwhile, I think what you see here is the best approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With