It can be easy to assume that, if you set up Staging in Kentico, that Custom Modules - the most integrated way of making custom objects in Kentico - that Staging should 'just work'. But by default, classes are not in fact staged.
Setting up Staging
In order to make staging happen at all, if you generate a Code file for your custom module class, it'll come with a section initializing a TYPEINFO object. This is the key to enabling staging for custom modules.
In the TypeInfo of that class, firstly, set up any fields that aren't already identified, if you've got them. We'll want these mapped out properly for *any* class that's generated a code file, so it's worth touching it here, to be sure. Here's an example one for an Office.
public static readonly ObjectTypeInfo TYPEINFO = new ObjectTypeInfo(typeof(CustomOffice), OBJECT_TYPE, "Custom.Office", "OfficeID", "OfficeLastModified", "OfficeGuid", "OfficeCodeName", "OfficeDisplayName", null, SiteID, null, null). Take especial note of those last two 'null' values. We'll talk about those in a moment.
Now that that's ready, under the existing parameters in that TYPEINFO definition... ModuleName, TouchCacheDependencies, etc.. Add a new one, "SynchronizationSettings". Here is where you'll set the LogSynchronizationProperty as SynchronizationTypeEnum.LogSynchronization (staging tasks are logged when the class is dierctly touched)
Another value for this Settings field exists, "TouchParent" - Essentially, if you wire up a class as a Child Class with a Parent Class, its synchronization gets wrapped into the parent. So, if you edit the child, it doesn't generate a task for the child, it generates one for the parent... and when you synchronize the parent, it brings over its children with it.
Kentico's example in their documentation is an Office, and a Job in that office. Further assume, that the only place you talk about Jobs, is in the context of its Office. It may be one to many, but the idea here is that the child objects are so bound together with the parent, that it doesn't make sense for a Job to exist without an Office, and you'd never push a Job to the next server without its relevant Office existing as well. This is what Child/Parent relationships are for.
Generally, though, we won't be setting TouchParent directly. This is because if your type has a direct parent (in other words, a class that this class is wholly dependent on, like a Category to a Category Group, or an Order Item to an Order... you can fill out those last two 'null' properties on the TYPEINFO with the ID field of the parent item, and the Namespace.Class.OBJECT_TYPE of the parent class (if you've generated it a code file, too). If you do this, those two fields will actually set the Synchronization type to TouchParent by default - if the parent is deleted, the child will be deleted, and if the parent is exported, the children will be, too. This also simplifies the display of the child objects when you build custom interfaces, too. So if this fits your project, this is the best path.
Here's the documentation on that - read this, as there are a few caveats: https://docs.kentico.com/k12sp/custom-development/creating-custom-modules/setting-the-type-information-for-module-classes/setting-parent-child-relationships-for-classes
Carrying over IDs
It can also be easy to assume that an ID is an ID - the unique identifier for a thing. But in Kentico, those IDs are just integers auto incremented - so,if you have a Staging and Production environment set up, and create a new document in each environment, they could share the same ID. For this reason, Kentico uses a separate field, a GUID, to serve as a unique identifier for items - a GUID is much less likely to accidentally collide between environments.
However, this means, if you go with your instinct to reference a related object... say, a User as the manager of an "Office" custom module class... by its ID (for example, the "UserID" field in a "ManagerUserID" field on the Office class), that won't be compatible with Staging out of the box. The UserID will get regenerated when you sync, but the reference in the ManagerUserID field will stay the same, and you've lost your reference.
All is not lost, though. That same TYPEINFO class is the key to wiring this back up.
When you stage an object that contains another object's ID by default, it won't change anything over. In this case, we'll be adding a field "DependsOn".
DependsOn = new List<ObjectDependency>()
new ObjectDependency("OfficeJobOfficeID", Custom.Office.OBJECT_TYPE, ObjectDependencyEnum.Required, "OfficeID"),
This example is what we'd put on a class that has an "
field with a Required reference to OfficeID. If we set this, then when we stage, it'll know to submit the Office GUID field of that Office as well, and the target server will find the Office with that GUID, and use *its* ID as the value of OfficeID, thus preserving the reference.
Obviously, this only works if your 'target' class *has* a GUID field - if you're thinking you might ever use Staging on your Kentico project, you should definitely include a GUID field (which Kentico will autogenerate) on every custom class you create (other than, like, many-many binding classes).
As you can see, even though it doesn't happen by default, you can pretty easily guide Kentico in code to know how to stage things, and preserve their ID mappings when it does.