Detecting changes to a specific attribute of NSManagedObject - iphone

How can I detect changes to a specific attribute of an NSManagedObject? In my Core Data data model, I have a Product entity that represents a product for sale. The Product entity has several attributes: price, sku, weight, numberInStock, etc. Whenever the price attribute of a Product changes, I need to perform a lengthy calculation. Consequently, I would like to know when the price attribute of any Product changes, [edit] even if that change comes from merging a context saved on another thread. What is a good way to go about doing this? I have thousands of Product objects in my store; obviously it's not feasible to send each one an addObserver message.
I have been using NSManagedObjectContextObjectsDidChangeNotification to detect changes, but it only notifies me that a managed object has changed, not which attribute of that object has changed. I could redo the calculation whenever there's any change to a Product, but that results in useless recalculations whenever an irrelevant attribute has changed. I'm considering making a Price entity (that only contains a price attribute) and using a to-one relationship between Product and Price. This way, I can detect changes to Price objects in order to kick off the calculation. This seems excessively kludgy to me. Is there a better way?
Update:
#railwayparade pointed out that I could use the changedValues method of NSManagedObject to determine which properties have changed for each updated object. I completely missed that method, and it would totally solve my problem if the changes weren't being made on a background thread and merged into the main thread's context. (See next paragraph.)
I completely missed a subtlety about the way that NSManagedObjectContextObjectsDidChangeNotification works. As far as I can tell, when a managed object context saved on another thread is merged into a context on the main thread (using a mergeChangesFromContextDidSaveNotification:), the resulting NSManagedObjectContextObjectsDidChangeNotification only contains change information about objects that are currently in the main thread's managed object context. If a changed object isn't in the main thread's context, it won't be part of the notification. It makes sense, but wasn't what I was anticipating. Therefore, my thought of using a to-one relationship instead of an attribute in order to get more detailed change information actually requires examination of the background thread's NSManagedObjectContextDidSaveNotification, not the main thread's NSManagedObjectContextObjectsDidChangeNotification. Of course, it would be much smarter to simply use the changedValues method of NSManagedObject as #railwayparade helpfully pointed out. However, I'm still left with the problem that the change notification from the merge on the main thread won't necessarily contain all of the changes made on the background thread.

One point with regard to this thread,
The NSManagedObjectContextObjectsDidChangeNotification generated by Core Data indicates that a managed object has changed, but doesn't indicate which attribute has changed.
It actually does. The "changedValues" method can be used to query which attributes changed.
Something like,
if([updatedObjects containsKindOfClass:[Config class]]){
//if the config.timeInterval changed
NSManagedObject *obj = [updatedObjects anyObject];
NSDictionary *dict=[obj changedValues];
NSLog(#"%#",dict);
if([dict objectForKey:#"timeInterval"]!=nil){
[self renderTimers];
}
}

This type of circumstance is where you need a custom NSManagedObject subclass. You need the subclass because you are adding a behavior, reacting to a price change, to the managed object.
In this case, you would override the accessor for the price attribute. Create a custom subclass using the popup menu in the data model editor. Then select the price attribute and choose 'Copy Obj-C 2.0 Implementation to the Clipboard`. It will give you a lot of stuff but the key bit will look like this:
- (void)setPrice:(NSNumber *)value
{
[self willChangeValueForKey:#"price"];
[self setPrimitivePrice:value];
[self didChangeValueForKey:#"price"];
}
Just add the code to deal with the price change and you are done. Anytime a specific product's price changes, the code will run.

You could take a look at KVO (Key Value Observing). Not sure if there are wrappers built into Core Data API, but I know it's part of Objective-C.

I thought I would document my design decisions here in case they're useful to others. My final solution was based on TechZen's answer.
First, I'll start with a short, and hopefully clearer, restatement of the problem:
In my application, I want to detect changes to a specific attribute (price) of a managed object (Product). Furthermore, I want to know about those changes whether they're made on the main or a background thread. Finally, I want to know about those changes even if the main thread currently does not have the changed Product object in its managed object context.
The NSManagedObjectContextObjectsDidChangeNotification generated by Core Data indicates that a managed object has changed, but doesn't indicate which attribute has changed. My kludgy solution was to create a Price managed object containing a single price attribute, and to replace the price attribute in Product with a to-one relationship to a Price managed object. Now, whenever a change is made to a Price managed object, the Core Data NSManagedObjectContextObjectsDidChangeNotification will contain that Price object in its NSUpdatedObjectsKey set. I simply need to get this information to the main thread. This all sounds good, but there's a hitch.
My Core Data store is being manipulated by two threads. This is done in the "usual" way—there is a managed object context for each thread and a single shared persistent store coordinator. After the background thread makes changes, it saves its context. The main thread detects the context save via the NSManagedObjectContextDidSaveNotification and merges the context changes using mergeChangesFromContextDidSaveNotification:. (Actually, since notifications are received in the same thread they're posted in, the NSManagedObjectContextDidSaveNotification is received on the background thread and passed to the main thread via performSelectorOnMainThread: for merging.) As a result of the merge, Core Data generates a NSManagedObjectContextObjectsDidChangeNotification indicating the changed objects. However, as far as I can tell, the NSManagedObjectContextObjectsDidChangeNotification only includes those objects which are currently represented in the receiving context. This makes sense from the perspective of updating the UI. If a managed object isn't being displayed, it probably won't be in the context, so there's no need to include it in the notification.
In my case, my main thread needs to know about changes made to managed objects whether or not they're currently in the main thread's context. If any price changes, the main thread needs to queue an operation to process that price change. Therefore, the main thread needs to know about all price changes even if those changes are made on a background thread to a product that's not currently being accessed on the main thread. Obviously, since NSManagedObjectContextObjectsDidChangeNotification only contains information about objects currently in the main thread's context, it doesn't meet my needs.
The second option I thought of was to use the NSManagedObjectContextDidSaveNotification generated by the background thread when it saves its context. This notification contains information about all changes to managed objects. I already detect this notification and pass it to the main thread for merging, so why not peek inside and see all of the managed objects that have changed? You'll recall that managed objects are not meant to be shared across threads. Consequently, if I start examining the contents of NSManagedObjectContextDidSaveNotification on the main thread, I get crashes. Hmm ... so how does mergeChangesFromContextDidSaveNotification: do it? Apparently, mergeChangesFromContextDidSaveNotification: is specifically designed to work around the "don't share managed objects across threads" restriction.
The third option I thought of was to register for NSManagedObjectContextDidSaveNotification on the background thread and while still on the background thread convert its contents into a special PriceChangeNotification containing object IDs instead of managed objects. On the main thread, I could convert the object IDs back into managed objects. This approach would still require the to-one Price relationship so that changes in prices are reflected as changes to Price managed objects.
I based my fourth option on TechZen's suggestion to override the price setter in the Product managed object. Rather than use a to-one relationship to force Core Data to generate the notifications I needed, I went back to using a price attribute. In my setPrice method, I post a custom PriceChangeNotification. This notification is received on the background thread and is used to construct a set of Product objects with price changes. After the background thread saves its context, it posts a custom PricesDidChangeNotification which includes the object IDs of all Product objects whose prices have changed. This notification can be safely transferred to the main thread and examined because it uses object IDs instead of managed objects themselves. On the main thread I can fetch the Product objects referenced by those object IDs and queue an operation to perform the lengthy "price change" calculation on a new background thread.

Are you using an NSArrayController or some other controller? Presumably you need some way for the user to interact with the model. It's this point of interaction that give a nice hook for this type of update call. Perhaps the appropriate strategy is to observe the relevant properties of the array controller's arrangedObjects.

Related

What is the point of the Update function in the Repository EF pattern?

I am using the repository pattern within EF using an Update function I found online
public class Repository<T> : IRepository<T> where T : class
{
public virtual void Update(T entity)
{
var entry = this.context.Entry(entity);
this.dbset.Attach(entity);
entry.State = System.Data.Entity.EntityState.Modified;
}
}
I then use it within a DeviceService like so:
public void UpdateDevice(Device device)
{
this.serviceCollection.Update(device);
this.uow.Save();
}
I have realise that what this actually does it update ALL of the device's information rather than just update the property that changed. This means in a multi threaded environment changes can be lost.
After testing I realised I could just change the Device then call uow.Save() which both saved the data and didnt overwrite any existing changes.
So my question really is - What is the point in the Update() function? It appears in almost every Repository pattern I find online yet it seems destructive.
I wouldn't call this generic Update method generally "destructive" but I agree that it has limited use cases that are rarely discussed in those repository implementations. If the method is useful or not depends on the scenario where you want to apply it.
In an "attached scenario" (Windows Forms application for instance) where you load entities from the database, change some properties while they are still attached to the EF context and then save the changes the method is useless because the context will track all changes anyway and know at the end which columns have to be updated or not. You don't need an Update method at all in this scenario (hint: DbSet<T> (which is a generic repository) does not have an Update method for this reason). And in a concurrency situation it is destructive, yes.
However, it is not clear that a "change tracked update" isn't sometimes destructive either. If two users change the same property to different values the change tracked update for both users would save the new column value and the last one wins. If this is OK or not depends on the application and how secure it wants changes to be done. If the application disallows to ever edit an object that is not the last version in the database before the change is saved it cannot allow that the last save wins. It would have to stop, force the user to reload the latest version and take a look at the last values before he enters his changes. To handle this situation concurrency tokens are necessary that would detect that someone else changed the record in the meantime. But those concurrency checks work the same way with change tracked updates or when setting the entity state to Modified. The destructive potential of both methods is stopped by concurrency exceptions. However, setting the state to Modified still produces unnecessary overhead in that it writes unchanged column values to the database.
In a "detached scenario" (Web application for example) the change tracked update is not available. If you don't want to set the whole entity to Modified you have to load the latest version from the database (in a new context), copy the properties that came from the UI and save the changes again. However, this doesn't prevent that changes another user has done in the meantime get overwritten, even if they are changes on different properties. Imagine two users load the same customer entity into a web form at the same time. User 1 edits the customer name and saves. User 2 edits the customer's bank account number and saves a few seconds later. If the entity gets loaded into the new context to perform the update for User 2 EF would just see that the customer name in the database (that already includes the change of User 1) is different from the customer name that User 2 sent back (which is still the old customer name). If you copy the customer name value the property will be marked as Modified and the old name will be written to the database and overwrite the change of User 1. This update would be just as destructive as setting the whole entity state to Modified. In order to avoid this problem you would have to either implement some custom change tracking on client side that recognizes if User 2 changed the customer name and if not it just doesn't copy the value to the loaded entity. Or you would have to work with concurrency tokens again.
You didn't mention the biggest limitation of this Update method in your question - namely that it doesn't update any related entities. For example, if your Device entity had a related Parts collection and you would edit this collection in a detached UI (add/remove/modify items) setting the state of the parent Device to Modified won't save any of those changes to the database. It will only affect the scalar (and complex) properties of the parent Device itself. At the time when I used repos of this kind I named the update method FlatUpdate to indicate that limitation better in the method name. I've never seen a generic "DeepUpdate". Dealing with complex object graphs is always a non-generic thing that has to be written individually per entity type and depending on the situation. (Fortunately a library like GraphDiff can limit the amount of code that has to be written for such graph updates.)
To cut a long story short:
For attached scenarios the Update method is redundant as EFs automatic change tracking does all the necessary work to write correct UPDATE statements to the database - including changes in related object graphs.
For detached scenarios it is a comfortable way to perform updates of simple entities without relationships.
Updating object graphs with parent and child entities in a detached scenario can't be done with such a simplified Update method and requires significantly more (non-generic) work.
Safe concurrency control needs more sophisticated tools, like enabling the optimistic concurrency checks that EF provides and handling the resulting concurrency exceptions in a user-friendly way.
After Slauma's very profound and practical answer I'd like to zoom in on some basic principles.
In this MSDN article there is one important sentence
A repository separates the business logic from the interactions with the underlying data source or Web service.
Simple question. What has the business logic to do with Update?
Fowler defines a repository pattern as
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
So as far as the business logic is concerned a repository is just a collection. Collection semantics are about adding and removing objects, or checking whether an object exists. The main operations are Add, Remove, and Contains. Check out the ICollection<T> interface: no Update method there.
It's not the business logic's concern whether objects should be marked as 'modified'. It just modifies objects and relies on other layers to detect and persist changes. Exposing an Update method
makes the business layer responsible for tracking and reporting its changes. Soon all kinds of if constructs will creep in to check whether values have changes or not.
breaks persistence ignorance, because the mere fact that storing updates is something else than storing new objects is a data layer detail.
prevents the data access layer from doing its job properly. Indeed, the implementation you show is destructive. While the Data Access Layer may be perfectly capable of perceiving and persisting granular changes, this method marks a whole object as modified and forces a swiping UPDATE.

How does refreshObject work in nested managed object contexts?

Say I have an object on a parent, main queue concurrency type, and I change one of its property values. Then, without saving, I execute a fetch request on a child, private queue type, to fetch an array where that object is included. The issue I'm having is that sometimes the object I get does not have the pending changes included that were made on the main thread, although it should.
However, if I call [childMOC refreshObject:object mergeChanges:YES];, I do get pending changes. But the description in the documentation says that this method
"Updates the persistent properties of a managed object to use the
latest values from the persistent store.
" Wouldn't these values then be the values that are saved to disk, so it would not include any pending changes?
I'm a little confused as to how this method works..
With nested managed object contexts, child context will not see any changes made directly to the parent context. You will always need to refresh your object in child context if you want the latest changes.
And yes, refreshing includes pending changes from the parent context. The docs need update in that regard.
The change propagation goes automatically (when saving) only from child -> parent context.

How does deleteObject: work in coreData?

I just took the plunge and rewrote my App on top of CoreData (previously I was using my own internal save format).
Things are mostly working, although I'm a little confused by the behaviour of deleteObject:.
I have an object that is part of my graph, and when I delete it nothing seems to happen to the object. The object has relationships where some of them are "Cascade" and some are "Nullify". Every relationship to / from the object has an inverse relationship.
After I delete the object, the only thing that seems to change is that the "isDeleted" flag is set on my object. All of the relationships exist as they did before.
If I try to find the objects using a NSFetchRequest, it does not find the deleted objects. However, if I traverse my graph using the KVC relationships, the NSSet returned contains all of the objects including the deleted objects.
After I send the save: method to my ManagedObjectContext, then everything is as I expect.
When I do a deletion, do I need to manually nil out relationships I don't want or do I need to continuously save to keep my data sane? This seems very counter intuitive to me.
Is there anything that I can do to "commit" the deletion or at least make my object graph sane short of doing a save. It seems a little drastic to be doing a save every time I want to modify my graph.
Thanks,
Ron
p.s. Here is some of the behaviour that seems strange to me:
Before deleting the object, this is the "description" of the parent object which has a categoryObjs "to many" relationship:
categoryObjs = (
"0x613e1a0 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p9>",
"0x613e1b0 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p12>",
"0x613e190 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p7>"
);
After deleting the "p12" object (the middle one above), the state of the relationship does not change when accessed through KVC. If I try to fetch the TestCategory entities, then only two are found.
After a "save:" the p12 object disappears:
categoryObjs = (
"0x613e1a0 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p9>",
"0x613e190 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p7>"
);
Every time you call save:, your Managed Object Context must go back to the store and actually write the changes. This is expensive. Therefore, deleteObject: merely marks the object as deleted, which will actually be applied the next time you save. (Remember that this helps out with undo functionality too, it's just going against the way you want to do things.)
According to the documentation, the isDeleted property just states whether the object is going to be deleted upon the next commit, and sets an isDeleted flag on the object. Additionally, deleteObject: will remove the receiver from the context if it was never committed.
For example (where Objects A and B are NSManagedObject instances):
Create Object A
Save MOC
Delete Object A
Object A has been marked for deletion but is not actually deleted until you perform step 2 again.
Contrast with this:
Create Object B
Delete Object B
Object B is gone, since it was never saved, there is no "marking for deletion". It's simply gone.
Edit:
I'm just curious, are you using an NSFetchedResultsController for your tableview's datasource? It's worth looking in to, if you haven't already.
I think Core Data want to minimize memory & IO usage while deleteObject:, and do all the heaver jobs, like write sqllite file, in save:. That could be the most time-efficient way.

validation of persistent ignorant domain objects

I want to know how to check if Domain Object is in right state while saving it.
If Object has persistent information - it is clear as for instance I can check Order's amount in its Save method. However persistent ignorant objects don't have Save method. They just have data and behavior. Should I rely on developers' accuracy or I need to check all entities graphs somehow before saving it in database?
Perhaps I'm not understanding you right.
I'm not sure how you get an "ignorant" object. That seems to run against the core tenet of encapsulation. The only way to modify an object's state should be through its interface. It's the interface's responsibility to ensure any updates to state are valid. So, to your example, you shouldn't need to check the amount in the save() operation (assuming here "save" means persist). It should be updated as a consequence of e.g. calling addProduct() (or something similar - i.e. adding item to order updates amount).
Whether an object is persistent or transitory is irrelevant - it should always encapsulate state update through its interface.
Hope that helps - apologies if I've misunderstood.

Passing objects between managed object contexts

I have an iphone app with 2 managed object contexts. One of my contexts deals with a picker, which allows the user to add new records and then select one of those new records. Once the picker is hidden, that managed object context is saved and discarded.
I then want to use this selected object in my second managed object context, and add (relate) it to another object. This second MOC's changes may be saved or deleted. This is why the first MOC is created to allow the changes made in the picker to always be saved no matter wether they save or discard the changes in the second MOC.
I hope this is clear! My problem is that when the user selects the object from the picker, this object resides in a different context to where it needs to be used. Is there a way you can pass objects between contexts? Perhaps using the object's ID (after it has been persisted).
Thanks for your help!
You mentioned the correct solution in the question. You cannot pass NSManagedObjects between multiple contexts, but you can pass NSManagedObjectIDs and use them to query the appropriate context for the object represented by that ID. So simply persist out the data (via a save:), and then pass the ID to the other context and use it to ask the context for the appropriate object.
Depending on what you want to do you may want to rig up the mergeChangesFromContextDidSaveNotification: so that changes in one context are automatically reflected in the other.