1

I am developing an IOS application which has core data and I have to preload data many to many relationship to the core data. I have three tables with supermarket, product and intermediate table called supermarketToproduct which stores the relationship between supermarkets to products. As I stated, there is many to many relationship between supermarket and product entities hence I needed to create an intermediate table to indicate the relationships. My question is that what is the best practice to preload JSON formatted data into my core data with many to many relationship. In Apples reference documents, it is stated that there is no need to create intermediate table as coredata creates one for us. However, How can I define many to many relationship while preloading the date without intermediate table? By the way, all the data is static so no need to insert new data into tables, I only need fetch the data and its related supermarket or product. Please note that joined table has two independent attributes called priceofProductforGivenMarket and primaryProductforGivenMarket Here is the JSON representation of my data, also it is the model that I would create in core data:

Supermarket:
name
location
phone number

Product:
name
price
company

supermarketToproduct:
 nameofsupermarket
 nameofproduct 
 priceofProductforGivenMarket
 primaryProductforGivenMarket
emkay
  • 187
  • 12

2 Answers2

1

I think the above approach would be hard on prefetching and would prefetch almost all matching entries and the real motive of prefetch would be gone.

also I would suggest to add topProduct in Supermarket table.

You must define many-to-many relationships in both directions—that is, you must specify two relationships, each being the inverse of the other

Edit 01:

Product (Attributes):
name
price
company

Supermarket (Attributes):
name
location
phone number
supermarket top Product

Once you create Attributes like above and add the many to many relationship.

Coredata will add below to the respective entity for relationships.

**NSSet products, [supermarket table] and NSSet supermarkets [product table]**

So now you can actually update this relationship after inserting the data as bellow: i.e.

Insert

    // Create Product
    NSManagedObject *product = [[NSManagedObject alloc] initWithEntity:@"Product" insertIntoManagedObjectContext:self.managedObjectContext];

    // Set details
    [product setValue:@"xxx" forKey:@"name"];
    ...

// Create Supermarket

    NSManagedObject *supermarket = [[NSManagedObject alloc] initWithEntity:@"SuperMarket" insertIntoManagedObjectContext:self.managedObjectContext];

    // Set First and Last Name
    [supermarket setValue:@"Main Street" forKey:@"name"];
    ...

// Create Relationship

    [supermarket setValue:[NSSet setWithObject:product] forKey:@"products"];

    // Save Managed Object Context
    NSError *error = nil;
    if (![supermarket.managedObjectContext save:&error]) {
        NSLog(@"Unable to save managed object context.");
        NSLog(@"%@, %@", error, error.localizedDescription);
    }

//And similarly inverse relationship.

Fetching

also now since you have related information about product in Supermarket table and vice versa, the fetching will be smoothing and as discussed before, will not prefetch all data upfront which it would have done otherwise. As there is no need to create further relationship by coreData fetch.

Please follow for more details: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW10

Also Please follow this: Core Data sectionNameKeyPath with Relationship Attribute Performance Issue

Community
  • 1
  • 1
bllakjakk
  • 5,045
  • 1
  • 18
  • 28
  • Thank you very much for your quick answer. I already read apples document but could not find related solution. What do you mean by adding topProduct ? You mean I should create a attribute in supermarket table called topProduct which shows the relationship between supermarket and product? – emkay Sep 21 '14 at 17:54
  • But how does it create relationship from products to supermarket? As it is logically many to many relationship, I also need it. Can you please give more details? – emkay Sep 21 '14 at 18:10
  • By the way, all the data is static so no need to insert data to tables, I only need fetch the data and its related supermarket or product. – emkay Sep 21 '14 at 18:14
  • Updated about relationship and fetch above. – bllakjakk Sep 21 '14 at 18:36
1

I would create three entities, Supermarket, Product and SupermaketProductDetails, with attributes and relationships as follows:

Supermarket:
Attributes: name, location, phone number
Relationship (to many): productDetails

Product:
Attributes: name, price, company
Relationship (to many): supermarketDetails

SupermarketProductDetails:
Attributes:  priceofProductforGivenMarket, primaryProductforGivenMarket
Relationships: (to one) supermarket, (to one) product

In Xcode, indicate that the "destination" for the productDetails relationship in the Supermarket entity is the SupermarketProductDetails entity, with inverse supermarket, likewise set the SupermarketProductDetails entity as the destination for the supermarketDetails in the Product entity, with inverse product.

Then I would parse your JSON data for supermarkets first, creating Supermarket objects and setting the name, location and phone number but without entering anything for the products relationship. Likewise, parse the products JSON and create all the Products objects. Then I would parse the join table, and create the SupermarketProductDetails objects. Set the attributes based on your JSON data, and execute a fetch to get the Supermarket entity with the right name, likewise fetch the Product entity with the right name, and then set these relationships directly.

EDIT: Assume that you parse each line in your join table into four NSStrings: nameofsupermarket, nameofproduct, priceofProductforGivenMarket and primaryProductforGivenMarket. Then for each line...

// First fetch the correct Supermarket...
NSFetchRequest *supermarketFetch = [NSFetchRequest fetchRequestWithEntityName:@"Supermarket"]
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@",nameofsupermarket];
supermarketFetch.predicate = predicate;
NSError *error;
NSArray *results = [context executeFetchRequest:supermarketFetch error:&error];
// should really check for errors, and that we get one and only one supermarket
NSLog(@"Supermarket fetch returned %i results",[results count]);  // should be only 1!
mySupermarket = (NSManagedObject *)[results firstObject];
if (![[mySupermarket valueForKey:@"name"] isEqualToString:nameofsupermarket]) {
    NSLog(@"Wrong supermarket fetched");
    }

// Now fetch the product...
NSFetchRequest *productFetch = [NSFetchRequest fetchRequestWithEntityName:@"Product"]
predicate = [NSPredicate predicateWithFormat:@"name like %@",nameofproduct];
productFetch.predicate = predicate;
results = [context executeFetchRequest:productFetch error:&error];
// should really check for errors, and that we get one and only one product
NSLog(@"Product fetch returned %i results",[results count]);  // should be only 1!
myProduct = (NSManagedObject *)[results firstObject];
if (![[myProduct valueForKey:@"name"] isEqualToString:nameofproduct]) {
    NSLog(@"Wrong product fetched");
    }

// Now create the SupermarketProductDetails entity:
NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context];
// set attributes...
[mySupermarketProductDetails setValue:primaryProductForGivenMarket forKey:@"primaryProductForGivenMarket"];
[mySupermarketProductDetails setValue:priceOfProductForGivenMarket forKey:@"priceOfProductForGivenMarket"];
// set relationships...
[mySupermarketProductDetails setValue:mySupermarket forKey:@"supermarket"];
[mySupermarketProductDetails setValue:myProduct forKey:@"product"];
[context save:&error];
// should check for errors...

Note that you only need to set one side of these relationships - CoreData updates the other side for you (i.e. it will add mySupermarketDetails to the set of supermarketDetails for myProduct, etc). Note also that the "value" for a (to-one) relationship (e.g. supermarket) is the destination object itself (e.g. mySupermarket); you don't use the name or any other key. Coredata is (hidden in the underlying sql tables) using unique objectIDs to do the linking up.

(There are probably more efficient means of doing this, rather than doing two fetches for every SupermarketProductDetails entry, but this will work.)

EDIT2: Note that the above assumes that your entities are all implemented as NSManagedObjects. If you have created separate subclasses for each entity, then you can simplify some of the above code. For example, valueForKey: and setValue:forKey: can be replaced by the equivalent property accessor methods using dot notation, eg.:

[mySupermarketProductDetails setValue:primaryProductForGivenMarket forKey:@"primaryProductForGivenMarket"];

would become:

mySupermarketProductDetails.primaryProductForGivenMarket = primaryProductForGivenMarket;

and

[mySupermarket valueForKey:@"name"]

can be replaced by:

mySupermarket.name

Likewise, objects should be created with the appropriate subclass rather than NSManagedObject. eg.

NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context];

would become

SupermarketProductDetails *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context];
pbasdf
  • 21,386
  • 4
  • 43
  • 75
  • This is very very good point. I forgot to mention about it. Yes I have two other attributes for joint table. So you are saying that I need to build the join table so can you please give me some information about how to do that? Thanks again for the very important remark? – emkay Sep 21 '14 at 18:56
  • So, your table structure will need to be slightly different - I'll edit my response. – pbasdf Sep 21 '14 at 19:01
  • Thanks, I look forward to see your approach – emkay Sep 21 '14 at 19:05
  • Thank you very much for your response. The solution seems clear for me except one point. As you can see from my question, SupermarketProductDetails has 4 attributes and 2 of them are nameOfSupermaerket and nameofProduct which create the relation between two tables such as Tesco and CocaCola. In your approach, I could not understand how to use these attributes to create relationship with using your code of: [mySupermarketProductDetails setValue:fetchedSupermarket forKey:@"supermarket"]; [mySupermarketProductDetails setValue:fetchedProduct forKey:@"product"] – emkay Sep 22 '14 at 00:19
  • You would use the name as part of the fetch - I will edit my response to expand on this. – pbasdf Sep 22 '14 at 07:03
  • Hello, Thank you very much for your editing and response again. But still having some trouble. In your answer, you have taken out nameofsupermarket nameofproduct from join table which create relationship?Am I right or you just have forgotten to include these attributes in your answer? Moreover, in your second line of the code, you have NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@",nameofsupermarket]; so where nameofsupermarket is coming from? – emkay Sep 22 '14 at 20:18
  • You are right - Coredata does not use the name as the link between the join table and the other tables. As far as Coredata is concerned, the supermarket and product are both properties of the SupermarketProductDetails entities. But to populate the join table from your JSON data, we need to use the name to find the right Supermarket and Product objects to assign to the SupermarketProductDetails properties. So in the above code, nameofsupermarket, nameofproduct, primaryProductForGivenMarket and priceOfProductForGivenMarket are all values that you need to get from your JSON data. – pbasdf Sep 22 '14 at 20:54
  • Thank you I will try to implement as you say. – emkay Sep 22 '14 at 21:22
  • Hello, I have implemented your solution and it really works good except one point. When I take a loo at the core data joint table is created but the product names are not correct.Such as supermarket has 7 products, I can see there are 7 products for a single supermarket but product names are incorrect. Do you have any idea why it happens? Small note in your answer you have line of NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context]; – emkay Sep 23 '14 at 15:11
  • Small note in your answer you have line of NSManagedObject *mySupermarketProductDetails = [NSEntityDescription insertNewObjectForEntityForName:@"SupermarketProductDetails" inManagedObjectContext:context]; but I believe it should be SupermarketProductDetails instead of NSManagedObject. If you think it is correct, can you please update your answer so that other people can also get benefit from your answer. – emkay Sep 23 '14 at 15:18
  • I can't see any reason that the names would be wrong, but my guess is that the predicates for the fetches are returning the wrong results. I'll amend the answer to include some NSLogs to test this. As regards the class for mySupermarketProductDetails, I had for simplicity assumed you were working with native NSManagedObjects, but if you have implemented specific subclasses for each entity, then you are correct - the class would be SupermarketProductDetails. This also enables the use of dot notation for property values; I'll update the response accordingly. – pbasdf Sep 23 '14 at 17:14
  • I think the wrong names may be due to using "==" in the predicate, rather than "like". I'll amend that too. – pbasdf Sep 23 '14 at 17:45
  • It was amazing answer and help. Thank you very much. I believe this answer not only will help me, but also many others. I notice that there is lack of resource for this kind of many to many approach implementation on the web which can be a big pain for newbies in core data. Thank you really very much. Just a small question: Suppose I want to get the products for selected supermarket. As there is 2 many from supermarket to join table and many to one to product, I can not figure out how to get multiple products for a given supermarket in this relationship. Any recommendations or codes for it? – emkay Sep 23 '14 at 18:42
  • You can use Key Value Coding: `[mySupermarket valueForKeyPath:@"productDetails.product"]` should give you an NSSet of the products for mySupermarket. – pbasdf Sep 23 '14 at 23:30
  • Yes It is okey with KVC but suppose that I also want to get priceofProductforGivenMarket, primaryProductforGivenMarket values for selected supermarket for all related products. Is it also possible with KVC ? For example, when I select a supermarket, I want to show its product sets with names, companies and normal price as well as products' price and primary product for given market? Do you have recommendation for it? Should I go back to join table for every product and fetch ? – emkay Sep 24 '14 at 20:00
  • What is the reason to stick with the intermediate table for relations? Anyway, I am currently working on a very similar issue, but instead went with a many-to-many relationship. My model is not about products and supermarkets but if using that analogy the relationship would be directly between these two entities, and no third entity exists. Well, I have a couple of hundreds of thousands of relations and it turned out that "connecting" all these up with each other was painfully slow. I am thinking I should ignore the Core Data docs and go with a "normal" intermediate table like in this answer. – Jonny Jul 30 '15 at 04:22
  • Just a guess, the reason is to speed up the initial insert/migration process? I just feel something about Core Data is not quite right there. – Jonny Jul 30 '15 at 04:26
  • @Jonny The intermediate "join" entity was necessary in this instance because the relationship between Supermarket and Product has attributes eg. priceofProductforGivenMarket. If you just use a many-many relationship between Supermarket and Product, you have nowhere for those attributes to be modelled. I'm not sure using an intermediate entity will speed up your import process dramatically. – pbasdf Jul 30 '15 at 07:27
  • @Jonny Have you tried the techniques in Apple's Core Data Programming Guide, [Efficiently Importing Data](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174-SW1)? – pbasdf Jul 30 '15 at 07:45
  • Ok that makes sense. And no, that's something I'll definitely read up on. Thanks. – Jonny Jul 30 '15 at 07:49