7

Normally, to select things we use SOQL code like:

[ 
    SELECT Id, Blah1__c, Blah2__c, Blah3__c
    FROM Yada_Yada_Yada__c
    WHERE Id IN :idSet
];

When we do this, the compiler (and our IDEs) will tell us if the variables, fields, or sobjects are somehow incorrect.

For a Batchable implementation, we would like to use a Database.QueryLocator.

However, the corresponding expression to get the Database.QueryLocator looks like:

Database.getQueryLocator(
    'SELECT Id, Blah1__c, Blah2__c, Blah3__c '
    + ' FROM Yada_Yada_Yada__c '
    + ' WHERE Id IN :idSet '
);

Now, nothing is typesafe. My compiler won't alert me if I've named anything wrong. My IDE won't help me if I rename "idSet" later. SFDC won't stop anyone from renaming or deleting the SObject or SObject fields.

Obviously I could use the SOQL query to get a list and then create an iterable, but this would bring back the undesirable governor limit.

Is there any typesafe way to get a Database.QueryLocator?

7

Provide hardcoded fields in query locator? I believe you can do this, this gives a compile-time check

Database.getQueryLocator([SELECT Id FROM Account]);

Src:https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_database_batch.htm#apex_Database_QueryLocator_getQuery

5

Partially. You can always establish static references to the involved fields and sObjects, and build the query dynamically with templating. I never like seeing queries built by string concatenation anyway.

List<String> fieldList = new List<String>{
    String.valueOf(Account.Id),
    String.valueOf(Account.Name)
};

String query = String.format(
    'SELECT {0} FROM {1}',
    new List<String> {
        String.join(fieldList, ', '),
        String.valueOf(Account.sobjectType)
    }
);
System.debug(query);
Database.query(query);

11:31:26:004 USER_DEBUG [13]|DEBUG|SELECT Id, Name FROM Account

That at least gets the system's metadata dependency checking into the mix. Gets a lot messier if you want to traverse relationships, of course.

You're not going to be able to validate name references in Apex bindings, though, because you cannot introspect the local namespace in Apex. At most, you could factor it into a builder method, but the vulnerability to changing, say, parameter names to a builder method is still there.

2

@PranayJaiswal has the best answer but you can also consider the Force.com Enterprise Patterns - Selector layer that uses a queryFactory and type safe fields as in:

Database.QueryLocator ql = CasesSelector.newInstance().selectByCloseDateAsQueryLocator(someDateFilter);


public virtual class CasesSelector extends fflib_SObjectSelector implements ICasesSelector {
  public List<Schema.SObjectField> getSobjectFieldList() {
    return new List<Schema.SObjectField> {
      Case.Id,
      Case.CloseDate,
      Case.Subject,
      ...
    };
  }
 public virtual Database.QueryLocator selectByCloseDateAsQueryLocator(String dateFilter) {
  fflib_QueryFactory qF = new QueryFactory(false) // false=enumerate fields you want 
                           .selectFields(new Set<SObjectField> {
                              Case.Id, Case.CloseDate, ...}  
                           .setCondition('CloseDate = :dateFilter');
  return Database.getQueryLocator(qf.toSOQL());

}

public virtual Database.QueryLocator selectByCloseDateAsQueryLocator(String dateFilter) {
  fflib_QueryFactory qF = new QueryFactory(true) // use fields defined by class method getSobjectFieldList  
                           .setCondition('CloseDate = :dateFilter');
  return Database.getQueryLocator(qf.toSOQL());

}

That said, fflib Selector pattern doesn't support setCondition expressions using Schema.SObjectField (the arg to setCondition is a string) but you could do:

.setCondition(Case.CloseDate.getDescribe.getName() + ' = :dateFilter)

if you were super worried about typesafe here.

The nice thing about the pattern is that the queryfactory allows composing queries from multiple factories (so you can build lookups and children) easily, you get all the describe bits for free, and my favorite, the selectors are easily mockable (ApexMocks or other DI approaches) making unit tests super fast.

I'm not showing the whole shebang as that is a larger topic - see Trailhead and Andrew Fawcett's blog/book

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.