Managed Object wants a per-relationship attribute?

  • It thought this would be a natural for Core Data, but I've run myself
    into a conceptual wall.

    Say that I'm modelling a swimming meet.  My data model has two
    entities: Events and Swimmers.  Of course, each Event requires
    multiple Swimmers, and each Swimmer can swim in several Events.

    My Events should show swimmers ordered by speed: 1st, 2nd, 3rd, etc.
    Since CoreData's to-many relationships are not ordered, I add to
    Swimmer a 'position' attribute.  In the 100-meter freestyle Event,
    Suzie's position is 1 and Jane's position is 2.  But in some other
    event, say the 200-meter butterfly, Jane is faster than Suzie, so the
    positions need to be reversed.  But I can't do that since each
    Swimmer only has one 'position' attribute.

    I can easily extend some Apple examples to run into the same wall.
    Imagine in CoreRecipes, if you added a Pantry entity which contained
    common Ingredients used for different Recipes.  The 'displayOrder'
    attribute would be conflicted among the different Recipes in which
    the Ingredient was used.  Or, imagine in DepartmentsAndEmployees, if
    you added a Projects entity, with Employees having a different
    'function' in each project?

    Duh!  What's the proper way to model this?

    Much thanks,

    Jerry Krinock
  • The proper way to model it is to add another entity in-between. In
    this case, something like "Entry", where an Event has multiple
    Entries, a Swimmer has multiple Entries, and each Entry is for a
    single Swimmer in a single Event. The position is then part of the
    Entry.

    Hope this helps,
    - Greg

    On Sep 15, 2007, at 6:35 PM, Jerry Krinock wrote:

    > It thought this would be a natural for Core Data, but I've run
    > myself into a conceptual wall.
    >
    > Say that I'm modelling a swimming meet.  My data model has two
    > entities: Events and Swimmers.  Of course, each Event requires
    > multiple Swimmers, and each Swimmer can swim in several Events.
    >
    > My Events should show swimmers ordered by speed: 1st, 2nd, 3rd,
    > etc.  Since CoreData's to-many relationships are not ordered, I add
    > to Swimmer a 'position' attribute.  In the 100-meter freestyle
    > Event, Suzie's position is 1 and Jane's position is 2.  But in some
    > other event, say the 200-meter butterfly, Jane is faster than
    > Suzie, so the positions need to be reversed.  But I can't do that
    > since each Swimmer only has one 'position' attribute.
    >
    > I can easily extend some Apple examples to run into the same wall.
    > Imagine in CoreRecipes, if you added a Pantry entity which
    > contained common Ingredients used for different Recipes.  The
    > 'displayOrder' attribute would be conflicted among the different
    > Recipes in which the Ingredient was used.  Or, imagine in
    > DepartmentsAndEmployees, if you added a Projects entity, with
    > Employees having a different 'function' in each project?
    >
    > Duh!  What's the proper way to model this?
    >
    > Much thanks,
    >
    > Jerry Krinock
  • On Sep 15, 2007, at 6:35 PM, Jerry Krinock wrote:

    > Duh!  What's the proper way to model this?

    You a completely separate set of objects (table) to record the
    various orderings of the objects you want to order.

    -Shawn
  • On Sep 15, 2007, at 6:35 PM, Jerry Krinock wrote:
    > Say that I'm modelling a swimming meet.  My data model has two
    > entities: Events and Swimmers.  Of course, each Event requires
    > multiple Swimmers, and each Swimmer can swim in several Events.
    > My Events should show swimmers ordered by speed: 1st, 2nd, 3rd,
    > etc.  Since CoreData's to-many relationships are not ordered, I add
    > to Swimmer a 'position' attribute.  In the 100-meter freestyle
    > Event, Suzie's position is 1 and Jane's position is 2.  But in some
    > other event, say the 200-meter butterfly, Jane is faster than Suzie,
    > so the positions need to be reversed.  But I can't do that since
    > each Swimmer only has one 'position' attribute.
    > [...]
    > Duh!  What's the proper way to model this?
    >
    Summary: You need to make the join table explicit and add an attribute
    to that.

    In a traditional relational database, the many-to-many relationship
    would be expressed using a join table (an intermediate entity).

    Event <-->> SwimmerEvent <<-> Swimmer

    Core Data hides this from you.  This usually makes life easier.  If
    you need to add additional information to the intermediate entity, you
    need to make it explicit and add the attribute yourself.

    So create a new entity (SwimmerEvent or somesuch), break the direct
    relationships between Event and Swimmer, and create relationships to
    SwimmerEvent instead.

    Event
    swimmerEvents ->> SwimmerEvent
    (inverse is event)

    Swimmer
    swimmerEvents ->> SwimmerEvent
    (inverse is swimmer)

    SwimmerEvent
    event -> Event
    swimmer -> Swimmer
    position (Integer)

    mmalc
  • On Sep 15, 2007, at 6:50 PM, mmalc crawford wrote:

    > SwimmerEvent or somesuch)

    >
    Just saw Greg's reply -- Entry is a much more sensible name...

    mmalc
  • I understand the solution given by mmalc a few weeks ago to my thread
    "Managed Object wants a per-relationship attribute" (Thanks!) and
    have been working with it.  I'm using the recommended data model for
    this "swimming meet" demo I'm working on:

      Event
      -----        Entry
      name        -----          Swimmer
      entries <->> event          -------
                    orderInE      name
                    swimmer  <<->  entries
                    orderInS

    The attributes orderInE (order in event) and orderInS (order in
    swimmer) are positive integers, and should always be contiguous
    within any event and within any swimmer.  For example, if there are 4
    swimmers in event, the orderInE of their respective entries should be
    1, 2, 3 and 4.

    I have found it quite involved to maintain this contiguity, and other
    "cascading" business logic required to update related objects after
    changes.  in this little demo.  For example, if an Entry is deleted
    or inserted, or is moved (orderInE or orderInS changed), both their
    'event' and their 'swimmer' must go through and reorder their
    remaining entries, and all the views (and I have several) must be
    updated.  Another example: If a swimmer or event is deleted, all of
    their entries must be deleted.  And if entry deletion causes an event
    to have no swimmers or a swimmer no events, I might like to delete
    them too.  Newly-inserted objects should appear at the bottom of
    their lists, i.e. have an index one higher than the last entry.
    Besides the cascading business logic, there are validation
    requirements also; for example, swimmer may not repeatly enter the
    same event.

    After trying many approaches and force-quitting out of many infinite
    loops, I've found myself solving the most difficult business logic
    problems by implementing a selector for
    NSManagedObjectContextObjectsDidChangeNotification.  This
    notification has become my "goto" for all the hard stuff.

    It makes sense to me, since there are many forces which can change
    the model, that this notification is the logical place to do the
    "cascading clean up" required in related objects.  But since I've not
    seen this used in any Examples, I thought I would check in and ask if
    anyone else would do it this way, or if there is a more conventional
    approach?

    Jerry Krinock

    In case anyone wants a feel for what it looks like, the following
    code is the selector I run for
    NSManagedObjectContextObjectsDidChangeNotification.  I have written
    it using generic class, entity and key names so that it can be re-
    used in other projects.  To translate from the swimming meet demo,

        Event is MO1 (managed object #1)
        Entry is Glue (the "glue" object that joins MO1 and MO2)
        Swimmer is MO2 (managed object #2)

    - (void)modelChanged:(NSNotification*)notification {
        NSArray* insertedObjects = [[notification userInfo]
    objectForKey:NSInsertedObjectsKey] ;
        NSArray* deletedObjects = [[notification userInfo]
    objectForKey:NSDeletedObjectsKey] ;
        NSArray* updatedObjects = [[notification userInfo]
    objectForKey:NSUpdatedObjectsKey] ;

        NSManagedObject* object ;
        NSEnumerator* e ;
        Class glueClass = NSClassFromString(kGlueClassAndEntityName) ;

        // DIFFICULT PROBLEM #1
        // If mo1 or mo2 instances were deleted, their glues will be
    'updated'
        // and these updated glues will have no mo1 or no mo2.
        // We find those objects and delete them
        // Before doing so, we also check to make sure there was at
    least one
        // deleted object, which tells us that this happened as the
    result a
        // a deletion and is not a temporary situation due to an object
        // under construction.
        if ([deletedObjects count]) {
            e = [updatedObjects objectEnumerator] ;
            while ((object = [e nextObject])) {
                if ([object isKindOfClass:[glueClass class]]) {
                    if (![object valueForKey:[MO2 keyToMe]] || ![object
    valueForKey:[MO1 keyToMe]]) {
                        NSLog(@"DebugLog: 13672 Deleting Glue due to no
    mo1 or mo2: %@", object) ;
                        [[self managedObjectContext] deleteObject:object] ;
                    }
                }
            }
        }

        // DIFFICULT PROBLEM #2
        // If glues were added or removed, their mo1 and mo2 will need
    their orders rebuilt.
        // As it turns out, the deletedObjects and insertObjects, which
    are Glue class, arrive
        // here with their mo1 and mo2 already set to nil.  However, the
    two affected mo1 and mo2
        // objects (i.e., the -mo1 and -mo2 of the inserted or deleted
    Glue), will arrive
        // simultaneously in the updatedObjects.  So, we collect all the
    updatedObjects as affected.
        NSManagedObject* innerObject ;
        NSMutableSet* affectedMO1Objects = [[NSMutableSet alloc] init] ;
        NSMutableSet* affectedMO2Objects = [[NSMutableSet alloc] init] ;
        e = [updatedObjects objectEnumerator] ;
        while ((object = [e nextObject])) {
            if ([object isKindOfClass:[MO1 class]]) {
                [affectedMO1Objects addObject:object] ;
            }
            else if ([object isKindOfClass:[MO2 class]]) {
                [affectedMO2Objects addObject:object] ;
            }
        }

        // DIFFICULT PROBLEM #3
        // When glue are added to empty mo1 or mo2, they will still have
    their initial order
        // setting of 2^31-1 (or more in a 64-bit compile?), and since
    there are no
        // related mo1 or mo2 objects to trigger rebuilding the order,
    we have to explicity
        // detect those situations and do it.
        e = [insertedObjects objectEnumerator] ;
        while ((object = [e nextObject])) {
            if ([object isKindOfClass:[glueClass class]]) {
                if ([[object valueForKey:[MO1 gluesOrderKey]] intValue]
    > =  2147483647) {
                    [affectedMO1Objects addObject:[object valueForKey:
    [MO1 keyToMe]]] ;
                }
                if ([[object valueForKey:[MO2 gluesOrderKey]] intValue]
    > =  2147483647) {
                    [affectedMO2Objects addObject:[object valueForKey:
    [MO2 keyToMe]]] ;
                }
            }
        }

        // Rebuild orders of affected objects
        e = [affectedMO1Objects objectEnumerator] ;
        while ((innerObject = [e nextObject])) {
            [innerObject
    rebuildOrderOfObjectsGlueEntityName:kGlueClassAndEntityName
                                                      keyToMe:[MO1 keyToMe]
                                                    orderKey:[MO1
    gluesOrderKey]] ;
        }

        e = [affectedMO2Objects objectEnumerator] ;
        while ((innerObject = [e nextObject])) {
            [innerObject
    rebuildOrderOfObjectsGlueEntityName:kGlueClassAndEntityName
                                                      keyToMe:[MO2 keyToMe]
                                                    orderKey:[MO2
    gluesOrderKey]] ;
        }

        [affectedMO1Objects release] ;
        [affectedMO2Objects release] ;
    }
previous month september 2007 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Go to today