views:

871

answers:

1

Is it possible to model relationships between entities that are defined in separate NSManagedObjectModels if the entities are always used within an NSManagedObjectModel that is created by merging the relevant models?

For example, say model 1 defines an entity Foo with relationship (one-to-one) toBar and that model 2 defines an entity Bar with a relationship (one-to-one) toFoo. I will build a CoreData stack using -[NSManagedObjectModel mergedModelFromModels], merging model 1 and model 2. Is there any way to define these relationships either in the data modeler or programatically so that they behave as if they were in-model relationships?

+4  A: 

Neither model 1 nor model 2 will be loadable at run time unless they're well-formed — that is, unless the toBar and toFoo relationships have destinations. Furthermore, if model 1 and model 2 have identically-named models, you won't be able to create a merged model from them; they will not be coalesced, they will collide, which is an error.

However, you can use the NSManagedObjectModel API manually to load each model and create a new model by hand that contains entities from both. The NSEntityDescription and NSPropertyDescription classes (and its subclasses) do implement the NSCopying protocol so in most cases you should just be able to copy properties over from each component model to your overall model.

Furthermore, the NS*Description classes all support a userInfo dictionary that you can edit in Xcode's data modeling tool, which you can use to do things like tag the destination of a relationship as a stand-in. For example, in model 1 you could have a Bar entity with a userInfo key MyRealEntity and check for that when creating your merged model, as a signal to use the real entity instead.

You'll also want to put stand-in inverse relationships to your stand-in entities; these will be replaced with real inverses after merging. You don't have to totally replicate your stand-in entities in all models, though; you only need the inverse relationships used in your real model in a stand in entity.

Thus if your real Foo has a name attribute, and your real Bar has a kind attribute, your stand-in Foo and Bar won't need those, just stand-in toBar and toFoo relationships.

Here's some code demonstrating what I'm talking about:

- (NSManagedObjectModel *)mergeModelsReplacingDuplicates:(NSArray *)models {
    NSManagedObjectModel *mergedModel = [[[NSManagedObjectModel alloc] init] autorelease];

    // General strategy:  For each model, copy its non-placeholder entities
    // and add them to the merged model. Placeholder entities are identified
    // by a MyRealEntity key in their userInfo (which names their real entity,
    // though their mere existence is sufficient for the merging).

    NSMutableArray *mergedModelEntities = [NSMutableArray arrayWithCapacity:0];

    for (NSManagedObjectModel *model in models) {
        for (NSEntityDescription *entity in [model entities]) {
            if ([[entity userInfo] objectForKey:@"MyRealEntity"] == nil) {
                NSEntityDescription *newEntity = [entity copy];
                [mergedModelEntities addObject:newEntity];
                [newEntity release];
            } else {
                // Ignore placeholder.
            }
        }
    }

    [mergedModel setEntities:mergedModelEntities];

    return mergedModel;
}

This works because copying of NS*Description objects in Core Data is by-name rather than by-value with respect to a relationship's destination entity and inverse (and to an entity's subentities as well). Thus while a model is mutable — that is, before it's set as the model for an NSPersistentStoreCoordinator — you can use tricks like this to break your model into multiple models.

Chris Hanson