As you've found out, you can access cross-object fields in workflow rules, but cross-object fields cannot trigger workflow.
Intuitively, this makes perfect sense. Workflow rules for Object X are triggered when there is a change on Object X. You're updating a field on User
, not Contact
. Therefore, workflow rules are not evaluated on Contact
because you aren't changing a Contact
, you're changing a User
.
If you don't need the Contacts
owned by a User
to be updated immediately, then you might try creating a Schedulable class, and having it run on a schedule The documentation on the Apex Scheduler will help with that.
If you do need Contacts
to be updated immediately, and you're absolutely certain that updating Contacts
from a User
trigger causes the mixed DML error, then you really don't have much choice besides going async.
You don't need to use an @future
method. Implementing the Queueable interface (the documentation on Queueable Apex will be helpful there) is one way to work around that issue.
Another way to make it work would be to make your @future
calls smarter. We can detect if we're in an async context or not. The key here is to not directly call your @future
method. If you're already in an async context, just call a non-future version of your code. The general pattern for that looks like this...
public class MySmarterFuture{
// A custom exception class isn't strictly required, but is good practice
public class MySmarterFutureException extends Exception{}
public static void run(List<Id> sobjIds){
try{
if(system.isFuture() || system.isBatch() || system.isScheduled()){
system.debug('running async, calling normal run method');
doNormal(sobjIds);
} else {
if (Limits.getFutureCalls() < Limits.getLimitFutureCalls()){
system.debug('running future run method');
doFuture(sobjIds);
} else {
// Error out
throw new MySmarterFutureException('Hit the single transaction @future call limit');
}
}
} catch(Exception e){
system.debug('Caught Exception');
// Good idea to do some explicit notification here, otherwise the
// issues encountered in async contexts are generally silent
}
}
@future
public static void doFuture(List<Id> sobjIds){
// No need to duplicate your code, just call the doNormal method!
// The fact that we're in this block of code at all means that we've
// already taken care of getting into an async context.
doNormal(sobjIds);
}
private static doNormal(List<Id> sobjIds){
// The work that you would normally put in your future method goes in this
// non-future method instead
}
}