Restore deleted records with code (preview)
[This article is pre-release documentation and is subject to change.]
Sometimes people delete records that they shouldn't. Administrators can enable a recycle bin for tables so that they can restore deleted records within a specified period of time. Learn how administrators can restore deleted records
When the recycle bin is enabled, developers can use the Restore
message to restore deleted record before the specified period of time. The period of time can be up to 30 days.
Retrieve deleted records that can be restored
To retrieve deleted records that can be restored, set the datasource of the query to 'bin
'.
The following examples return up to three deleted account records.
When using the SDK, you can retrieve data using FetchXml or QueryExpression.
When you retrieve data using FetchXml, set the fetch element datasource
attribute to 'bin
' when you retrieve records.
static EntityCollection GetDeletedAccountRecordsFetchXml(IOrganizationService service) {
string queryString = @"<fetch top='3' datasource='bin'>
<entity name='account'>
<attribute name='name' />
</entity>
</fetch>";
FetchExpression query = new(queryString);
return service.RetrieveMultiple(query);
}
When you retrieve data using QueryExpression, set the QueryExpression.DataSource property to 'bin
' when you retrieve records.
static EntityCollection GetDeletedAccountRecordsQueryExpression(IOrganizationService service) {
QueryExpression query = new("account") {
ColumnSet = new ColumnSet("name"),
DataSource = "bin",
TopCount = 3
};
return service.RetrieveMultiple(query);
}
Restore a deleted record
Use the Restore
message to restore a deleted record. The Target
parameter isn't a reference to a deleted record, it's a full record so you can set column values while you restore the record. All the original column values are restored unless you override them by setting values during the Restore
operation.
Note
At this time you can only restore records using the primary key value. You can't use an alternate key to restore a record.
How you restore a deleted record depends on whether you're using the SDK for .NET or Web API.
How you restore a record using the SDK for .NET depends on whether you're generating early bound types using pac modelbuilder, or if you're using the late bound style.
Learn about late-bound and early-bound programming using the SDK for .NET
Early bound example
The static RestoreAccountRecordEarlyBound
method uses the RestoreRequest<T>
and Account
classes generated using the pac modelbuilder.
/// <summary>
/// Restores an account record
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance</param>
/// <param name="accountId">The ID of the deleted account record.</param>
/// <param name="originalName">The original name value for the account record.</param>
/// <returns>The ID of the restored account</returns>
static Guid RestoreAccountRecordEarlyBound(
IOrganizationService service,
Guid accountId,
string originalName)
{
Account accountToRestore = new()
{
Id = accountId,
// Appending '(Restored)' to the original name
// to demonstrate overwriting a value.
Name = originalName + " (Restored)"
};
RestoreRequest<Account> request = new()
{
Target = accountToRestore
};
var response = (RestoreResponse)service.Execute(request);
return response.id;
}
Late bound example
The static RestoreAccountRecordLateBound
method uses the OrganizationRequest class to invoke the Restore
message, setting the Target
parameter.
/// <summary>
/// Restores an account record
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance</param>
/// <param name="accountId">The ID of the deleted account record.</param>
/// <param name="originalName">The original name value for the account record.</param>
/// <returns>The ID of the restored account</returns>
static Guid RestoreAccountRecordLateBound(
IOrganizationService service,
Guid accountId,
string originalName)
{
Entity accountToRestore = new("account", accountId)
{
Attributes = {
// Appending '(Restored)' to the original name
// to demonstrate overwriting a value.
{"name", originalName + " (Restored)"}
}
};
OrganizationRequest request = new("Restore")
{
Parameters = {
{ "Target", accountToRestore }
}
};
OrganizationResponse response = service.Execute(request);
return (Guid)response.Results["id"];
}
Best practices when restoring records
The following are issues you can avoid when restoring records:
- Restore related records before restoring primary record
- Don't specify primary key values when creating records
- Records with matching alternate key values block restore
- Records using removed Choice options aren't restored
- Primary Key Violation on Delete
Restore related records before restoring primary record
If some related records whose reference were removed as part of cascade relationship no longer exist, the restore operation fails. To avoid this problem, always restore the related records not deleted as part of current record before trying to restore the primary record.
Name:
RefCannotBeRestoredRecycleBinNotFound
Code:0x80049959
Number:-2147182247
Message:Entity with id '<Guid Value>' and logical name '<Entity.LogicalName>' does not exist. We cannot restore the reference '<Referred Primary Key Name>' that must be restored as part of this Restore call. ValueToBeRestored: <Guid Value>, ReferencedEntityName: <Referenced Entity Name>, AttributeName: <Referred Attribute Name>
Don't specify primary key values when creating records
It's generally a good practice to always let Dataverse set the primary key when creating a record. If you create a new record that has the same primary key value as a deleted record, the deleted record can't be restored. If you do, you must delete the new record before you can restore the deleted one.
Name:
DuplicateExceptionRestoreRecycleBin
Code:0x80044a02
Number:-2147182279
Message:Please delete the existing conflicting record '<Entity Platform Name>' with primary key '<Primary Key Name>' and primary key value '<Primary Key Value>' before attempting restore.
Records with matching alternate key values block restore
If you create a record that has the same alternate key column values as a deleted record, you can't restore it. If you do, you must delete the new record before you can restore the deleted one.
Name:
DuplicateExceptionEntityKeyRestoreRecycleBin
Code:0x80049929
Number:-2147182295
Message:Duplicate entity key preventing restore of record '<Entity Platform Name>' with primary key '<Primary Key Name>' and primary key value '<Primary Key Value>'. See inner exception for entity key details.
Records using removed Choice options aren't restored
If you delete an optionset option, and that option was used in a deleted record, you can't restore it because the option is now invalid. Before deleting an option set option, check that no records use that option, including deleted records.
Name:
PicklistValueOutOfRangeRecycleBin
Code:0x80049949
Number:-2147182263
Message:Picklist value not valid, please add the invalid value back to the picklist before restoring record
Primary Key Violation on Delete
If the record with same primary key was already deleted before, copy to recycle bin is ignored for the record. To enforce all deleted items are stored in recycle bin, you can set the DoNotEnforcePrimaryKeyOrgSettingRecycleBin
setting using the OrgDBOrgSettings tool for Microsoft Dynamics CRM.
After enabling this setting, you might receive the following error:
Name:
DuplicateExceptionRestoreRecycleBin
Code:0x80049939
Number:-2147182279
Message:A record that has the attribute values Deleted Object already exists on Delete.
Detect which tables are enabled for recycle bin
Before the recycle bin feature is enabled, the Recycle Bin Configuration (RecycleBinConfig) table has no rows.
In time, we expect that eventually most tables will be available to use the recycle bin feature. Solution components, virtual tables, and elastic tables aren't supported for recycle bin. During this preview, some tables not currently enabled might be enabled later (For example, tables with more than 400 columns). For a list of tables that don't support recycle bin, see Tables not currently supported for Recycle Bin.
You can also disable recycle bin for specific tables and disable recycle bin for the environment. If the recycle bin isn't enabled for a table, you won't find any records eligible to be restored. You can query Dataverse to find out whether the recycle bin is enabled for a table or not.
Tables that are enabled for recycle bin have a row in the RecycleBinConfig
table where the statecode
is active and isreadyforrecyclebin
is true. The RecycleBinConfig
table doesn't contain the name of the table, but refers to a row in the Entity table where the logicalname
column contains the LogicalName of the table.
Use the following FetchXml query to detect which tables have recycle bin enabled:
<fetch>
<entity name='recyclebinconfig'>
<filter type='and'>
<condition attribute='statecode'
operator='eq'
value='0' />
<condition attribute='isreadyforrecyclebin'
operator='eq'
value='1' />
</filter>
<link-entity name='entity'
from='entityid'
to='extensionofrecordid'
link-type='inner'
alias='entity'>
<attribute name='logicalname' />
<order attribute='logicalname' />
</link-entity>
</entity>
</fetch>
Learn to query data using FetchXml
Detect which tables don't have recycle bin enabled
To know which tables aren't enabled for recycle bin, use the following FetchXml query that is the reverse of the one found in Detect which tables are enabled for recycle bin.
<fetch>
<entity name='entity'>
<attribute name='logicalname' />
<filter type='or'>
<condition entityname='recyclebin'
attribute='extensionofrecordid'
operator='null' />
<condition entityname='recyclebin'
attribute='statecode'
operator='ne'
value='0' />
<condition entityname='recyclebin'
attribute='isreadyforrecyclebin'
operator='ne'
value='1' />
</filter>
<order attribute='logicalname' />
<link-entity name='recyclebinconfig'
from='extensionofrecordid'
to='entityid'
link-type='outer'
alias='recyclebin' />
</entity>
</fetch>
Learn to query data using FetchXml
The results of this query as of May 2024 when this preview feature began are in Tables not currently supported for Recycle Bin
Retrieve and set the automatic cleanup time period configuration for the recycle bin
The value to determine how long deleted records are available to be restored is set in the RecycleBinConfig.CleanupIntervalInDays column where the Name column value is organization
. Every other row in the RecycleBinConfig
table has a CleanupIntervalInDays
column value of -1
. This value indicates it uses the same values set for the organization
table.
To specify a different value for another table, set the CleanupIntervalInDays
column value where the Name
matches the logical name of the table. This column allows values up to 30, we recommend not setting it unless different from organization default value.
You can use this static SetCleanupIntervalInDays
method to set the CleanupIntervalInDays
column value for a specific table.
/// <summary>
/// Updates the CleanupIntervalInDays value for a specified table
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance</param>
/// <param name="entityId">The entityId of the table</param>
/// <param name="cleanupIntervalInDays">The new CleanupIntervalInDays value</param>
static void SetCleanupIntervalInDays(
IOrganizationService service,
Guid entityId,
int cleanupIntervalInDays)
{
QueryExpression query = new("recyclebinconfig")
{
ColumnSet = new ColumnSet("recyclebinconfigid"),
Criteria = new FilterExpression(LogicalOperator.And)
{
Conditions = {
{
new ConditionExpression(
attributeName: "extensionofrecordid",
conditionOperator: ConditionOperator.Equal,
value: entityId)
}
}
}
};
EntityCollection records = service.RetrieveMultiple(query);
if (records.Entities.Count.Equals(1))
{
Guid id = records.Entities[0].Id;
Entity record = new(entityName: "recyclebinconfig", id: id)
{
Attributes = {
{ "cleanupintervalindays", cleanupIntervalInDays }
}
};
service.Update(record);
}
else
{
throw new Exception($"Recycle bin configuration for table '{tableLogicalName}' not found.");
}
}
Disable recycle bin for a table
To disable the recycle bin for a table, disable the recyclebinconfig
record for the table by setting the statecode and statuscode properties to their Inactive values: 2
and 1
respectively.
Note
The following queries compare the EntityId
value against the Entity.EntityId column value, which stores the table EntityMetadata.MetadataId .
Use this static DisableRecycleBinForTable
method to disable the recycle bin for a specific table.
/// <summary>
/// Disable the Recycle bin for a specified table
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance</param>
/// <param name="tableEntityId">The entityId of the table</param>
static void DisableRecycleBinForTable(
IOrganizationService service,
Guid tableEntityId)
{
QueryExpression query = new("recyclebinconfig")
{
ColumnSet = new ColumnSet("recyclebinconfigid")
};
LinkEntity entityLink = query.AddLink(
"entity",
"extensionofrecordid",
"entityid");
entityLink.LinkCriteria.AddCondition(
"extensionofrecordid",
ConditionOperator.Equal,
tableEntityId);
EntityCollection recyclebinconfigs = service.RetrieveMultiple(query);
if (recyclebinconfigs.Entities.Count.Equals(1))
{
var id = recyclebinconfigs.Entities[0].GetAttributeValue<Guid>("recyclebinconfigid");
Entity recyclebinconfig = new("recyclebinconfig", id)
{
Attributes = {
{ "statecode", new OptionSetValue(1) },
{ "statuscode", new OptionSetValue(2) }
}
};
service.Update(recyclebinconfig);
}
else
{
string message = $"Recycle bin configuration for table '{extensionofrecordid}' not found.";
throw new Exception(message);
}
}
Disable recycle bin for the environment
Note
The preferred way to disable recycle bin for an environment is to turn it off in the Power Platform admin center. The method described here may change before the feature becomes generally available.
Delete the row in the RecycleBinConfig table where the name
value is "organization"
. This triggers deleting all the records in the RecycleBinConfig
table and disable recycle bin for the environment.
Important
Don't try to delete other individual records. It is important that Dataverse manage this.
Manage restoring records deleted by custom business logic
Dataverse provides a mechanism to manage desired actions for related records when a row is deleted. This configuration data is part of the definition of the relationship. When a related record is deleted, there are four possible behaviors that you can configure:
Delete Behavior | Description |
---|---|
Cascade All | The related records are deleted. |
Remove Link | The lookup columns to the deleted record are set to null. |
Cascade None | No changes are applied to related records. (Internal Only) |
Restrict | Dataverse prevents deleting the record to maintain data integrity. The record can't be deleted unless there are no records related for this relationship. |
Learn more about relationship behaviors
There's nothing to do when the relationship is configured for Cascade All, Remove Link, and Restrict because Dataverse manages these behaviors.
If you have a relationship configured to use the Remove Link behavior, but this relationship is supposed to delete the related record, you might have custom logic that applies some custom behavior. For example, you might wish to respond to this behavior differently and implement your own 'Cascade some' behavior based on rules you define. For example, you might delete inactive records or records that weren't updated in a certain period of time. This logic is usually implemented using a plug-in, but it could also be done using Power Automate with the Microsoft Dataverse connector: When a row is added, modified or deleted trigger.
If you have this kind of custom business logic, then Dataverse doesn't know about it and can't automatically 'undo' your logic. However, you can register another plug-in on the Restore
message to reverse whatever custom logic you have. Or you could use Power Automate and the Microsoft Dataverse connector: When an action is performed trigger.
Important
Be careful about the context when you register plug-in steps for the Restore
message. The record being restored will not be available in the PreOperation
stage. If related records need to be created, use the PostOperation
stage. Learn more about plug-in stages.
The InputParameters and OutputParameters of the Restore
message are similar to Create
message, so plug-ins written to be registered for the Create
message can be re-used for the Restore
message with fewer changes.
Tables not currently supported for Recycle Bin
The query described in Detect which tables don't have recycle bin enabled was used to generate this list in August of 2024.