Understanding Historical Field Updates in Microsoft Dataverse: CreatedOn, CreatedBy, ModifiedOn, and ModifiedBy
- Get link
- X
- Other Apps
When working on data migration projects in Dynamics CRM / Dataverse, one of the most common requirements is preserving historical data such as:
- Created On
- Created By
- Modified On
- Modified By
At first glance, it may seem straightforward to set these fields using the CRM SDK. However, many developers quickly discover that some values are ignored while others work only in specific scenarios.
This blog explains the behavior of these system fields across different execution contexts and clarifies when Dataverse allows overriding them.
Why This Matters
In most enterprise migration projects, business users expect migrated records to retain original audit history from legacy systems.
For example:
| Field | Expected Historical Value |
|---|---|
| Created On | Original record creation date |
| Created By | Original creator |
| Modified On | Last modification date from source system |
| Modified By | Last modifier from source system |
Many developers attempt to achieve this directly through SDK operations, only to find inconsistent behavior.
The key lies in understanding the execution context.
Scenario 1: Using SDK Create from an External Application
Consider the following console application code:
var createEnt = new Entity("account");
createEnt["name"] = "Data Migration Create from Console";
createEnt["overriddencreatedon"] = new DateTime(2018, 03, 01);
createEnt["createdon"] = new DateTime(2017, 03, 01);
createEnt["modifiedon"] = new DateTime(2018, 03, 01);
createEnt["modifiedby"] =
new EntityReference("systemuser", userId);
createEnt["createdby"] =
new EntityReference("systemuser", userId);
service.Create(createEnt);
What Happens?
After execution:
| Field | Result |
|---|---|
| overriddencreatedon | Applied |
| createdon | Ignored |
| createdby | Ignored |
| modifiedon | Ignored |
| modifiedby | Ignored |
Important Observation
When using service.Create() externally:
overriddencreatedon
is the only supported way to preserve historical Created On values.
Even if you explicitly set:
createEnt["createdon"]
Dataverse ignores it.
Instead, the platform internally maps the visible Created On value from:
overriddencreatedon
Key Takeaway
External SDK Create Supports Only:
overriddencreatedon
for historical Created On migration.
The remaining audit fields are controlled by the platform runtime.
Scenario 2: Setting Values Inside a Pre-Create Plugin
Now consider the same logic inside a Pre-Create plugin:
public void Execute(IServiceProvider serviceProvider)
{
var context =
(IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
var target =
(Entity)context.InputParameters["Target"];
target["createdon"] = new DateTime(2017, 03, 01);
target["modifiedon"] = new DateTime(2018, 03, 01);
target["modifiedby"] =
new EntityReference("systemuser", userId);
target["createdby"] =
new EntityReference("systemuser", userId);
}
Result
This time all values are successfully applied.
| Field | Works? |
|---|---|
| createdon | Yes |
| createdby | Yes |
| modifiedon | Yes |
| modifiedby | Yes |
| overriddencreatedon | Ignored |
Why Does Plugin Execution Work?
The answer lies in the Dataverse execution pipeline.
A Pre-Create plugin runs before Dataverse finalizes system values and commits the record to the database.
At this stage:
- system fields are still mutable
- plugins can override audit fields
- the platform respects those values
This is fundamentally different from external SDK execution.
Most Important Insight
External SDK Context
Dataverse controls:
- CreatedBy
- ModifiedBy
- ModifiedOn
and ignores custom values.
Pre-Create Plugin Context
Developers can override:
- CreatedOn
- CreatedBy
- ModifiedOn
- ModifiedBy
before database persistence.
Scenario 3: Pre-Update Plugin
A Pre-Update plugin behaves similarly.
If you set:
target["modifiedon"]
target["modifiedby"]
inside Pre-Update, Dataverse respects the values.
Although this scenario is less common for migration projects, it follows the same pipeline behavior.
Scenario 4: SDK Update Method
Now consider:
service.Update(entity);
Attempting to update historical fields using SDK update operations does not work.
Even:
entity["overriddencreatedon"]
has no effect during update operations.
Summary Table
| Scenario | createdon | overriddencreatedon | modifiedon | createdby | modifiedby |
|---|---|---|---|---|---|
| SDK Create | ❌ | ✅ | ❌ | ❌ | ❌ |
| Plugin Pre-Create | ✅ | ❌ | ✅ | ✅ | ✅ |
| Plugin Pre-Update | ✅ | ❌ | ✅ | ✅ | ✅ |
| SDK Update | ❌ | ❌ | ❌ | ❌ | ❌ |
Recommended Approach for Data Migration
For enterprise-grade migrations, the most reliable pattern is:
Migration Tool
↓
Dataverse SDK Create
↓
Pre-Create Plugin
↓
Set Historical Audit Fields
This architecture ensures:
- audit fields are preserved
- migration remains supported
- business expectations are met
Final Thoughts
Historical data preservation in Dataverse is highly dependent on execution context.
The most common misconception is assuming that SDK operations can directly update all audit fields. In reality:
overriddencreatedonworks only during SDK Create- audit fields like
createdbyandmodifiedbycan only be overridden within plugin pipeline stages
Understanding this distinction can save significant debugging effort during migration projects.
For anyone working on CRM/Dataverse data migration, this behavior is essential knowledge before designing migration architecture.
- Get link
- X
- Other Apps
Comments
Post a Comment