A generic pattern for the sort of logic I think you are aiming to create is this:
for (CampaignMember cm : [
SELECT Id, CampaignId, AccountID_Formula__c
FROM CampaignMember
WHERE AccountID_Formula__c = :accIds
ORDER BY Name
]){
List<CampaignMember> l = accountCampaignMap.get(cm.AccountID_Formula__c);
if (l == null) {
l = new List<CampaignMember>();
accountCampaignMap.put(cm.AccountID_Formula__c, l);
}
l.add(cm);
}
This will result in each list being in the order of the CampaignMember.Name.
salesforce - Apex - Retrieving Records from a type of Map - Stack Overflow
Cast a Map <Id, sObject> to Map <Id, Opportunity>
Cast map<id, sObject> to map<id, opportunity>
salesforce - Map of Sobject in SOQL query - Stack Overflow
The .values() method will return a list of sObjects, which means that you can simply follow the same scenario and cast the list to a concrete sObject type:
for (Opportunity opp : (List <Opportunity>)newItems.values())
As long as all the values are of type "Opportunity" and you know that for sure.
The alternative way is to work with generic sObjects the whole time:
for (sObject opp : newItems.values())
Read more about dynamic apex
I know this is old, but still pulls up as the result via a google search. So I'm going to post something I have had luck with.
If you know what sObject you are converting into, I've had success doing this:
objList //we'll say this is a Map<Id, sObject> where the sObject is an Opportunity
List<Opportunity> oppList = objList.values();
Or looping:
for(Opportunity o : objList.values())
{
//operations
}
It's a little different than the previous answer given, so I hope someone finds it useful.
Edit: Apologies, realized this is for Map to Map. However I'll leave this because in some situations converting to a list from a Map will be beneficial as it should have already removed dups from being in a map, and then you can iterate through the list or perform DML on it.
Hi all,
I am working on an after update trigger for opportunities. I would like logic to only execute on opportunities after a specific field has been changed. I'm using a trigger framework that uses generic sObject maps, so my logic is initiated as:
public static void oppTrigger (Map<Id, SObject> newItems, Map<Id, SObject> oldItems) {}What I would normally do if I had two maps of opportunities is:
map<id, opportunity> updatedOpps = new map<id, opportunity>();
for (opportunity o: newItems) {
if (o.value != oldItems.get(o.id).value) {
updatedOpps.put(o.id, o);
}Any advice on how to do this with generic sObject maps?
I duplicated this (V58)
- my IDE (Illuminated Cloud) says
ParentIdis an invalid symbol but Apex compiler accepts it; - at runtime, if list size is one, returns the 0th
element.ParentId; - if list size is > 1, returns runtime error
System.QueryException: List has more than 1 row for assignment to SObject
In the code example you showed, it would only work if you knew that values() always returned exactly one row, never two or more. This is not great practice as one should be thinking about Map class method values() as always returning a list of size 0,1,2, ... These elements could be primitives, apextypes, or sobjects depending on the Map's declaration.
I'd be more explicit about it by doing:
// only care about 1st value params.put('cCaseId', caseIdToFeedItemMap.values()[0].ParentId);
What you've run into is a feature that's documented as Using SOQL Queries That Return One Record. Basically, there are some cases where you can assign a list that has a single item in it to a scalar value. Despite what the documentation says, you can use this in a variety of ways that are not specifically called out in the documentation, notably that this is a feature baked into the language itself, and not specifically to SOQL.
To understand what's going behind the scenes, there is an object that looks like a QueryResult. Apex uses this object transparently in order to facilitate the ability to behave the way it is documented. You'll see this in times when you JSON.serialize a query directly, and you'll find it has some specific behaviors. This object tends to not be null, and has a language-specific feature that allows you to access the first element if, and only if, exactly one item is in the list. It is also responsible for facilitating query cursors when there are too many results to fit in memory all at once.
As a simple example, let us write the following code:
Contact c = (Contact)JSON.deserialize(
'{"Cases":{"done": false, "totalSize": 0, "records": []}}',
Contact.class
);
Case[] cases = c.Cases;
When this code runs, we get the exception:
System.QueryException: Aggregate query has too many rows for direct assignment, use FOR loop
This happened because the underlying QueryResult object detected that the list was incomplete ("done": false).
Similarly, if we have exactly one entry, we can use the "Using SOQL Queries That Return One Record" behavior.
Contact c = (Contact)JSON.deserialize(
'{"Cases":{"done": true, "totalSize": 1, "records": [{"Subject":"Need Help"}]}}',
Contact.class
);
Case theCase = c.Cases;
System.debug(theCase.Subject); // Need Help
This code does not crash, as there is exactly one record to return. As long as the internal QueryResult meets the conditions that (1) done is true, (2) totalSize is 1, and (3) records contains exactly one record, you can use this behavior.
Interestingly, Map.values() appears to return a QueryResult object internally. This is inconsistent with the documentation for Map. However, I think this was documented this way intentionally, since we aren't necessarily meant to "know" what's going on in the backend. However, we know that this must be true, because the following code also throws the QueryException from above:
Map<Id, Case> cases = new Map<Id, Case>();
Case theCaseValues = cases.values();
If Map.values() did not return this QueryResult, then the language would not work according to its documented behavior. After all, we can't assign a normal list this way:
Case[] cases = new Case[0];
// Compiler error: Illegal assignment from List<Case> to Case
Case theCase = cases;
It is a shame that we only have the one page in the documentation that suggests this is possible. While it is not strictly "undocumented," I would say that it is poorly documented. Apex is working in the manner that it was documented in. Now that you know this feature exists, and has literally existed since at least the first public release of Apex, you can understand why it works.
I'd like to add that the Apex Language Server does not throw an error in VS Code when you write code that looks like this. The bug here is that any IDE that does not use the official Apex Language Server may be inconsistent with the actual implementation of Apex. Illuminated Cloud simply has a detail wrong about the underlying implementation, and I wouldn't blame them, as this is a rather niche construct.
Also, unless it is very explicitly proven to be a bug, which I don't think this is, this is more of a documentation omission. Salesforce R&D typically maintain that the behavior produced by the server is the correct behavior, and that the documentation is incorrect, instead.
That said, if you're going to use the behavior intentionally, you should leave a comment about how there's only ever one record returned, etc. You usually want to be more specific by selecting the first index, if that is your intent (someValues[0]).
If you open up the details of the error message, it probably says Invalid conversion from runtime type Map<Id, SObject> to Map<Id, Segment__c> Class. You can't convert Maps with a different type specialization, even if the changed types are convertible. In the particular case of converting a Map<Id, sObject> to a Map<Id, Specific_SObject__c> where the key is the Id of the sObject value, you could do this by taking advantage of the List constructor for Map:
Map<Id, Segment__c> noLockedRecordsMap = new Map<Id, Segment__c>(
(List<Segment__c>)new LockedRecordHandler().removeLockedRecords(newMap).values()
);
In all other cases, you should loop through the map and put its key-value pairs in a new map:
Map<Id, Segment__c> noLockedRecordsMap = new Map<Id, Segment__c>();
Map<Id, SObject> lockedRecordsSObject = new LockedRecordHandler().removeLockedRecords(newMap);
for(Id key : lockedRecordsSObject.keySet())
{
noLockedRecordsMap.put(key, (Segment__c)lockedRecordsSObject.get(key));
}
This could probably also be done by doing round-trip JSON, but the only advantage to that is saving lines of code, most likely it won't be faster. Hopefully in the future Apex will support methods with generic arguments so that it can return a map of the correct type and this won't be necessary.
You should not try to put a generic type in to a concrete type. This can cause bugs. The ideal situation is to create a copy so you retain the concrete type:
public Map<Id, SObject> removeLockedRecords(Map<Id, SObject> newMap) {
// Make a copy //
Map<Id, SObject> returnMap = newMap.clone();
// Clear out existing key/values in copy (not original) //
returnMap.clear();
for (SObject s : newMap.values()) {
if (!(Boolean)s.get('Locked__c')) {
returnMap.put((Id)s.get('Id'), s);
}
}
return returnMap;
}
By using clone, you preserve the original type that was passed in, and then your cast won't fail in the end.
Yes it seems alright to do it like you have, To add for the first time
mapProductEntity.put(et.Product__c, new List <Entitlement__c> { et });
Even where you're adding it above you don't need to reconstruct Entitlement__c as that is already your loop variable.
So even above you can use
if (mapProductEntity.containsKey(et.Product__c))
{
mapProductEntity.get(et.Product__c). add(et);
One can use Map.containsKey() to check and initialise a list as indicated, or alternate way could be like this [matter of taste ;)]
Map<id,List<Entity__c>> mapProductEntity = new Map<id,List<Entity__c>>();
for(Entity__c et : [SELECT name,Field1__c, Product__c, Field2__c FROM Entity__c WHERE Product__c IN : Items ORDER BY Product__c ]) {
List<Entity__c> entitiesForKey = mapProductEntity.get(et.Product__c);
if (entitiesForKey == null) {
entitiesForKey = new List<Entity__c>();
mapProductEntity.put(et.Product__c, entitiesForKey);
}
entitiesForKey.add(new Entity__c( field__c = et.field1__c,product__c = et.product__c, field2__c = et.value__c ));
}
The special map constructor only handles the case of creating a map where the key is the ID and the value is the SObject.
To do what I think you want to do requires a loop:
Map<Id, List<Custom__c>> m = new Map<Id, List<Custom__c>>();
for (Custom__c c : [
SELECT Lookup_To_Custom__c, Id, Name
FROM Custom__c
WHERE Lookup_To_Custom__c != null
ORDER BY Name
]) {
List<Custom__c> l = m.get(c.Lookup_To_Custom__c);
if (l == null) {
l = new List<Custom__c>();
m.put(c.Lookup_To_Custom__c, l);
}
l.add(c);
}
Note that using the foreign key field Lookup_To_Custom__c is a little more direct than going through the reference and taking the ID Lookup_To_Custom__r.Id.
This is certainly possible. You just need to build the map yourself instead of trying to create it form a a single query.
As an example lets say we have a custom lookup on the contact record to another contact, say 'Emergency Contact'. Its possible that a person is listed as the emergency contact for more 1 person, so we could create a map where the key is the contact id, and the value is actually a list of contacts in which they are the emergency contact for.
Obviously this is a bit different from your case, but the same idea and you should easily be able to adjust the code for your scenario.
Map<Id, List<Contact>> conMap = new Map<Id, List<Contact>>();
for(Contact parent : [Select Id, Name, (Select Id, Name From EmergencyContacts__r) From Contact]) {
conMap.put(parent.Id, new list<Contact>());
for(Contact child : parent.EmergencyContacts__r){
conMap.get(parent.Id).add(child);
}
}
system.debug('My Map: ' + conMap);
You now have a map where a contact Id is the key, and a list of contacts that person is the emergency contact for is the value.
Hope that helps
EDIT
Thinking about it more, you could likely make trim this down a bit more and just do this as well.
Map<Id, List<Contact>> conMap = new Map<Id, List<Contact>>();
for(Contact parent : [Select Id, Name, (Select Id, Name From EmergencyContacts) From Contact]) {
conMap.put(parent.Id, parent.EmergencyContacts__r);
}
system.debug('My Map: ' + conMap);
you can use below code to get your desired Map : -
for(Account_Territory_Loader_vod__c atl: atlList) {
if(accATLMap.containsKey(atl.account_vod__c) && accATLMap.get(atl.account_vod__c) != null) {
List<Account_Territory_Loader_vod__c> lst_terr = accATLMap.get(atl.account_vod__c);
lst_terr.add(atl);
accATLMap.put(atl.account_vod__c,lst_terr);
}
else {
accATLMap.put(atl.account_vod__c, new List<Account_Territory_Loader_vod__c> {atl});
}
}
hope it solve your purpose.
You're trying to put an instance of Account_Territory_Loader_vod__c into a list of Account_Territory_Loader_vod__c.
accATLMap.put(atl.account_vod__c,atl);
You need to instantiate a new list for each key in your map, and then add the element to the list:
// Check whether the value for that key exists
if (accATLMap.get(atl.account_vod__c) == null)
{
// instantiate a new list
accATLMap.put(atl.account_vod__c, new List <Account_Territory_Loader_vod__c> ());
}
// add the element to the list
accATLMap.get(atl.account_vod__c).add(atl);