Contact subtype is removed after being cached incorrectly
Contact subtypes can be overwritten if there's a create call before and after:
Call 1:
civicrm_api3('Contact', 'create', [ 'id' => 6, 'birth_date' => 20190119, ]);
Call 2:
civicrm_api3('Contact', 'create', [ 'id' => 6, 'contact_sub_type' => "Test Subtype", ]);
Call 3:
civicrm_api3('Contact', 'create', [ 'id' => 6, 'birth_date' => 20190315, ]);
The subtype set in 2 will get overwritten in 3, because it's been cached in 1.
This is because of a call to CRM_Contact_BAO_Contact::getContactSubType, which in turn calls CRM_Core_DAO_getFieldValue(). getFieldValue() checks a cache first before querying the db. In this case the subtype has been set as empty by the first call, and doesn't get updated by the second. The third call then sets it with the empty value from the first call.
A workaround is to force getFieldValue to bypass the cache:
$subtype = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $id, 'contact_sub_type', 'id', true);
This works (including if the whole thing is wrapped in a transaction). But obviously this negates the cache entirely. Is there a better approach?
Discovered in 5.13.5, and replicated on 5.16.3.