Tuesday, March 16, 2010

Custom Settings and MIXED_DML_OPERATION

Some folks have noticed an issue when using the new Custom Settings feature in Apex. When you have a piece of code that does DML on both a custom setting and a regular object, you get:

"MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa)"

What is Mixed DML? Well, certain types of DML simply do not mix well. For instance, if you change a Profile and also an Account (in the same transaction), you might be altering the profile in such a way that it no longer has access to that Account. So, there's a certain set of combinations that will yield a MIXED_DML_OPERATION exception.

In general, the way to get around this is to do your second bit of DML in a @Future method, as that will happen in its own transaction (though it's important to note that, in a test method, @Future method invocations are inlined, so you will still get the MIXED_DML_OPERATION exception).

However, starting in version 18.0, Custom Settings are no longer placed in this special bucket. You can now do DML on Custom Setting objects and other (non-setup) objects in the same transaction.

Just be sure your code is set to version 18.0!

Thursday, March 11, 2010

Apex Describes and class versions

I'm resurrecting this blog for posts related to Apex, as using my java.net blog for that purpose would be inappropriate. I'm not sure how often I'll post. When things of general interest crop up while I'm working on the Apex compiler, I suppose.

Today's topic came up as part of a customer support escalation. It's a tricky gotcha with the Apex getMap() function, used for getting all the fields for an sObject type (kind of like reflection for your database objects).

For example, you can get a map of all the fields in the User object by writing:

Map userFields = SObjectType.User.fields.getMap();

With this map, you can see which fields the running user has access to:

boolean b = userFields.get('accountid').getDescribe().isAccessible();

There's a subtle gotcha with using describes, and a fairly simple rule of thumb:

Don't pass the map around between classes.

In my example, I'm getting the AccountId field. This field was added in version 17. Let's say you have two classes, QueryBuilder and DescribeUtil.

// version 16
public class QueryBuilder {
  public String buildQuery {
    Map userFields = DescribeUtil.getUserFields();
    String fieldlist = '';
    for (String key : userFields.keySet()) {
      if (userFields.get(key).getDescribe().isAccessible())
        fieldlist = fieldlist + ',' + key;
    }
  }
}

// version 17
public class DescribeUtil {
  public static Map getUseFields() {
    return SObjectType.User.fields.getMap();
  }
}

You will get an error:

Field User.AccountId is inaccessible in this context

Why? Because QueryBuilder is running in a world where the AccountId field doesn't exist yet! If the getMap() call were done in QueryBuilder, it wouldn't be in there.

So, do yourselves a favor and don't pass these maps around from class to class. The version mismatch is an easy thing to miss.