Shared records

From Resco's Wiki
Revision as of 11:05, 2 December 2020 by Jzambor (talk | contribs) (Created page with "{{Synchronization TOC}} To ensure that each user of a CRM or other computer systems has access to the right data, most data models work with the concept of ownership. Data can...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

To ensure that each user of a CRM or other computer systems has access to the right data, most data models work with the concept of ownership. Data can be owned directly by users or by teams or organizations. Ownership is often related to data visibility and access, for example, users generally can access the data they own.

Many backend servers have ways to share particular records with other users or organizations. The exact implementation is backend-specific. Sharing is a mean to make accessible records which are normally invisible to the current user.

To ensure that shared records are properly available also in Resco mobile apps, the synchronization process includes a special step called "SyncSharedEntities" that's responsible for keeping shared records up to date.


In Dynamics CRM, users can share records to others. For example, when user A shares the record R to user B:

  • R is added to so-called POA (PrincipalObjectAccess) table. (In this context, principal means a user, or a team, etc.)
  • R becomes "visible" to user B.
  • R itself was not modified through the act of sharing.

This mechanic can have the following impact on Resco Mobile CRM app:

  • Full synchronization: No impact. Shared records are downloaded together with other records the mobile user is entitled to see.
  • Incremental synchronization: Newly shared records (that were not modified) won't be downloaded. This is a problem.
If AppSettings.SyncShared == true
    Sync progress bar shows "Shared".
    foreach normal entity (not NN)
        Fetch id's of all entity records that were shared since the last sync    // Fetch POA table records
        If there are such records
        Fetch those of them that match the SyncFilter

A similar process also runs for records that were unshared since the last sync.

Notes on POA:

  • This table can be important for Technical Support. This table is known to deteriorate server performance. (Possible server timeouts if POA has multi-million entries.)
  • Any fetch executed on some table incorporates implicitly also shared records:
Original fetch
 SELECT * FROM email WHERE condition
is replaced by:
WITH emailSecurity as (SELECT * FROM email UNION SELECT shared emails from POA)
SELECT * FROM emailSecurity WHERE condition

Sync log

This synchronization step saves information to the sync log.

<SharedEntities Recv=1234 [Del=123 Conflicts=12] TotalTim=12345ms />
<FullSync Recv='14719' TotalTime='15828ms' Entitys='2312ms' ManyToMany='578ms' Shared='469ms' MarketingLists='11953ms' />


Sharing is completely transparent to Salesforce fetches. E.g., you can ask for all contact records and you'll get:

  • All records to which you are entitled based on your current permissions.
  • All records which are shared with you at the moment the fetch was issued.

As a result:

  • FullSync returns all suitable records, including all shared records.
  • IncSync has problems:
    • It downloads changed records. Newly shared records do not need to be changed. Incremental sync will not download them.
    • Another problem is record unsharing: These records just silently disappear from the view; standard incremental sync has no chance to find out that a record is no longer shared.

Sharing in Salesforce

First of all, not all objects support sharing:

  • Object with OWD (Organization Wide Defaults), i.e., globally accessible objects, do not need sharing at all as "every user sees everything".
  • Objects on the detail side of Master-Detail relationship do not support sharing as well; access to these records is defined by the access to their master record. (Note: MCRM v13.3 does not check this.)

Records are shared with a specific user or to a public group. Groups are hierarchical - a group can contain both users and other groups. Moreover, users can share records not only to a group but also to its subordinates.

From another point of view, sharing can be manual (by a user action), automatic (based on the rules), or even added programmatically via Apex or SOAP.

Rather than analyzing record access in real-time, Salesforce precalculates access data when configuration changes occur. (Manually invoked via Recalculate button in Sharing Settings.) To store the results of these calculations, Salesforce maintains a separate share table for every object that supports sharing. For example:

  • AccountShare for Account (standard) object,
  • Brunch_Office__Share for sf_brunch_office__c (custom) object.

Note: This contrasts to Dynamics CRM solution, which maintains a single share table (POA) for all entities.

Resco implementation

Woodford admin can enable objects (entities) for which the sharing should be checked. Select the object from the Project menu and check Sync shared records.

Resco mobile apps detect objects that support sharing in the permission check phase of synchronization. (Permission check is done during the first sync in an app session, and also when the sync updates the customization.)


  • Normal sync downloads all visible records, included those which are shared at the moment.
  • SyncShared module just downloads a list of IDs of shared records and stores them in the local share table. (rs_sf_account_share, rs_sf_brunch_office__c_Share...)
  • The app remembers the time when the entity shared records were synced.


  • The app first downloads record IDs that were shared on the server since the last sync.
  • These IDs are added to the local share table and corresponding entity records are downloaded from the server.

Sharing cleanup (SyncUnshared)

  • The app explicitly checks the visibility of all known shared records and deletes those not visible from the client device.
(Known shared records = records whose IDs are stored in local share table. Record visibility is checked via an attempt to download record ID. This is done in batches.)
  • Eventual conflicts (Server unshared a record, which was edited on the client at the same time) are detected.

The sharing cleanup is carried out always, i.e., also for FullSync. In an ideal world, this would not be needed. However, there can be a considerable time between the entity normal sync and shared sync. (For example, when sync was aborted and the user does not start it again until after several days.) Therefore, the app executes the cleanup always.


The following entities/properties must be enabled in the app project:

  • sf_user.userroleid
  • sf_group.relatedid
  • sf_groupmember.userorgroupid

Sync Shared Records must be enabled on two locations:

Behavior of the mobile app

During the synchronization, the sync progress bar displays the message "Syncing Shares: ...entity_name...".

Error treatment: Abort breaks the sync. Other errors are logged (prompt "SyncSharedEntities"), sync goes on. An (artificial) example of such a log follows:

<EXCEPTION>16:21:58.135: SyncSharedEntities
NullReferenceException: Object reference not set to an instance of an object.
  at Salesforce.SFSharing.SharedSync(SalesforceService sfService)
  at SyncEngine.SFSharedSync(db)

Sync log

Sharing summary:

<SyncShared Recv='1234' ApiCalls='15' Conflicts='1' Attachments='3' CheckUnshared='2345ms/12calls' TotalTime='3333ms' />


Sharing initialization failed: No share objects accessible to this user
Sharing initialization failed: Disabled sf_user     // sf_user.userroleid, sf_group, sf_group.relatedid, sf_groupmember, sf_groupmember.userorgroupid
WARN: More than 10000 shared records for entity sf_contact; the rest is ignored

List of implemented detail logs:

GetListOfShareIds(sf_account) -> 314 recs, 234ms                            // FullSync
GetListOfShareIds(sf_account) since 2020-11-13 10:23:36 -> 314 recs, 234ms  // IncSync
12 shared sf_account recs ignored as they are already on the client         // IncSync
Download sf_account shares -> 302 recs / 1234ms[; Saved in 12ms]            // IncSync
<CheckUnshared Entity='sf_account' Deleted='0' Conflicts='1'  ApiCalls='3' DB='233ms' TotalTime='2345ms' />  // Cleanup

Sync summary: As of release 13.3, synchronization of shared records for Salesforce does not put any sharing info into the <Summary> section.


In the context of synchronization of shared records, a conflict means that the server unshared the record and the client edited the same record. In technical terms: Device:Update|Server:AccessRevoked.

A different case, Device:Delete|Server:AccessRevoked is not considered as a conflict: The client record is silently deleted, nothing is logged.

Conflict resolution is defined in entity attributes (Woodford):

  • Server Wins (default conflict resolution): the conflict is automatically resolved and logged this way: Device:Update|Server:AccessRevoked -> ServerEntityWins (LocalRecordDeleted)
  • In other cases (Device Wins or Manual Action) the conflict must be resolved manually. The sync errors form shows the conflict and sync log contains: Device:Update|Server:AccessRevoked -> CustomHandling

Examples of logged conflicts:

  • Conflict: sf_contact ID[1fb67823-8000-f000-0000-882702725352] Name[John Smith]: Device:Update|Server:AccessRevoked -> ServerEntityWins(LocalRecordDeleted)
  • Conflict: sf_contact ID[1fb67823-8000-f000-0000-882702725352] Name[John Smith]: Device:Update|Server:AccessRevoked -> CustomHandling

Limits, potential problems

  • Master-detail relations. (E.g., Order - OrderItem.): The detail objects do not support sharing. In fact, detail records should be shared automatically with their master record. However, this is not implemented in Resco mobile apps.
  • Max 10.000 shared records/entity are supported. This is a performance measure. When checking for unshared records, the app validates the existence of past known shares. However, due to Salesforce limitation on SOQL command length, it is possible to check at most 180 IDs in one web request. For this reason, Resco adopted this limitation.