iphoneobjective-ccore-datafetched-property

Core Data get sum of values. Fetched properties vs. propagation


I'm relatively new to Core Data (coming from an SQLite background). Just finished reading the 'Core Data for iOS' book but I'm left with a number of baffling questions when I started modelling an app which has the following model:

  1. 'Accounts' entity which has a to-many 'transactions' relationship and a 'startingBalance' property
  2. 'Transaction' entity which has a to-many 'payments' relationship (and an inverse to Accounts)
  3. 'Payment' entity which contains details of the actual 'amount' paid

For performance reasons I wanted to de-normalize the model and add a 'TotalAmountSpent' property in the 'Accounts' entity (as suggested by the book) so that I could simply keep updating that when something changed.

In practice this seems difficult to achieve with Core Data. I can't figure out how to do this properly (and don't know what the right way is). So my questions are:

a) Should I change the 'TotalAmountSpent' to a Fetched Property instead? Are there performance implications (I know it's loaded lazily but I will almost certainly be fetching that property for every account). If I do, I need to be able to get the total amount spent against the 'startingBalance' for a given period of time (such as the last three days). This seems easy in SQL but how do I do this in Core Data? I read I can use a @sum aggregate function but how do I filter on 'date' using @sum? I also read any change in the data will require refreshing the fetched property. How do I 'listen' for a change? Do I do it in 'Payment' entity's 'willSave' method?

b) Should I use propagation and manually update 'TotalAmountSpent' each time a new payment gets added to a transaction? What would be the best place to do this? Should I do it in an overridden NSManagedObject's 'willSave' method? I'm then afraid it'll be a nightmare to update all corresponding transactions/payments if the 'startingBalance' field was updated on the account. I would then have to load each payment and calculate the total amount spent and the final balance on the account. Scary if there are thousands of payments

Any guidance on the matter would be much appreciated. Thanks!


Solution

  • If you use a fetched property you cannot then query on that property easily without loading the data into memory first. Therefore I recommend you keep the actual de-normalized data in the entity instead.

    There are actually a few ways to easily keep this up to date.

    1. In your -awakeFromFetch/-awakeFromInsert set up an observer of the relationship that will impact the value. Then when the KVO (Key Value Observer) fires you can do the calculation and update the field. Learning KVC and KVO is a valuable skill.

    2. You can override -willSave in the NSManagedObject subclass and do the calculation on the save. While this is easier, I do not recommend it since it only fires on a save and there is no guarantee that your account object will be saved.

    In either case you can do the calculation very quickly using the KVC Collection Operators. With the collection operators you can do the sum via a call to:

    NSNumber *sum = [self valueForKeyPath:@"transactions.@sum.startingBalance"];