objective-ccocoacore-datansmanagedobjectnsentitydescription

Adding relationships in NSManagedObjectModel to programmatically created NSEntityDescription


When you write a static library which uses CoreData there's a big mess including a normal .xdatamodeld file into the project because you simply cannot just link its compiled version (.momd) into your binary, so it's better to create the whole NSManagedObjectModel in the code like this:

NSAttributeDescription *dateAttribute = NSAttributeDescription.new;

dateAttribute.name = @"timestamp";
dateAttribute.attributeType = NSDoubleAttributeType;
dateAttribute.optional = NO;
dateAttribute.indexed = YES;

NSAttributeDescription *payloadAttribute = NSAttributeDescription.new;

payloadAttribute.name = @"payload";
payloadAttribute.attributeType = NSBinaryDataAttributeType;
payloadAttribute.optional = NO;
payloadAttribute.indexed = NO;

NSEntityDescription *entry = NSEntityDescription.new;

entry.name = entry.managedObjectClassName = NSStringFromClass(MyCustomEntry.class);
entry.properties = @[dateAttribute, payloadAttribute];

NSManagedObjectModel *mom = NSManagedObjectModel.new;

mom.entities = @[entry];

And everything is just perfect....

But! Wait, if I have more than one entity in my NSManagedObjectModel and they are related (to-many, inversed, and so on), how in the world I gonna connect them in the code, like in example above, without that nice Xcode editor, where you make relationships with several mouse clicks?

Example

Imagine, we have a class MyCustomElement, which is almost the same as in MyCustomEntry from the code above. Now, here're their interfaces how they would appear if I used Xcode generation for entities:

@interface MyCustomEntry : NSManagedObject

@property (nonatomic, retain) NSNumber *timestamp;
@property (nonatomic, retain) NSData *payload;
@property (nonatomic, retain) MyCustomElement *element;

@end

@interface MyCustomElement : NSManagedObject

@property (nonatomic, retain) NSNumber * timestamp;
@property (nonatomic, retain) NSString * identifier;
@property (nonatomic, retain) NSSet *entries;

@end

@interface MyCustomElement (CoreDataGeneratedAccessors)

- (void)addEntriesObject:(MyCustomEntry *)value;
- (void)removeEntriesObject:(MyCustomEntry *)value;
- (void)addEntries:(NSSet *)values;
- (void)removeEntries:(NSSet *)values;

@end

What NSRelationshipDescription I need to create for them and how to init it?


Solution

  • Relationships are described by NSRelationshipDescription objects. The following code creates two entity descriptions for "MyCustomEntry", "MyCustomElement" with relationships

    Both entities have only a string attribute "identifier" (to save some lines of code).

    Objective-c:

    NSEntityDescription *entry = [[NSEntityDescription alloc] init];
    [entry setName:@"MyCustomEntry"];
    [entry setManagedObjectClassName:@"MyCustomEntry"];
    
    NSAttributeDescription *entryIdAttribute = [[NSAttributeDescription alloc] init];
    entryIdAttribute.name = @"identifier";
    entryIdAttribute.attributeType = NSStringAttributeType;
    
    NSEntityDescription *element = [[NSEntityDescription alloc] init];
    [element setName:@"MyCustomElement"];
    [element setManagedObjectClassName:@"MyCustomElement"];
    
    NSAttributeDescription *elementIdAttribute = [[NSAttributeDescription alloc] init];
    elementIdAttribute.name = @"identifier";
    elementIdAttribute.attributeType = NSStringAttributeType;
    
    // To-many relationship from "Element" to "Entry":
    NSRelationshipDescription *entriesRelation = [[NSRelationshipDescription alloc] init];
    
    // To-one relationship from "Entry" to "Element":
    NSRelationshipDescription *elementRelation = [[NSRelationshipDescription alloc] init];
    
    [entriesRelation setName:@"entries"];
    [entriesRelation setDestinationEntity:entry];
    [entriesRelation setMinCount:0];
    [entriesRelation setMaxCount:0]; // max = 0 for to-many relationship
    [entriesRelation setDeleteRule:NSCascadeDeleteRule];
    [entriesRelation setInverseRelationship:elementRelation];
    
    [elementRelation setName:@"element"];
    [elementRelation setDestinationEntity:element];
    [elementRelation setMinCount:0];
    [elementRelation setMaxCount:1]; // max = 1 for to-one relationship
    [elementRelation setDeleteRule:NSNullifyDeleteRule];
    [elementRelation setInverseRelationship:entriesRelation];
    
    [entry setProperties:@[entryIdAttribute, elementRelation]];
    [element setProperties:@[elementIdAttribute, entriesRelation]];
    
    NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
    [mom setEntities:@[entry, element]];
    

    Swift (now updated for Swift 3/4):

    let entry = NSEntityDescription ()
    entry.name = "MyCustomEntry"
    entry.managedObjectClassName = "MyCustomEntry"
    
    let entryIdAttribute = NSAttributeDescription()
    entryIdAttribute.name = "identifier";
    entryIdAttribute.attributeType = .stringAttributeType;
    
    let element = NSEntityDescription()
    element.name = "MyCustomElement"
    element.managedObjectClassName = "MyCustomElement"
    
    let elementIdAttribute =  NSAttributeDescription()
    elementIdAttribute.name = "identifier"
    elementIdAttribute.attributeType = .stringAttributeType
    
    // To-many relationship from "Element" to "Entry":
    let entriesRelation = NSRelationshipDescription()
    
    // To-one relationship from "Entry" to "Element":
    let elementRelation = NSRelationshipDescription ()
    
    entriesRelation.name = "entries"
    entriesRelation.destinationEntity = entry
    entriesRelation.minCount = 0
    entriesRelation.maxCount = 0  // max = 0 for to-many relationship
    entriesRelation.deleteRule = .cascadeDeleteRule
    entriesRelation.inverseRelationship = elementRelation
    
    elementRelation.name = "element"
    elementRelation.destinationEntity = element
    elementRelation.minCount = 0
    elementRelation.maxCount = 1 // max = 1 for to-one relationship
    elementRelation.deleteRule = .nullifyDeleteRule
    elementRelation.inverseRelationship = entriesRelation
    
    entry.properties = [entryIdAttribute, elementRelation]
    element.properties = [elementIdAttribute, entriesRelation]
    
    let mom = NSManagedObjectModel()
    mom.entities = [entry, element]
    

    I have tested this code and it seems to work, so I hope that it will be useful to you.