2

I made a Trigger for an Object1 that gets Id from Object2. Rather, Object1 entries already have Object2 IDs, but now I need to make a lookup based on it. The Object2 can be any object you want: system or custom. So, how can I write the universal SOQL for my Map? Now I have this one:

Map<Id, Object> tMap = new Map<Id, Object>([SELECT Id FROM Object]);

My errors are: Invalid type: Schema.Object and Variable does not exist: Id. All I need to do this:

trigger ToolsObjTrigger on Tool__c (before insert) {
    for(Tool__c t : Trigger.new){
        if(!String.isEmpty(t.textId__c)){
            Id neededId = Id.valueOf(t.textId__c);
            String objectAPIName  = neededId.getSobjectType().getDescribe().name;
            Map<Id, SObject> objectsMap = new Map<Id, SObject>(Database.query('SELECT Id FROM ' + objectAPIName ));      
            if(objectsMap.get(t.textId__c).Id != null && objectAPIName == 'Obj__c'){
                t.Obj__c = objectsMap.get(t.textId__c).Id;
            }  
        }    
    }
}
  • Are you saying that you've created a sort of synthetic polymorphic lookup? That is, a text field on Tool_c that holds the Id of some other record, of any type? – David Reed Dec 18 '19 at 16:27
  • Yes. There can be any object Id. It's a text field only. – Apelsinova Dec 18 '19 at 16:32
  • And what is the field Obj__c? Is that a lookup relationship? – David Reed Dec 18 '19 at 16:37
  • Yes, it's our lookup. – Apelsinova Dec 18 '19 at 16:38
  • A lookup to what object? As far as I know, you still cannot create your own polymorphic lookups. – David Reed Dec 18 '19 at 16:40
6

It is possible to get the object's type from the ID then use this in the dynamic SOQL. For example:

Id id = Id.valueOf(tool.cId__c);

String objectAPIName = id.getSobjectType().getDescribe().name;

List<SObject> matches = Database.query('SELECT Id FROM ' + objectAPIName + ' WHERE Id = :id');

Bulkification is possible, but worst case you need n separate queries for n separate IDs where the types are all different. The idea would be to build a map of objectAPIName to list of IDs, like:

Map<Id, List<Id>> idsByObjectAPIName = new Map<Id, List<Id>>();

for (Tool__c tool : Trigger.new) {
    Id id = Id.valueOf(tool.cId__c);
    String objectAPIName = id.getSobjectType().getDescribe().name;

    List<Id> idsForId = idsByObjectAPIName.get(objectAPIName);

    if (idsForId == null) {
        idsByObjectAPIName.put(objectAPIName, new List<Id> { id });
    else {
        idsForId.add(id);
    }
}

At this point you can then iterate the map by key set to perform the various queries, though if there are too many object types you will get a too many queries error:

for (String objectAPIName : idsByObjectAPIName.keySet()) {
    List<Id> ids = idsByObjectAPIName.get(objectAPIName);

    // Query the objects, with whatever fields you need for this object type
    List<SObject> theObjects = Database.query('SELECT Id FROM ' + objectAPIName + ' WHERE Id IN :ids');

    // Do what you need
}

If your trigger is being called from a batch, and you don't have other types of bulk change, you can control the maximum possible number of different object types, by controlling the number of Tool__c instances that are processed together. This is done using the scope size parameter on Database.executeBatch. If you know you can safely handle 50 (say) types of object, just set the scope size to 50. After all, the most cId__c values you can have is one per Tool__c instance, and the most different types you can then have is one per cId__c value.

| improve this answer | |
  • 1
    This is correct. But it would be very hard to bulkify. Imagine the trigger being called with 150 records, each pointing towards a different object. You would end up executing 150 unique queries. – Aayush K Dec 18 '19 at 17:42
  • Thank you for help. It works! I updated my code. Can you tell me please how to remove the Map with SOQL from the loop in this case? – Apelsinova Dec 18 '19 at 17:50
  • See updated answer. – Phil W Dec 18 '19 at 18:27
6

You can make use of dynamic SOQL:

String type = 'Abc__c';
Map<Id, SObject> m = new Map<Id, SObject>(Database.query('SELECT Id FROM ' + type));

Also see the SObject documentation for methods to access the data.

By taking these approaches, the types involved only matter at runtime, not at compile time. But you naturally do lose compile-time type checking.

| improve this answer | |
  • Thank you. But I'm really not sure how to set the type. In the field can be any Object Id. So we have to search it by Id in all database. – Apelsinova Dec 18 '19 at 16:34
  • 2
    See the getSobjectType() method available on Id in Phil W's answer; very handly for this context. But if you have many Id values you will need to work out the set of types and do a query per type. – Keith C Dec 18 '19 at 18:07

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.