I have a simple has many relationship between 2 entities in Core Data:
Team <------->> Games
When I insert a new game managed object into a context, I need to query some properties of the team entity, at the model layer. However, when I use awakeFromInsert the relationship has not been set yet, so team is nil.
// game.m
- awakeFromInsert
{
[super awakeFromInsert];
if ([self team] isActive] {
//.... set game properties
}
}
Is there a way to build the relationship before calling insert and setting the relationship after:
Game *newGame = [NSEntityDescription insertNewObjectForEntityForName:#"Game"
inManagedObjectContext:managedObjectContext];
[newGame setTeam:team];
In rails I would use #team.games.build but this doesn't seem possible in Core Data.
I suspect you will either need a custom method such as + (Game *)insertGameForTeam:(Team *)team and do your team checks there or else handle the relationship conditions in - (void)willSave if you need to keep it at the model level.
Using only the default logic, the object doesn't exist until you finish inserting it and you can't associate it with another object until it exists.
Related
i would like to have some clarification about the correct approach to prefill a (complex) DB with multiple entities.
This is the approach i want to use:
Creation of a separate project that cares about DB Population
In case of Entities with no relations: parsing of a .json file in order to gather information of the Entity
In case of Entities with relation with other Entities (1:N relation):
I have a .json file for each entity
I allocate a Managed Object for each element involved with the relation between the Entities and i fill their attributes.
Please consider the example below for better explaination:
Let's consider the DB contains two Entities: Owner and House with a (1:N) relation.
In order to prefill the DB I:
configurea owner.json file and a house.json file
parse owner.json and gather and create a ManagedOwnerObject
parse house.json and create a set of ManagedHouseObject that have a relation with the first object created.
fill the attribute of ManagedOwnerObject that describe the relation with ManagedHouseObject with the NSSet just created
Iterate the process for all the house.json
Now, this process seems to me a little complex considering that my application has something like 10 Entities connected with 1:N relation.
Could you please suggest me if i'm doing right or if other better solutions could be considered?
Kind regards
Nicolò
You could simply fill the database manually using your app and ship the resulting SQLite file with your app instead of creating an empty one when the user opens the app for the first time.
EDIT:
Using KVC could simplify the creation of the objects. Setting the relationships probably isn't that easy. I would go through all of the data twice. Create the object without relationships in the first run and set up the relationships in the second.
Here is a method to do something similar, without relationships though:
-(void)createEntities:(NSString *)entityName fromFile:(NSString *)filePath inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
// Remove all existing objects of this entity.
[self removeExistingEntriesForEntityName:entityName managedObjectContext:managedObjectContext];
// Read all data from file path.
NSArray *newObjects = [self readTestDataFromFilePath:filePath];
// Insert new object for all existing keys.
for (NSDictionary *no in newObjects) {
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:managedObjectContext];
for (NSString *key in no.keyEnumerator) {
[managedObject setValue:[no valueForKey:key] forKeyPath:key];
}
}
[(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
}
I have implemented awakeFromInsert to set some default values and relationships in my core data objects. However, the method is being called twice, meaning that the to-many values I am adding are being added multiple times.
I am using parent and child managed object contexts.
What gives?
awakeFromInsert will be called when you insert the object into its initial context. When this context is saved and the changes are pushed up to the parent context, it will be called again.
You can query the self.managedObjectContext property to determine which case the method is being called for. Depending on your particular use case, you may want to check for the presence or absence of a parentContext and act accordingly.
thanks to jrturton help:
here is the simplest one:
when parentContext is null, means when this context is saved you can do you custom logic, for example incrementing table number
- (void)awakeFromInsert
{
if (!self.managedObjectContext.parentContext) {
//setting tableNumber
[self willChangeValueForKey:#"number"];
[self setPrimitiveNumber:tableNumber];
[self didChangeValueForKey:#"number"];
}
}
I have the following Core Data model, in which ProFormaPeriod is a subclass of the abstract entity FiscalPeriod. In my fetch request, I would like to traverse from CalendarPeriod to IBEstType.
How can I do this as fiscalPeriod is the only relationship I get offered in code completion and not ProformaPeriod.
Would I need to model an additional direct relationship from CalendarPeriod to ProformaPeriod?
One way is to fetch FiscalPeriod and traverse the result to check for the right class and filter out the correct IBEstType(s). It should still be quite efficient as opposed to a direct key path in the predicate, depending on the size of your data.
Otherwise, yes, you would have to make the child entity a direct relationship.
To check for the class:
for (NSManagedObject *obj in fetchedFiscalPeriods) {
if ([obj isKindOfClass:[ProformaPeriod class]]) {
ProformaPeriod *period = (ProformaPeriod*) obj;
// check period.estimateType
}
}
pretty new to objective-c and VERY new to CoreData - this question feels ridiculously simple, but I can't figure it out despite 1.5 hours of searching! looking for greater minds.
situation: have an Entity in CoreData ("AssetType"), and that entity has one Attribute ("label"). AssetType has a to-many relationship with another entity ("Items"). Items has a to-one relationship to AssetType there are currently 3 values possible for a "label" - "Electronics", "Furniture", "Jewelry".
goal: very simply, i would like to remove one of the values from all objects. i would prefer this be done all in one go instead of via a for-loop on the "many" objects (eww), but truthfully i'm just lost in CoreData and syntax so whatever you can provide would be awesome.
code structure/background: I can paste more in if needed, but i'm using a generic UITableView + UINavigationItem editButtonItem to execute edits (within a UIPopoverController if that matters) - that's where the delete method is coming from - and i'm capturing it via the tableView:commitEditingStyle:forRowAtIndexPath: delegate/protocol method. this part is not the problem - i know where to put the code, i'm just lost in CoreData. :(
twist: AssetType does not currently have a class - it exists purely as a property in the Item class (and a separate entity in CoreData), which has been fine up until now.. but maybe when i need to manipulate the attributes (e.g. delete them!), this is when i need to introduce its own class? hoping that's not the case.
thanks folks!
Alternatively if you actually want to remove the AssetType altogether, you can set a delete rule in your model for the reverse relationship from AssetType back to Item. In this case it sounds like you would want a Nullify rule,
Then you would simply delete the AssetType object in question which would then automatically nullify all links from Items that had that AssetType on the next save.
// Get descriptions of our entities so we can create some.
NSEntityDescription *assetTypeEntityDescription = [NSEntityDescription entityForName:#"AssetType" inManagedObjectContext:self.managedObjectContext];
NSEntityDescription *itemEntityDescription = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
// Create some asset types
AssetType *furnitureAssetType = [[AssetType alloc] initWithEntity:assetTypeEntityDescription insertIntoManagedObjectContext:self.managedObjectContext];
AssetType *electronicsAssetType = [[AssetType alloc] initWithEntity:assetTypeEntityDescription insertIntoManagedObjectContext:self.managedObjectContext];
AssetType *jewelryAssetType = [[AssetType alloc] initWithEntity:assetTypeEntityDescription insertIntoManagedObjectContext:self.managedObjectContext];
furnitureAssetType.label = #"Furniture";
electronicsAssetType.label = #"Electronics";
jewelryAssetType.label = #"Jewelry";
// Create some items
Item *item1 = [[Item alloc] initWithEntity:itemEntityDescription insertIntoManagedObjectContext:self.managedObjectContext];
item1.assetType = furnitureAssetType;
Item *item2 = [[Item alloc] initWithEntity:itemEntityDescription insertIntoManagedObjectContext:self.managedObjectContext];
item2.assetType = electronicsAssetType;
Item *item3 = [[Item alloc] initWithEntity:itemEntityDescription insertIntoManagedObjectContext:self.managedObjectContext];
item3.assetType = jewelryAssetType;
[self.managedObjectContext save:nil];
NSLog(#"item1 asset type is:%#", item1.assetType.label);
// Output: item1 asset type is:Furniture
// Delete the furniture asset type
[self.managedObjectContext deleteObject:furnitureAssetType];
NSLog(#"item1 asset type is:%#", item1.assetType.label);
// Output: item1 asset type is:Furniture
// Save the changes..
[self.managedObjectContext save:nil];
NSLog(#"item1 asset type is:%#", item1.assetType.label);
// Output: item1 asset type is:(null)
// Because of the delete rule when the furniture object is deleted relationships that pointed to it are nulled out.
// The furniture asset type no longer exists.
// There are now only 2 asset types in the persistent store.
Just to be clear: you've two entities, "Asset Type" and "Items", which have a many-to-many relationship. You want to have all items sever their connection with a certain asset type simultaneously and, ideally, you don't want to iterate through items. Have I got it?
If your asset types are genuinely unique (so, there's exactly one that has the label 'Furniture', for example) then you can just unlink that way around — relationships in Core Data are always bidirectional and unlinking from one side automatically unlinks from the other. NSManagedObject subclasses also have accessors that can add or remove things at the end of 'to many' relationships in sets. So e.g.
[furnitureObject removeItems:furnitureObject.items];
That is assuming that your asset type entity has a 'to many' connection named 'items' that links to items.
Otherwise you can run a predicate to get the list of all assets with the string name 'Furniture' and iterate through those, I guess.
During the creation of a Core Data entity (Event), I am creating a relationship to another entity (Team). This relationship is many-to-one from Team to Events (one team, many events) and has an inverse relationship from Event to Team.
Team<----->>Event.
The delete rule for both relationships is set to 'Nullify'.
The below block of code works successfully on first population when a new Team is created during the creation of each Event. However, if I then remove an Event and attempt to re-add it, the existing Team is retrieved but the code fails when attempting to add the Team object to the Event in the final line of the example. The error is as follows: -[__NSCFDictionary managedObjectContext]: unrecognized selector sent to instance 0x699ed60
What is the correct way to create a relationship between the Event object to the Team object that already exists?
Team *currentTeam = self.team;
Team *newTeam = (Team *)[self loadTeamForNid:[NSNumber numberWithInteger: [teamNid integerValue]]];
// If the nid of the referenced team has changed,
if (![[[currentTeam nid] stringValue] isEqualToString:teamNid]) {
currentTeam = nil;
currentTeam = newTeam;
}
// If an event has not been set by this point, it does not exist in the CD store, and we need to create it.
if (currentTeam == nil) {
currentTeam = (Team *)[NSEntityDescription insertNewObjectForEntityForName:#"Team" inManagedObjectContext:[delegate managedObjectContext]];
[currentTeam populateTeamWithNode:[node nodeGet:teamNid]];
}
// TODO: This breaks on reload of an object
// self.team = currentTeam;
[self setValue:currentTeam forKey:#"team"];
Conceptually, you aren't mistaken: you set the event's "team" property to an instance of NSManagedObject that represents the appropriate team.
This message:
-[__NSCFDictionary managedObjectContext]: unrecognized selector sent to instance 0x699ed60
Means that some line of code is handling an instance of NSDictionary where it expects (I assume) an instance of NSManagedObject. When it tries to query the object's managedObjectContext, an exception is thrown, because an NSDictionary doesn't implement a method for that selector.
The first thing to do is put a breakpoint on that last line and see if currentTeam is actually an NSDictionary in disguise. (This seems unlikely, given the code above an exception would have been hit earlier.) If not, you'll have to hunt around for related properties that might be involved in this code path.
Note that Core Data supports a fetch request style where it returns NSDictionary instances instead of NSManagedObjects; if you are using this anywhere in your code, you might be accidentally passing the result along to another method that doesn't expect it.