Storing Related Entities in Core Data - ios

I new to core data, it would great if someone can give feedback/hint me with given requirement.
Entity A
{
property 1;
property 2;
property 3;
}
Entity B
{
property 1;
property 2;
property 3;
}
Entity C
{
EntityB ObjB; // pointing to Entity B
EntityA ObjA; // pointing to Entity A
property 1;
property 2;
property 3;
}
Entity D
{
EntityB ObjB; // pointing to Entity B
EntityA ObjA; // pointing to Entity A
property x;
property y;
property z;
}
Newly Inserted objected needs to be store into Entity C,Entity D with few more properties.
for isolation of insertion to main context below mentioned approach is used
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityB" inManagedObjectContext:self.managedObjectContext];
objB = [[EntityB alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
As I have store objB & objA into EntityC, EntityD below method will be use.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityC" inManagedObjectContext:self.managedObjectContext];
objC = [[EntityC alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
objC.objB = objC;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EntityD" inManagedObjectContext:self.managedObjectContext];
objD = [[EntityD alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
objD.objB = objB;
then finally I will save all objects into main context.
[self.managedObjectContext insertObject:objB];
[self.managedObjectContext insertObject:objC];
[self.managedObjectContext insertObject:objD];
[self.managedObjectContext save:&error];
Since I am not have much experience on core data I would like to know they way I am storing information is good or there is another approach to store data in mentioned scenarios.
Thanks for your valuable time.

This:
objC.objB = objC;
does not make sense and is probably a typo and you meant
objC.objB = objB;
This:
objD.objB = objB;
is correct and establishes the relationship between objD and objB.
These calls:
[self.managedObjectContext insertObject:objB];
[self.managedObjectContext insertObject:objC];
[self.managedObjectContext insertObject:objD];
are not needed, because initWithEntity:insertIntoManagedObjectContext: already
inserts the objects into the context.
Note that you should also define inverse relationships from EntityA and EntityB
to EntityC and EntityD, I cannot see from your question if you already did that.

Related

Entity relationship between NSManagedObjects issue

I'm running into a CoreData problem related to one-to-many relationships.
Entity 1 - Authors has a one-to-many relationship with Entity 2 - Books. I think Books has a one-to-one relationship with Authors.
Since there are multiple books per author in the author object I have
#property (nonatomic, retain) NSSet *book;
The corresponding property in the book object is:
#property (nonatomic, retain) Authors *author;
When the app downloads books from the server through an API, after saving the book, I am trying to also save the author and associate the book with the author with the following code:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Books" inManagedObjectContext:self.managedObjectContext];
NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
record setValue:bookname forKey:#"bookname"];
if ([self.managedObjectContext save:&error]) {
Authors *newAuthor = [NSEntityDescription insertNewObjectForEntityForName:#"Authors" inManagedObjectContext:self.managedObjectContext];
newAuthor.aid = authorid;
newAuthor.book = record;
}
This code has worked for me for one to one relationships, but in this case, is throwing following exception error:
'NSInvalidArgumentException', reason: 'Unacceptable type of value for
to-many relationship: property = "book"; desired type = NSSet; given
type = Books;
Can anyone suggest how to fix this?
Update:
I also tried switching it around to read:
record.author = newAuthor;
But this gives error
"property 'author' not found on object of type NSManagedObject"
although there is such a property defined in the Books object (as shown above).
Since the book property is a set, CoreData should have created an Authors method such as addBookObject:. When you created your custom entity classes, there should be a file with a name similar to "Authors+CoreDataProperties.h". Look in there for defined methods.
Your second option should also work if you use Books instead of NSManagedObject.
i.e.:
Books *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
U can generate NSManagedObject subclass, after just create few objects, set relations and save context once, someting like:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Book" inManagedObjectContext:self.context];
Book *newBook = [[Book alloc] initWithEntity:entity insertIntoManagedObjectContext:self.context];
newBook.bookName = #"My new book";
NSEntityDescription *entity2 = [NSEntityDescription entityForName:#"Record" inManagedObjectContext:self.context];
Record *newRecord = [[Record alloc] initWithEntity:entity2 insertIntoManagedObjectContext:self.context];
newRecord.name = #"New record";
newBook.record = newRecord;
[newRecord addBook:[NSSet setWithObject:newBook]];
[self.context save:nil];
This is sample for db like below:
To autogenerate classes - select Entities in *.xcdatamodel file and press File->New->File select CoreData section and NSManaged Object subclass, go by wizard steps.
You will get something like
And even more:
Good tutorial u can also found here

Managed object data one-to-many write data

I have a data model in my small application with two small tables: Test and subTest, where Test can have many subTests.
Into Test table we can add as many as we want items and for each item in Test table there is a bunch of items in subTest table.
I can easily request a data from the subTest table associated with Test1, Test2 etc by using CoreData predicates and then using valueForKeyPath. However, I have the problem with saving data into the subTest table. I cannot managed, how to write data in a way, that later on I can fetch this data by passing the test name (e.g. Test1) and then using valueForKeyPath.
Here is an example to clarify what I did mean.
In table Test is a Test1 associated with the the bunch of subtests(st1, st2). Now the subtest st3 must be added to the subTest table. Later we want to get bunch of subtests associated with Test1:
Test test = call here the method which returns NSManagedObject;
NSMutableSet setOfSubTests = [test valueForKeyPath testTosubtests.toTest]; /* Returns st1 st2 and st3 */
Is it only about to write data to the subTest table using old school indexes? (Test1 has index 1 and in subTest table all subtests associated with it have Test1 index)? Is the right thing to do with a CoreData in iOS? Can I apply the same principles as with common SQL DB?
All those things(CoreData and NSManagedObject) are quite new to me. And I want to understand them better.
You need to stop thinking about core data as database tables. Core data was designed to manage collections of related objects. It's underlying implementation may be an SQL database, or a flat binary file, or some custom store implementation. Do not think of it as a database with tables and such.
Now, as for your example...
I have a data model in my small application with two small tables:
Test and subTest, where Test can have many subTests.
You would have a Test entity, and a Subtest entity. The Test entity would have a to-many relationship to Subtest, since one test can "hold" many Subtest entities.
The Subtest entity would have a to-one relationship to Test since a Subtest can only belong to one Test.
I understand things better in code, so the model may look like this when described in code.
- (NSManagedObjectModel*)modelForTestsAndSubtests {
NSEntityDescription *testEntity = [[NSEntityDescription alloc] init];
testEntity.name = #"Test";
NSAttributeDescription *testName = [[NSAttributeDescription alloc] init];
testName.name = #"name";
testName.attributeType = NSStringAttributeType;
NSEntityDescription *subtestEntity = [[NSEntityDescription alloc] init];
subtestEntity.name = #"Subtest";
NSAttributeDescription *subtestName = [[NSAttributeDescription alloc] init];
subtestName.name = #"name";
subtestName.attributeType = NSStringAttributeType;
// A Test can have many Subtest objects in its relationship
NSRelationshipDescription *testToSubtests = [[NSRelationshipDescription alloc] init];
testToSubtests.optional = YES;
testToSubtests.name = #"subtests";
testToSubtests.destinationEntity = subtestEntity;
testToSubtests.deleteRule = NSCascadeDeleteRule;
testToSubtests.minCount = testToSubtests.maxCount = 0;
testToSubtests.ordered = NO;
// A Subtest can (and must) reference exactly one Test
NSRelationshipDescription *subtestToTest = [[NSRelationshipDescription alloc] init];
subtestToTest.optional = NO;
subtestToTest.name = #"test";
subtestToTest.destinationEntity = testEntity;
subtestToTest.inverseRelationship = testToSubtests;
subtestToTest.deleteRule = NSNullifyDeleteRule;
subtestToTest.minCount = subtestToTest.maxCount = 1;
testToSubtests.inverseRelationship = subtestToTest;
testEntity.properties = #[testName, testToSubtests];
subtestEntity.properties = #[subtestName, subtestToTest];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
model.entities = #[testEntity, subtestEntity];
return model;
}
However, I have the problem with saving data into the subTest table.
Again, don't think of it as saving data into a table. Think of it as "I'm going to add subtest "st1" to "Test1"
You would do that like this...
NSManagedObject *subtest = [NSEntityDescription
insertNewObjectForEntityForName:#"Subtest"
inManagedObjectContext:test.managedObjectContext];
[subtest setValue:test forKey:#"test"];
Note that when you assign the to-one relationship from the Subtest entity to the Test entity, core data will automatically setup the inverse relationship, so you don't have to add the subtest to the to-many relationship in the Test entity.
Now, let's say you insert 10 subtests (this is just for testing)...
for (int i = 0; i < 10; ++i) {
NSManagedObject *subtest = [NSEntityDescription
insertNewObjectForEntityForName:#"Subtest"
inManagedObjectContext:moc];
[subtest setValue:[NSString stringWithFormat:#"st%02d", i] forKey:#"name"];
[subtest setValue:test forKey:#"test"];
}
[moc save:&error]; // Handle failure and error appropriately...
I cannot managed, how to write data in a way, that later on I can
fetch this data by passing the test name (e.g. Test1) and then using
valueForKeyPath.
So, if you want to get the test with name "Test1" you could write something like this...
- (NSManagedObject*)existingTestWithName:(NSString*)name
inMOC:(NSManagedObjectContext*)moc
error:(NSError**)error {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Test"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"name = %#", name];
fetchRequest.fetchLimit = 1;
NSManagedObject *result = nil;
NSArray *fetched = [moc executeFetchRequest:fetchRequest error:error];
if (fetched) {
if (error) *error = nil;
result = [fetched firstObject];
}
return result;
}
Then, you could grab 'Test1' like so...
NSManagedObject *test = [self existingTestWithName:#"Test1" inMOC:moc error:&error];
if (test) {
// Do something with the Test entity that has name "Test1"
}
And then, once you have the test object, you can get access to all the Subtest objects for this test instance via its "subtests" relationship.
NSSet *subtests = [test valueForKey:#"subtests"];
Thus, you can find a specific subtest by simply searching the collection...
- (NSManagedObject*)findExistingSubtestWithName:(NSString*)name
forTest:(NSManagedObject*)test {
// This is "simple" but could yield less than optimal performance
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name = %#", name];
NSSet *subtests = [test valueForKey:#"subtests"];
return [[subtests filteredSetUsingPredicate:predicate] anyObject];
}
However, this causes all subtests to be loaded into memory to perform an iterative search. Tis is fine if the objects are relatively small, and there are a small number of them.
Or, you can actually perform a fetch...
- (NSManagedObject*)fetchExistingSubtestWithName:(NSString*)name
forTest:(NSManagedObject*)test
error:(NSError**)error {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Subtest"];
fetchRequest.predicate = [NSPredicate
predicateWithFormat:#"test = %# AND name = %#", test, name];
fetchRequest.fetchLimit = 1;
NSManagedObject *result = nil;
NSArray *fetched = [test.managedObjectContext executeFetchRequest:fetchRequest
error:error];
if (fetched) {
if (error) *error = nil;
result = [fetched firstObject];
}
return result;
}
Since you seem to be familiar with SQL, here is the SQL that core data generates for the fetch above.
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZTEST
FROM ZSUBTEST t0 JOIN ZTEST t1 ON t0.ZTEST = t1.Z_PK
WHERE ( t0.ZNAME = ? AND t1.ZNAME = ?) LIMIT 1
However, that is just to show you how the fetch happens. You should still think of everything as interconnected objects, and don't worry about the implementation of the store being SQL until you need to be concerned about performance issues.
Note
You can generate subclass code in Xcode and third party tools like mogenerator that can greatly enhance your experience using managed objects. However, it's still good to know how it all works.

CoreData: Create temporary models and maybe save to context

I have a problem with Core Data, because I don't know the best way to handle my problem:
I load a json from a server and parse the results in ManagedObjects. At first the ManagedObjects should be temporary.
But the user can put a ManagedObject to a leaflet. And then the ManagedObject should be saved to CoreData. (The object should be accessible offline)
When the user load the same objects some time later from the server, already saved ManagedObjects should be fetched.
So I don't want to put every object in CoreData / PersistantStore the user doesn't need.
First what I do is to create a background context:
__block NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.parentContext = context;
With a fetch I check if there is already a ManagedObject in the persistantstore.
If there is one, I will take this. else create a new ManagedObject in nil context.
NSArray *results = [backgroundContext executeFetchRequest:fetch error:&error];
if (!error && results.count == 1) {
myModel = [results objectAtIndex:0];
}
else {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyModel" inManagedObjectContext:backgroundContext];
myModel = (MyModel *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
}
And I do the same with every relationship:
if (! myModel.relation) {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Relation" inManagedObjectContext:backgroundContext];
myModel.relation = (Relation *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:myModel.managedObjectContext];
}
Works fine so far with creating the models.
But how to save one model?
The managedObjectContext is nil. If I call save: on the managedObjectContext, it saves everything.
In my AppDelegate i wrote a function to insert a ManagedObject in the main ManagedObjectContext:
- (void)insertObjectAndSave:(NSManagedObject *)managedObject {
if (!managedObject.managedObjectContext) {
[self.managedObjectContext insertObject:managedObject];
}
[self saveContext];
}
Is this a good solution? or is there a better way to save temporary ManagedObjects in the main ManagedObjectContext?
Excellent Answered My Mundi..
Here is on more scenario to create NSManagedObject temporary, whether we can make it permanents If we want.
Create temporary object
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
Code this if you wants to save it permanently
[managedObjectContext insertObject:unassociatedObject];
NSError *error = nil;
if (![managedObjectContext save:&error])
{
//Respond to the error
}
You could not create the objects with nil context but with a "temp" context. If you want to save them, call [tempContext save:nil];. If you want to discard them, just throw away the context.
Another strategy is to avoid the complexity of multiple context altogether by adding a simple boolean attribute temp to your entity (if relationships are one-to-many, only the top level entity needs to have this). You can save by default and display only non-temp items. You can delete temp items, including all related items, immediately or periodically.

Core-data many-to-many relationship

I have an Android app, and now I'm making an iOS version, but I'm having some problem with the joins in CoreData.
I have the following tables:
Cidade
-cid_codigo integer primary key
-cid_nome text
-cid_nome_normalizado text
Anunciante
-anu_codigo integer primary key
-anu_nome text
-some other values
AnuncianteCidade
-cid_codigo integer
-anu_codigo integer
To get the all data from table Cidade I use the following method:
+(NSMutableArray *)getAllCidades{
NSMutableArray *retorno = [[NSMutableArray alloc] init];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"Cidade" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
//WHERE CLAUSE
NSPredicate *pred = [NSPredicate predicateWithFormat:#"1 = 1"];
[request setPredicate:pred];
NSError *error;
NSArray *cidades = [context executeFetchRequest:request error:&error];
if([cidades count] == 0){
NSLog(#"Nenhuma cidade encontrada");
}else{
for(NSManagedObject *cidade in cidades){
Cidade *c = [[Cidade alloc] init];
[c initWithCodigo:[[cidade valueForKey:#"cid_codigo"] integerValue] nome:[cidade valueForKey:#"cid_nome"] nomeNormalizado:[cidade valueForKey:#"cid_nome_normalizado"]];
[retorno addObject:c];
}
}
return retorno;
}
But now, given a name from Cidade, I want to get all the data from Anunciante associated with this Cidade.
How can I do that?
Core Data is not a database. Core Data is an object graph that happens to persist to disk and one of the formats that Core Data can persist to is a database structure. This is an important distinction that will help you to work with it moving forward.
First, you cannot call just -init on a NSManagedObject. That will not work as it is not the designated initializer of NSManagedObject. I would suggest you read up on Core Data and learn how to stand up the Core Data stack.
Having said that, your Cidade objects should have a reference to Anunciante. The join table is internal to Core Data and you don't have access to it nor should you. To get all of the Anunciante objects for a Cidade object is to simply request the objects:
Given an NSArray of Cidade objects:
NSArray *objects = ...;
for (NSManagedObject *object in objects) {
NSSet *anunciantes = [object valueForKey:#"anunciante"];
}
This is assuming you have a many to many relationship defined in the Core Data model editor between the Cidade and the Anunciante entities.
In addition to Marcus' answer, I would add that a predicate "1 = 1" could be simply left out.
To insert a managed object into the context you use a NSEntityDescription class method:
Cidade *cidade = [NSEntityDescription insertNewObjectForEntityForName:#"Cidade"
inManagedObjectContext:context];
All "anunciantes" of a cidade will be conveniently available to you as a NSSet:
cidade.anunciantes
is all you need.

iOS: core data update object with one- with to-many relationship

In my app I have this code to update a DB:
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest=[NSFetchRequest fetchRequestWithEntityName:#"Struct"];
NSError *error = nil;
for (id element in array){
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"id==%#",[element objectForKey:#"id"]]];
Struct *struct = [[context executeFetchRequest:fetchRequest error:&error] lastObject];
if (struct != nil){
//updating value of attributes
struct.name = [element objectForKey:#"n"];
struct.val = [element objectForKey:#"val"];
struct.pos = [element objectForKey:#"pos"];
}
else{
//create a new identity
}
}
it's all ok but I have other two objects in relationship with Struct:
"Id_Loc" (one to many)
"Details" (one to one)
I don't know how to update them, I don't know how to call them from my object 'Struct'
I don't find nothing
Can you help me?
thanks
It works the same way you're using .name, .val and .pos.
The one-to-one relationship will return an object, the one-to-many relationship will return a NSSet.
Say your one-to-one relationship is from Struct to Bar and the relationship name is bar, you'll receive a Bar object by using struct.bar.
Edit:
If you want to add new objects to the one-to-many relationship you should use [struct addFooObject:]; where Foo is your objects name.

Resources