views:

841

answers:

2

I'm trying to write my first Cocoa app, and Core Data is making it difficult to model my data objects. I have two entities that I want to manipulate: Account and Transaction. Account has a human-readable name and that's about it. Transaction stores a monetary value, and references to two accounts called debitAccount and creditAccount (I'm working on a double-book accounting app).

I want to be able to find all the Transactions for a given Account, whether they use debitAccount or creditAccount. How can I do this? Is there a way to do it that will work easily with Cocoa UI binding?

+3  A: 

If I understand you correctly, you want Transaction to be related to Account via two relationships: debitAccount and creditAccount, yes? And you're wondering about creating inverse relationships.

In short, a relationship can only be the inverse of one other relationship. So you won't be able to create a relationship called, say, transactions that is the inverse of both debitAccount and creditAccount. Instead, you'll have to create two relationships, like debitTransactions and creditTransactions (I'm sure you'll think of more appropriate names for these...)

Now, since relationships are modeled as sets (specifically, NSSets), you can union the creditTransactions and debitTransactions relationships for a particular Account to get all transactions that account is involved with.

A (possibly better) alternative would be to introduce an intermediate entity, with a name like TransactionAccount that has a to-one relationship to both Account and Transaction as well as an attribute, like accountRole that identifies the account as being the debit or credit account relative to that particular transaction. You'd create inverse to-many relationships on both Transaction and Account with a name like transactionAccounts. That way, you could write something like this:

[account valueForKeyPath:@"transactionAccounts.transaction"]

to get all the transactions for a particular account. You could use an NSPredicate to filter the set to only transactions where the account was the debit/credit account.

Alex
Thanks for your suggestion! I found a way that worked for me, but your input helped.
Justin Voss
A: 

The solution that worked best (and made the most sense) to me was to create a subclass of NSManagedObject to use as my Account entity:

@interface Account : NSManagedObject {

}
-(NSArray*)getTransactions;
@end

I have the actual lookup logic inside -getTransactions, which wasn't too hard to do by hand. This is working out well so far, especially because the method name conforms to KVO.

To make Core Data return Accounts instead of NSManagedObjects, I had to change the entity's "Class" property in Xcode's data modeling tool.

Justin Voss
Be careful. The reason for inverse relationships is that they help to ensure the model's consistency. If you delete an object that's the target of a relationship and it has no inverse, you have to make sure to remove it from that relationship, or else you'll end up with invalid references.
Alex
That method shouldn't be named "-getTransactions" because it doesn't return a collection of transactions by reference. Use "-allTransactions" or something similar.
Chris Hanson