6

I was trying to build a utility wherein I could pass in a literal field token for a relationship field and discovered an inconsistency

This works (going from Contacts -> Account)

Schema.SObjectField fToken;
fToken = Contact.Account.Name;
fToken = Contact.Account.OwnerId;
fToken = Contact.Account.CreatedDate;
fToken = Contact.Account.CurrencyIsoCode;
fToken = Contact.Account.Owner.Name;  // 3 levels !!

fToken = Account.Contacts.Name;  // you can go down from parent to child !
fToken = Account.Contacts.Owner.Name;  // down and then up !!

This does not work ...using anything other than fields present in almost any object

Variable does not exist: Contact.Account.Website

fToken = Contact.Account.Website;
fToken = Contact.Account.Type;

fToken = Account.Contacts.Email;  // going down to a non-omnipresent field

I understand that the doc doesn't say you can do this but in certain limited use cases, it works. I also realize there are plenty of other ways to find the field token for relationship fields.

It is curious that the Apex compiler can handle relationship field expressions for any field on an object,

someContactObj.account.website = 'foo.com';

but is restricted when using Schema.sobjectFields. Am I missing something?

1 Answer 1

3

Am I missing something?

Yes. There's no data type for Account.Contacts (try Schema.SObjectType.Account.Contacts, you'll see what I mean), and so it seems to be triggering some odd behavior in the compiler; it essentially replaces the original DescribeSObjectResult object in place for the relationship, allowing you to select a field from the original object.

It seems that even though you go "through" a relationship, you haven't left the object, even though they're aliased differently:

System.assertEquals(Account.Name, Account.Contacts.Name);

In fact, it behaves as if the intermediate relationship isn't there at all:

Account a = new Account();
a.put(Account.Contacts.Industry, 'Technology');
System.assertEquals(a.Industry, 'Technology');

Going even further, you can even repeatedly abuse it:

Account a = new Account();
a.put(Account.Contacts.Contacts.Contacts.Contacts.Industry, 'Technology');
System.assertEquals(a.Industry, 'Technology');

I remember playing with this feature years ago trying to build the same utility you are today, and I ultimately had to deal with using plain strings and string lists to navigate through relationships, combined with liberal usage of getSObject, getSObjects, and SObject.get.

You might try filing a bug for this if you want to, but you should definitely avoid trying to use it to reference your child relationships, because it's not a "real" data type.

Also, I should note that the other examples, such as Contact.Account.Name, also coincidentally fall under this same rule. Even though you reference Contact.Account.Owner.Name, you're really just referencing... Contact.Name. You really need to just use plain strings, split them apart, and navigate those relationships through dynamic code.

2
  • As you suggested, I had already moved to the string approach 'Contact.Account.Website` once I realized the limitation and getSObject(), getSObjects() and sobject.get() as you suggested -- never occurred to me that I hadn't moved 'off' the object but now that I think of it, since the field describe for name, currencyIsoCode, etc. is always the same regardless of sobject nothing would be gained by even exploiting the limited compiler support we seem to have.
    – cropredy
    Commented Apr 19, 2016 at 23:07
  • @cropredy I could see how it would be useful if we could codify relationships that way. But it's obviously not meant to be, since it just falls of the edge when we try.
    – sfdcfox
    Commented Apr 19, 2016 at 23:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .