1

I have a simple application with the following premise and a few minor issues:

  • I have 4 Tabs, each with a table view displaying different information and each with a plus button going modally to the same view controller, allowing users to add in information to the app.
  • The user adds a Name, Title, Date and Amount in the view controller and when they press save, it gets saved to the Core Data table view.

The first tab displays ALL for bits of information above (Name, Title, Date, Amount) in a custom table cell with labels, etc. That works well.

The second tab displays only information on the Name, the third tab displays only the Titles. This way, a user sees everything in the first Tab, but only the "Names" in the second tab which they can go select to gather information about that name.

The app is working well when there are only one entry per name (or date or title), but the moment I add a second entry, it duplicates in the Name tab.

For example, if I have an entry for John Smith with only a single entry, it will display that in the Name tab with one entry and when I go in, it shows me only transactions that have John's name attached to it (1 in this case). All good.

However, if I add another Entry for John (spelt exactly the same), it treats it as a separate entry and suddenly, the Name tab now has 2 John's.

I have not put any unique IDs in the attributes of the Core Data Model, but do I have to do this, or could I just use a predicate to ask "if this already exists, don't display it twice" in the Name Tab Bar?

Any assistance would be massively appreciated!

Edit:

Here is the fetchRequest for the Name tab:

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Transaction" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"whoBy.name" ascending:NO];
    fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];    
    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

As we can see, there are no predicates or anything similar. What I want is for a simple check to see "if name exists, don't add it twice" to the Name table view. I hope this makes sense?

amitsbajaj
  • 1,304
  • 1
  • 24
  • 59
  • using unique IDs in a database driven context is always a good idea. Maybe you can provide some fetch/insert code to get an idea for what's wrong. – MrBr Nov 15 '13 at 08:28
  • Thanks MrBr - the problem is I have not provided any unique IDs in the database just yet - that isn't a problem to actually do because I can just regenerate but I was hoping using predicates would be an easier way to go.. I'll edit my post to show the codes - thanks – amitsbajaj Nov 15 '13 at 08:43
  • How does the data model look like? This info (name, title, date, amount) is in a single table or multiple ones? – nikolovski Nov 15 '13 at 08:49
  • The question is: do you want two instances in the model or just one? You need to say what result you want before we can say how to achieve it... – Wain Nov 15 '13 at 08:50
  • @MarkoNikolovski Thanks for the reply - The model is like this: Transaction Entity which contains links to the Person (separate entity), title (separate entity) and amount (separate entity). I split out the functionality like this for other attributes and at some point, I'm sure I'd need to use Object Oriented methodologies to ensure the Transaction is not adding everything but each entity is adding it's own attributes.. – amitsbajaj Nov 15 '13 at 09:40
  • @Wain The way the program works is.. "John Smith" is likely to be in the database many times because it's transactions by John Smith, so there'll always be more than one. The first tab contains multiple entries split by sections with dates - so John Smith may have a transaction in July and in August and so will be listed twice in the 1st Tab bar.. but with the Name tab bar.. I have "John Smith" because I want to see a history of just John Smith.. but I don't want multiple John Smith entries.. it should be one John Smith which segues to another VC displaying the history of John Smith alone – amitsbajaj Nov 15 '13 at 09:41
  • @Wain to add to this, I already have it working when you select John Smith in the Name Tab and it shows you just that user's transactions - so if John has 2 entires, I'd see two entries in there, but I'd see two John Smith's in the Name Tab, the VC before segueing to the history of John Smith.. that is what I'm trying to eliminate.. it sounds like unique IDs would be a good way to go because there could of course be two separate John Smiths.. – amitsbajaj Nov 15 '13 at 09:43

2 Answers2

1

The entry duplicates because in the Names tab you're querying the Transactions table and then getting the name of the person who did that transaction. Because John has two transactions, you get his name twice.

Try something like this:

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];

Like this you should get every name (person) only once (assuming the relationship between Names and Transactions is one-to-many).

EDIT: Every time you create a Transaction, there are two things that can happen with the Person:

  • it can be a new Person, and you should create a new entity; or,
  • it is a new Person.

The logic is like this, and you'll have to figure out the code:

  1. When the user is adding a Transaction, you'll need to check if it's for a new person or an existing one — if it's an existing one, maybe he can choose it from a list of Persons (and when he selects a person, you get its NSManagedObjectID). If it's a new one, you create it on the spot.

  2. After creating the Transaction object, you need to add the Person object to its relationship. If I saw correctly, you have a whoBy relationship in the Transaction object. Set it to the Person object you got in step 1.

  3. Set all the other fields of the Transaction object (amount, etc.).

  4. Save the NSManagedObjectContext when you're done with all that.

What you're doing in your code at the moment, is create a new Person for every Transaction, which is why you see multiple entries of the same name.

nikolovski
  • 4,049
  • 1
  • 31
  • 37
  • Thanks very much Marko - that makes sense and as you mentioned it, I'm realising the issue with my code. Right now, I have the Add Entry VC adding directly to every component, so it adds the Name with transaction.name, it adds the Amount with transaction.amount (or something similar) so I kind of have the Transaction class doing everything, when I should actually split it out and say, if I'm adding a name, call the Name class. I suspect that would make this approach easier. Though I'm thinking uniqueID attributes might be good because what if I have two separate John Smith? – amitsbajaj Nov 15 '13 at 09:46
  • You don't have to manage your own uniqueID — Core Data already manages a uniqueObjectID for each `NSManagedObject`. When creating a new `Transaction`, get the uniqueID of the `Person` (assuming it's a transaction for an existing person) and set it for the `whoBy` relationship of the `Transaction`. – nikolovski Nov 15 '13 at 09:56
  • Check the `NSManagedObject` method `- (NSManagedObjectID *)objectID` – nikolovski Nov 15 '13 at 09:57
  • And yes, the optimal way would be to have the `Person` table have a to-many relationship with the `Transaction` table, so `Transaction` only references the person, and not save the name itself. By your `NSFetchedResultsController` code I see that's already the case? – nikolovski Nov 15 '13 at 10:00
  • Thanks @Marko, that's very interesting with the objectID - that could potentially make life much easier. I'm not sure if I am, basically when I add an entry, I'm calling NSManagedObject *transaction = [NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:context]; NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context]; So I'm kind of creating everything that way, rather than calling the specific Person subclass, etc.. I'm not sure if I even need to do that – amitsbajaj Nov 15 '13 at 10:06
  • I'm looking into the NSManagedObjectId and that is making sense - I get it but sorry, I'm just being blur here, but I'm creating the Transaction object in the Add Entry, how would I add code to acquire the unique ID of a Person.. like what kind of code would I put in, and is that the right place to do it? Sorry - first app - lots to learn! – amitsbajaj Nov 15 '13 at 10:08
  • No problem! :) I'll edit my answer with a few more suggestions. – nikolovski Nov 15 '13 at 10:09
  • Thanks very much Marko - that is immensely helpful and I will certainly - that also totally makes sense that I'm creating a new transaction with a new person every time rather than using an existing one - Insanely helpful! Thanks again - I will work through that now :) – amitsbajaj Nov 15 '13 at 10:29
  • No problem, I remember how it was when I started a couple of years back :) Glad I could be of help, good luck! – nikolovski Nov 15 '13 at 10:32
  • Hi @Marko Nikolovski - hope you're well. I've been battling with this for a few days and first went down the predicate route which was not working and then tried the nsmanagedobject id route which also doesn't seem to be working for me - it seems to still be creating a new entry every time for me.. do you have any example code you could potentially point me to pls regarding the "obtain the managedobjectID for a Person when getting it? Thanks! – amitsbajaj Nov 19 '13 at 07:25
1

Yes you can tell CoreData that you want only distinct values by setting:

NSFetchRequest *request;
request.propertiesToFetch = [NSArray arrayWithObject:@"Name"];
request.returnsDistinctResults = YES;
request.resultType = NSDictionaryResultType;
...

But if you don't want 2 Johns in database then it is better practice that you don't put them there than to filter results. So when you insert data check if you already have that entry and If you do don't insert it (you can just update that record)...

Mundi
  • 79,884
  • 17
  • 117
  • 140
AntonijoDev
  • 1,317
  • 14
  • 29