1
Vote

Addlink asks for sourceProperty but really wants target entity name

description

in ObjectContext.m I find @ line 269:
 
/**
  • To create an m_association between two entity instances
  • @param sourceObject The source object participating the m_association.
  • @param targetObject The target object participating the m_association.
  • @param entityName The class name of target object.
  • This method only supports adding links to relationships with
  • multiplicity = * (The source property is Collection).
    */
  • (void) addLink:(ODataObject*) aSourceObject sourceProperty:(NSString*) aSourceProperty targetObject:(ODataObject*) aTargetObject
    {
     
     
     
    So notice first that the method asks for sourceProperty but in the documentation above says it wants an entityName, which I've discovered is what it actually looks for. In:
    -(void) checkRelationForObject:(ODataObject*)sourceObject property:(NSString )sourceProperty method:(NSString )method
     
    it compares here: if([[[array objectAtIndex:j] objectForKey:@"Type"] isEqualToString:sourceProperty])
    but Type is always the name of the entity, not the source's property name.
     
     
    What this all boils down to for me is that everything is fine if you have an Order with OrderItems that are of type OrderItem, but if you have an Order with someOrderItems of type OrderItem you have to list the "sourceProperty" as "OrderItem" to pass the check but when it tries to post the link to OrderItems(guid'someguid')/$links/OrderItems (because in SaveResult.m -(void)processObjectLinks:(RelatedEnd *)aRelatedEnd it will append $links/ [Utility getEntityNameFromUrl:uri] which is NOT the source property name) it will return a 404 because that link doesn't exist.

comments

ljosaitis wrote Feb 10, 2011 at 12:57 AM

I further discovered that this poses an impossibility when you create an entity with a relationship back to itself. For instance, if you have an OrderModification with a property called ChildOrderModifications that relates to more OrderModifications that may have 0..1 ParentOrderModification you have a number of problems. Firstly, it will not get passed the relationship checker because it gets confused about which direction you're trying to go. Current Code:

<code>

-(void) checkRelationForObject:(ODataObject*)sourceObject property:(NSString )sourceProperty method:(NSString )method
{
int i, count,j;
id key;

NSArray *keys = [[sourceObject getEntityFKRelation] allKeys];
count = [keys count];
BOOL isfound=NO;
for (i = 0; i < count; i++)
{
    key = [keys objectAtIndex: i];
    NSString *fkey=[[sourceObject getEntityFKRelation] objectForKey:key];
    NSArray *array=[m_association objectForKey:fkey];

    for(j=0;j<[array count];j++)
    {
        if([[[array objectAtIndex:j] objectForKey:@"Type"] isEqualToString:sourceProperty])
        {
            isfound=YES;
            NSString *Multiplicity=[[array objectAtIndex:j]objectForKey:@"Multiplicity"];

            if(!(  ([method isEqualToString:@"setLink"] && [Multiplicity isEqualToString:@"0..1"]) ||
                 ([method isEqualToString:@"setLink"] && [Multiplicity isEqualToString:@"1"]) ||
                 ([method isEqualToString:@"addLink"] && [Multiplicity isEqualToString:@"*"]) ||
                 ([method isEqualToString:@"deleteLink"])   ))
            {
                //Invalid Operation
                @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_InCorrectLinking userInfo:nil];//incorrect linking
            }
            break;                          
        }
    }
}
if(!isfound)
{
    //Invalid Operation
    @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_AddLinkCollectionOnly userInfo:nil];//not associated entities
}
}

</code>

This code doesn't take into account the actual sourceProperty at all, as noted it actually expects the target entity's name. In my example, that term is the same for source and target so it will find the first depending on order and possibly fail. My new unvetted version goes something like

<code>
-(void) checkRelationForObject:(ODataObject*)sourceObject property:(NSString )sourceProperty method:(NSString )method
{
int i = 0;
BOOL isfound=NO;
NSDictionary *roleDescription;

//look at the entity map which relates to NavigationProperty name
NSMutableDictionary *entityMap = [sourceObject getEntityMap];
//get the key for the requested source property
NSString *relationshipKey = [entityMap objectForKey:sourceProperty];

if (relationshipKey == nil) {
    @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_AddLinkCollectionOnly userInfo:nil];//not associated entities
}

//look at all the relations
NSMutableDictionary *relations = [sourceObject getEntityFKRelation];
//find the table for our source property's key
NSString *relationshipTableName = [relations objectForKey:relationshipKey];

//look thru the role descriptions
NSArray *roleDescriptionArray = [m_association objectForKey:relationshipTableName];
for (i = 0; i < [roleDescriptionArray count]; i++) {
    roleDescription = [roleDescriptionArray objectAtIndex:i];

    //compare EndRole to the relationship key, this will ensure the correct direction
    if ([[roleDescription objectForKey:@"EndRole"] isEqualToString:relationshipKey]) {

        isfound=YES;            
        NSString *Multiplicity = [roleDescription objectForKey:@"Multiplicity"];

        //do the check
        if(!(  ([method isEqualToString:@"setLink"] && [Multiplicity isEqualToString:@"0..1"]) ||
             ([method isEqualToString:@"setLink"] && [Multiplicity isEqualToString:@"1"]) ||
             ([method isEqualToString:@"addLink"] && [Multiplicity isEqualToString:@"*"]) ||
             ([method isEqualToString:@"deleteLink"])   ))
        {
            //Invalid Operation
            @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_InCorrectLinking userInfo:nil];//incorrect linking
        }
        break;              
    }
}   

if(!isfound)
{
    //Invalid Operation
    @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_AddLinkCollectionOnly userInfo:nil];//not associated entities
}   
}
</code>

so when i call "addLink" if I set the sourceProperty to "ChildOrderModifications" it will now pass this check.



Now, when it goes to actually build the links, for batch saves I had to change in SaveResult.m
  • (NSMutableString*) createChangesetHeaderForBinding:(RelatedEnd*)aRelatedEnd
the linee
//uri = [Utility getEntityNameFromUrl:uri]; to
uri = [aRelatedEnd getSourceProperty];

Which in our example makes sure we post to /$links/ChildOrderModifications rather than /$links/OrderModifications which doesn't exist

and for non batch in
-(void)processObjectLinks:(RelatedEnd *)aRelatedEnd

I changed the line
//resourceUri = [NSString stringWithFormat:@"%@/$links/%@", [aResourceBoxSource getResourceUri:[m_context getBaseUriWithSlash]],[Utility getEntityNameFromUrl:uri]]; to
        resourceUri = [NSString stringWithFormat:@"%@/$links/%@", [aResourceBoxSource getResourceUri:[m_context getBaseUriWithSlash]],[aRelatedEnd getSourceProperty]];


Again all of this just works for my current example and may not be "right," but I don't know any other way to have navigation properties named differently than the pluralization of the target entity's name.

CTapang wrote Feb 18, 2011 at 4:25 PM

Thanks for your thoughts on this. I am hoping we can have time to consider this very carefully and incorporate whatever we decide in Version 1.2, but alas we are about to release Version 1.2.

CTapang wrote Mar 11, 2011 at 11:34 PM

This is the same problem as reported here; http://odataobjc.codeplex.com/discussions/224667. We've fixed it in Version 1.2 as follows:


-(void) checkRelationForObject:(ODataObject*)sourceObject property:(NSString )sourceProperty method:(NSString )method
{
//int i, count,j;
//id key;
BOOL isfound=NO;

NSString *toRole=[[sourceObject getEntityMap] objectForKey:sourceProperty];NSLog(@"to role= %@",toRole);
if(toRole == nil)
        @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_RelationNotRefOrCollection userInfo:nil];

NSString *relationship=[[sourceObject getEntityFKRelation] objectForKey:toRole];NSLog(@"relationship= %@",relationship);
if(relationship == nil)
    @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_RelationNotRefOrCollection userInfo:nil];

NSArray *arrOfAssociation=[m_association objectForKey:relationship];NSLog(@"association= %@",[arrOfAssociation description]);
if(arrOfAssociation == nil)
    @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_RelationNotRefOrCollection userInfo:nil];

int count=[arrOfAssociation count];
for(int z = 0; z < count; z++)
{
    if([[[arrOfAssociation objectAtIndex:z] objectForKey:@"EndRole"] isEqualToString:toRole])
    {
        isfound=YES;
        NSString *multiplicity=[[arrOfAssociation objectAtIndex:z] objectForKey:@"Multiplicity"];
        if(!(  ([method isEqualToString:@"setLink"] && [multiplicity isEqualToString:@"0..1"]) ||
             ([method isEqualToString:@"setLink"] && [multiplicity isEqualToString:@"1"]) ||
             ([method isEqualToString:@"addLink"] && [multiplicity isEqualToString:@"*"]) ||
             ([method isEqualToString:@"deleteLink"])   ))
        {
            //Invalid Operation
            @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_InCorrectLinking userInfo:nil];//incorrect linking
        }
        break;    
    }
}

if(!isfound)
{
    //Invalid Operation
    @throw [NSException exceptionWithName:@"Invalid Operation" reason:Resource_RelationNotRefOrCollection userInfo:nil];//not associated entities
}
}