0

I need to create a map between a custom object and a List, my code right now is this:

Map<Invoice_Header__c, Contact> headersToContact = new Map<Invoice_Header__c, Contact>();

List<Contact> financeContacts = [SELECT Id, FirstName, Email, AccountId FROM Contact WHERE Id in :financeContactsMap.values()];

List<Invoice_Header__c> headersWithAccountsInvoice = [SELECT Id, Account__c, AmountInclVAT_f__c, Account__r.Name, Name, Status__c FROM Invoice_Header__c WHERE Id in : headers AND Account__c in :accountsInvoice];

for(Invoice_Header__c h : headersWithAccountsInvoice){

for(Contact c : financeContacts ){

headersToContact.put(h, c);

}

I want to change this because the key it's only returning one value and I need to return every value from the list financeContacts.

  • 3
    Hi Nuno, welcome to SFSE. Please take some time to scroll through the tour and read How to Ask. Right now, it is very unclear what you are trying to achieve or where you are stuck, so your question will likely receive no useful answers. Please edit your post to add a more detailed explanation of what you want your code to do. Looking at the code you have does not give a very clear picture on its own. – Adrian Larson Oct 11 '18 at 15:14
6

The value of a map can be just about anything, including another collection.

It's usually1 not recommended to use an SObject as the key to a map, especially if you aren't intimately familiar with how Maps work. If you make any change to an SObject reference used as a key, the value stored in your map will no longer be able to be accessed2

What you're looking for here is to use a Map<Id, List<Contact>>. The key of the Map would be the Id (and only the Id) of the Invoice_Header__c record that you want to associate with some Contacts. Alternatively, if you want to associate a given Contact to more than one Invoice_Header__c, the key of the map could be the Id of the Account record that both your Invoice_Header__c and Contact record point to (in effect, treating the Account as a junction object of sorts).

Your provided code seems incomplete, but an example of this approach using Account and Opportunity

// This is a contrived example.
// You could achieve a very similar result with a single query

// Define and instantiate the map, as you would with any other variable
Map<Id, List<Opportunity>> accountIdToOppsMap = new Map<Id, List<Opportunity>>();

for(Opportunity opp : [SELECT Id, Name, AccountId FROM Opportunity]){
    // This is my preferred way to populate a map.
    // Check to see if the key exists and, if not, put the key into the map
    // This keeps the amount of work needed to be done inside the "if" minimal, and
    //   allows us to use one line of code outside of the "if" to continue building the map
    //   (as opposed to largely duplicating effort in the "if" block in a corresponding
    //   "else" block)
    if(!accountIdToOppsMap.containsKey(opp.AccountId)){
        // Putting a new instance of List into the map when we first encounter the map
        //   key (and we only enter this block when the key does not previously
        //   exist in the map) allows us to avoid null checks outside of this block 
        accountIdToOppsMap.put(opp.AccountId, new List<Opportunity>());
    }

    // Statement evaluation is done left-to-right
    // accountIdToOppsMap.get(<id>) returns a list
    // This list provides the add() method, and we can chain these method calls.
    // Because collections as the map value are stored as references, adding to the list
    //   automatically adds the value to the list stored in the map.
    // That means there's no need to use a temporary variable or put() the result
    //   back into the map.
    accountIdToOppsMap.get(opp.AccountId).add(opp);

    // "Primitive" types like Integer do not get stored as references, so if you
    //  were working with a Map<Id, Integer>, you'd need to do something like this instead
    // idToIntMap.put(someId, idToIntMap.get(someId) + 1);

}

You'll need to modify this approach to suit your specific need, but in the end it should look fairly similar to my example.

One more piece of advice that might help you:
If you see if(outerLoopVariable.field == innerLoopVariable.field), that's an indication that you should instead create a map where "field" is the key. In that vein of thought, first having a Map<Id, List<Contact>> where you store a list of contacts based on the Account's Id will likely be helpful to you.

1: One case for using SObjects as the key to a map is using the SObject as a "composite key", as sfdcfox notes. Useful if a single identifier is used for more than one record, but you know that the combination of two (or more) identifiers would be unique. Helps to avoid unnecessary wrapper classes.

2: In reality, things are more complicated than this. Sets and the keys of Maps store hashes. When you make a change to an object, the hash also changes. Setting a new field that was not set, not even null, at the time that the object was made into the map key will cause the map key to be irrevocably lost (even if you clone the object before making the change) because SObjects in collections are stored as references. You could still retrieve the values stored in the map by using the values() method, but at that point you only have a list.

| improve this answer | |
  • 2
    "It's not recommended to use an SObject as the key to a map" I would qualify that statement--I certainly do use SObject as a key for a map fairly regularly, but it's always used as a compound key with a specific purpose, like matching by name and email, for example. I certainly wouldn't advise using the map without understanding why it works the way it does, but when it makes sense to use it, I also wouldn't not recommend it. – sfdcfox Oct 11 '18 at 16:18
  • @sfdcfox Just because you can get away with it and know when it's appropriate, does not mean that 99.99% of developers should consider the same. The average developer, a good developer, and even many of the best developers out there should simply not consider it a stable approach. It takes careful consideration and a fair amount of platform knowledge to do it right. – Adrian Larson Oct 11 '18 at 17:06
  • 1
    @AdrianLarson It's a powerful tool that should be used when appropriate. If you need to nail a board down, I'm not going to recommend using a screwdriver. At some point during a typical developer career, they're going to be given an opportunity to use that tool, and the alternative is going to be no less than a 12-line wrapper class that will be less efficient and just as dangerous to use (perhaps more so, because of the risk for performance problems with a faulty hashCode). – sfdcfox Oct 11 '18 at 17:19
  • @sfdcfox -- once again, I learned a new trick - using an Sobject as a compound key -- I had been using concatenated strings before for compound keys. – cropredy Oct 11 '18 at 18:48

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.