Edit

Share via


Restore deleted records with code

[This article is prerelease documentation and is subject to change.]

Sometimes people delete records that they shouldn't. Administrators can enable deleted record keeping so that they can restore deleted records within a specified period of time. Learn how administrators can restore deleted records.

When deleted record keeping 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 by 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 by 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 by 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 by using the SDK for .NET depends on whether you're generating early bound types by 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 by 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"];
}

Use messages with the SDK for .NET

Best practices when restoring records

Avoid the following issues when restoring records:

If some related records reference records that the cascade relationship removed, the restore operation fails. To avoid this problem, always restore the related records that the current record deletion didn't remove 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

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, you can't restore the deleted record. If you want to restore the deleted record, delete the new record first.

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 the deleted record. If you want to restore the deleted record, delete the new record first.

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 the record 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 the same primary key already exists, nothing happens. To enforce that all deleted items are recorded, set the DoNotEnforcePrimaryKeyOrgSettingRecycleBin setting by 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 deleted record keeping

Before you enable this feature, the Deleted Record Keeping Configuration (RecycleBinConfig) table has no rows.

Over time, most tables will support deleted record keeping. Solution components, virtual tables, and elastic tables aren't supported for deleted record keeping. Some tables that aren't currently enabled might be enabled later (for example, tables with more than 600 columns). For a list of tables that don't support this feature, see Tables not currently supported.

You can also disable deleted record keeping for the environment. If deleted record keeping isn't enabled for a table, you won't find any records eligible to be restored. You can query Dataverse to find out whether deleted record keeping is enabled for a table or not.

Tables that are enabled for deleted record keeping 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 deleted record keeping 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 aren't enabled for deleted record keeping

To know which tables aren't enabled for deleted record keeping, use the following FetchXml query that is the reverse of the one found in Detect which tables are enabled.

<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 feature began are in Tables not currently supported for deleted record keeping.

Retrieve and set the automatic cleanup time period configuration for deleted record keeping

Set the value that determines how long deleted records are available to be restored 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 accepts values up to 30. Don't set this value unless you want to use a value different from the organization default.

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($"Deleted record keeping configuration for table '{tableLogicalName}' not found.");
    }
}

Use the SDK for .NET

Disable deleted record keeping for the environment

Note

The preferred way to disable deleted record keeping for an environment is to turn it off in the Power Platform admin center. The method described here might change before the feature becomes generally available.

Delete the row in the RecycleBinConfig table where the name value is organization. This action deletes all the records in the RecycleBinConfig table and disables deleted record keeping for the environment.

Important

Don't try to delete other individual records. It's important that Dataverse manages this action.

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, you can configure four possible behaviors:

Delete Behavior Description
Cascade All Deletes the related records.
Remove Link Sets the lookup columns to the deleted record to null.
Cascade None Applies no changes to related records. (Internal Only)
Restrict 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.

When you configure the relationship for Cascade All, Remove Link, or Restrict, Dataverse manages these behaviors and there's nothing extra to do.

If you configure a relationship to use the Remove Link behavior but the relationship is supposed to delete the related record, you might need 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 by using a plug-in, but it could also be done by 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, 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 you're restoring isn't available in the PreOperation stage. If you need to create related records, 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 deleted record keeping

The query described in Detect which tables aren't enabled was used to generate this list in August 2024.

See also

Restore deleted Microsoft Dataverse table records