Tuesday, March 7, 2017

Single statement insert of parent and child custom objects

An example for inserting parent-child records in a single statement is provided by Salesforce here.


The crux of it is to create a parent reference record and refer to it in the child record lookup instead of trying to refer to the parent record variable to be inserted, i.e.

Account accountReference = new Account(MyExtID__c='SAP111111');newOpportunity.Account = accountReference;

Account parentAccount = new Account(Name='Hallie', MyExtID__c='SAP111111');

Database.insert(new SObject[] { parentAccount, newOpportunity });


A couple of things I took some time to figure out were:

1. How to get this working with custom objects? e.g. if I have Parent__c and Child__c objects that are linked via custom field Child__c.Parent_Lookup__c

The equivalent of newOpportunity.Account in the example above would be Child__c.Parent_Lookup__r

I had an hour of brain fade wondering why Child__c.Parent_Lookup__c wouldn't work, but that would be the equivalent of newOpportunity.AccountId, not newOpportunity.Account.

2. I needed to do this bulkified.

I expect there is a syntax you could use to create an inline sobject list out of two non-sobject lists and insert in one line of code but I didn't invest the effort to get there and went with the following instead (heavily abridged pseudocode):

List<SObject> sobjList = new List();
for(loop criteria){
  Parent__c parentRec = new Parent__c(field values);
  sobjList.add(parentRec);
  Child__c childRec = new Parent__c(field values);
  sobjList.add(childRec);
}
Database.insert(sobjList);

Tuesday, September 20, 2016

Salesforce upserts, SOAP & standard object lookups

Requirement

Need to perform a Salesforce upsert via SOAP API using External IDs on standard objects.

e.g. you want to specify the record Owner or RecordType without using Salesforce IDs.

Getting set up


Don't bother trying to prototype using Data Loader. All the standard object external IDs are unavailable. AFAICT Workbench doesn't support them either.
  1. Generate the WSDL and strip out any comments and script
  2. Install SoapUI if you haven't already
  3. Create a new SOAP project using your WSDL
  4. Find the login method and set it up as below. Get rid of any question marks
  5. Press play. The response appears on the right hand side.
This example is for the Enterprise WSDL.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com">
   <soapenv:Header>
      <urn:LoginScopeHeader>
         <urn:organizationId></urn:organizationId>
         <!--Optional:-->
         <urn:portalId></urn:portalId>
      </urn:LoginScopeHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:login>
         <urn:username>me@myorg.org.sandbox</urn:username>
         <urn:password>reallybadpassword</urn:password>
      </urn:login>
   </soapenv:Body>
</soapenv:Envelope>

Upsert time

Find the Upsert method, set it up as below and press play. Copy the session ID from the login response to it.

  • The server URL returned in the login response is the target for this request. Put it up in the url box at the top of the request window
  • Account doesn't have any standard external ID fields so a field called ExternalID__c has been created. You could use the ID field instead, but yeuch
  • The Account has a record type with Name "Record Type 1"
<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:urn="urn:enterprise.soap.sforce.com"
    xmlns:urn1="urn:sobject.enterprise.soap.sforce.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <soapenv:Header>
      <urn:SessionHeader>
        <sessionId>00Dm00000001xVa!ARQAQCyXrqpRsPctsGUj4AAHIKJJlBeOjxM9M.GOGMCjsYrNNAHJ6OTRIYxZLwbjXb.5bEZh66q09rhYecRDl4YNUc0_7ruO</sessionId>
      </urn:SessionHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:upsert>
         <urn:externalIDFieldName>ExternalId__c</urn:externalIDFieldName>
         <urn:sObjects xsi:type="urn1:Account">
            <urn1:ExternalId__c>abcd1234</urn1:ExternalId__c>
            <urn1:Owner xsi:type="urn1:User">
               <urn1:Email>me@myorg.org</urn1:Email>
            </urn1:Owner>
            <urn1:RecordType xsi:type="urn1:RecordType">
               <urn1:Email>Record Type 1</urn1:Email>
            </urn1:RecordType>
         </urn:sObjects>
      </urn:upsert>
   </soapenv:Body>
</soapenv:Envelope>

Other things to note

To find out which standard object fields are available as external IDs, use the Workbench and look for idlookup:true" on a field.

If you're a freetard using the dead-ended version of SoapUI (5.2.1) then you'll need to enable TLS 1.2 as per instructions at the following link...(or so I've heard)

https://community.smartbear.com/t5/SoapUI-Open-Source/How-to-enable-TLS-1-2-in-SoapUI/td-p/96239/page/2

The correct instruction in this topic for Windows:

Place this in your SoapUI-X.Y.Z.vmoptionsf file (where X.Y.Z is the version number of SOAPUI you have)

-Dsoapui.https.protocols=TLSv1.2,SSLv3


The User object doesn't have any fields where both idlookup and unique are true. As a result, to do an upsert involving Users, the session user requires the incredibly broad View All Data rights, otherwise they will get this error: "INSUFFICIENT_ACCESS: Upsert requires view all data on a non-unique custom index". This despite UserNames being unique across the entire platform!

The RecordType Name field can be used as an external ID, but the DeveloperName field can't, which is odd given that the DeveloperName is recommended as the invariant reference for apex code.

Monday, January 11, 2016

Salesforce HYPERLINK expression and communities

Problem


A text formula field providing a url as follows doesn't work for a named community, so only works internally.

HYPERLINK( "/"+Parent_Custom_Object__r.Id, Parent_Custom_Object__r.Name,"_self")

e.g. if you have a community called mysite, you want the following url for users logging into this site

https://mysite-mydomain.instance.force.com/mysite/0ZZ123DEF456GHI

but instead you get

https://mysite-mydomain.instance.force.com/0ZZ123DEF456GHI

which only works for internal users and gives an Under Construction error for communities users.

Solution

HYPERLINK( LEFT($Api.Partner_Server_URL_260, FIND( '/services', $Api.Partner_Server_URL_260))+Parent_Custom_Object__r.Id, Parent_Custom_Object__r.Name,"_self")

Since the Partner_Server_URL_nnn is served up by the $API variable, I thought it wouldn't be sensitive to user context. But it is, which means this works for communities! It could be either $API or the UI itself which is converting to a communities-friendly URL, I don't know which.

Saturday, December 6, 2014

Windows 8 Pro and multiple simultaneous logins

If you have Windows 8 Pro then Remote Desktop is available for you to log in remotely. Great! However with the version of RDP that comes with Win 8 Pro, only one login is allowed at a time. If someone is using the PC and someone else logs in remotely, the console user (and any other remotely logged in users) will get kicked off.

Enabling multiple simultaneous logins involves updating a system file, which also means changing file security so you can work with the file. I had to refer to a few different sources to figure out how to achieve this.

So for next time these are my consolidated steps for this:

  1. Bring up the Task Manager and stop Terminal Services (may also need to stop UMRDPService)
  2. In the Start menu, type File Explorer, and when it comes up, right-click and Run As Administrator. Say OK to any dialog boxes that pop up
  3. Find termsrv.dll in the c:\windows\system32 folder, right-click and select properties
  4. Select the Security tab, click the Advanced button
  5. Next to Owner, click Change
  6. Type Administrators in the text box and click Check Names. Your local administrators group should be found (named YourPC\Administrators). Click OK
  7. Click the Auditing tab, click Add then click Select a Principal
  8. Type Administrators in the text box and click Check Names. Your local administrators group should be found (named YourPC\Administrators). Click OK
  9. For Basic permissions, tick Full Control and click OK in all dialog boxes until you're back to File Explorer
  10. Right-click termsrv.dll again, select properties, click the Security tab and click Edit
  11. In the Groups or User Names box, make sure you have Administrators selected then click Full Control and click OK until you're back to File Explorer
  12. Copy termsrv.dll to a backup location. Can't be too careful!
  13. Download zip file from http://cdn.nextofwindows.com/download/win81rdp.zip
  14. Now you need to figure out which file to use. If your Windows is 32 bit then you want to use 32_termsrv.dll, otherwise 64_termsrv.dll. If you're not sure then compare the size of your existing file with these two - you want to use the one that's the same size (or nearly). Extract the dll you want from the zip and remove the prefix so the name is termsrv.dll.
  15. Copy the new file into c:\system\windows32
  16. Restart Terminal Services (and also UMRDPService if you have it)


References:

Kent Chen's info about the replacement dll
http://www.nextofwindows.com/how-to-allow-multiple-concurrent-users-log-in-windows-8-through-remote-desktop/

Usman Javaid's info about changing system file permissions
http://www.addictivetips.com/windows-tips/take-ownership-of-files-folder-and-change-permissions-in-windows-8/

Tuesday, August 5, 2014

profileID attribute is either null or invalid

Error

Salesforce Community Self-registration attempt fails with the following error:

A new user's attempt to register at failed because the value for the profileID attribute is either null or invalid. Set the profileID for new users on the ChatterAnswersRegistration Visualforce page for the site associated with community  or on the apex class associated with the Facebook authprovider.

Possible Cause


CommunitiesSelfRegController.cls doesn't have a valid ProfileId

The error doesn't mention this class at all.

Saturday, July 19, 2014

Invalid Id: An unexpected error has occurred. Your development organization has been notified

This obscure Salesforce error appears from time to time when fetching a custom url parameter in an extension controller class.

Id recordId;
recordId = ApexPages.currentPage().getParameters().get('rid');
if(rid!=null&&rid!=''){ //ERROR REPORTED ON THIS LINE

Resolution is to change the data type to String. There seem to be more implicit conversions from String to Id than the other way.

Friday, June 6, 2014

apex soql subselect weirdness with "with sharing"

I had some code similar to the following:

Public with sharing class MyClass{
...
    ParentObject__c[] pos = [select Id, (select MasterDetail_2__c from ChildObjects__r)
        from ParentObject__c];
...
   Set MasterDetail_1s = new Set();
    for(ParentObject__c po:pos){
        for(ChildObject__co: po.ChildObjects__r){
            MasterDetail_1s.add(co.MasterDetail_1__c);
        }
    }
...
}

But I was not getting any child records back in the select. Infuriatingly, running the query in the Developer Console Query Editor/Force.com IDE Schema/Force.com Explorer returned the expected results.

While checking that the problem wasn't related to permissions, I changed "with sharing" to "without sharing" and then immediately the following error was reported:

SObject row was retrieved via SOQL without querying the requested field: ChildObject__c.MasterDetail__1__c

With this simplified version of the code it's obvious there is a problem: ChildObject__c.MasterDetail_1__c is used without having been queried. However this error wasn't being reported when the class was declared as "with sharing".

In summary: if subselects are not behaving, "with sharing" may be hiding the errors. Switch to "without sharing" for debug purposes, fix the code and switch back again.

Edit: it actually seems to be more complicated than this - the code stopped working again even with "without sharing". The actual code had something more like

MasterDetail_1s.add(String.valueOf(co.MasterDetail_1__c));

which appeared to cause a problem, but again no exception was raised. I avoided this by removing the explicit conversion.