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"];
}

Use messages with the SDK for .NET

Best practices when restoring records

The following are issues you can avoid when restoring records:

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 all tables will be available to use the recycle bin feature. During this preview, some tables don't. 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.");
    }
}

Use the SDK for .NET

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);
    }
}

Use the SDK for .NET

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 following tables are the result of the query found in Detect which tables don't have recycle bin enabled in May of 2024 when the preview of this feature started. Private tables aren't included in this list.

aaduser
aicopilot
aiplugin
aipluginconversationstartermapping
aipluginexternalschemaproperty
aipluginoperation
aipluginoperationresponsetemplate
annualfiscalcalendar
appaction_appactionrule_classicrules
appactionrule
appconfig
application
applicationuser
applicationuserrole
appmodulecomponent
appnotification
asyncoperation
attributeimageconfig
backgroundoperation
bot_botcomponent
bot_environmentvariabledefinition
botcomponent_aipluginoperation
botcomponent_connectionreference
botcomponent_environmentvariabledefinition
botcomponent_workflow
bulkdeletefailure
businessunit
callbackregistration
card
cardstateitem
catalogassignment
columnmapping
componentversion
componentversionnrddatasource
connectionreference
connectionroleassociation
copilotexamplequestion
copilotsynonyms
customapi
customapiresponseproperty
customcontroldefaultconfig
datalakefolder
datalakeworkspace
dataprocessingconfiguration
desktopflowbinary
displaystring
duplicaterulecondition
dvfilesearchattribute
dvtablesearch
dvtablesearchentity
entity
entitydataprovider
entityindex
entityrecordfilter
environmentvariabledefinition
eventexpanderbreadcrumb
expiredprocess
fabricaiskill
fieldpermission
fixedmonthlyfiscalcalendar
flowlog
flowmachinegroup
flowmachineimageversion
flowrun
goal
importentitymapping
importjob
importmap
interactionforemail
kbarticletemplate
lookupmapping
mainfewshot
managedproperty
metadataforarchival
mobileofflineprofileitem
mobileofflineprofileitemfilter
msdyn_aiconfiguration
msdyn_aitemplate
msdyn_componentlayer
msdyn_connectordatasource
msdyn_dataflow_datalakefolder
msdyn_dataflowtemplate
msdyn_dmsrequest
msdyn_entitylinkchatconfiguration
msdyn_insightsstorevirtualentity
msdyn_knowledgemanagementsetting
msdyn_mobileapp
msdyn_nonrelationalds
msdyn_pmanalysishistory
msdyn_pmcalendar
msdyn_pminferredtask
msdyn_pmprocesstemplate
msdyn_pmprocessversion
msdyn_pmtemplate
msdyn_salesforcestructuredobject
msdyn_schedule
msdyn_solutioncomponentcountdatasource
msdyn_solutioncomponentdatasource
msdyn_solutionhistory
msdyn_timelinepin
msdyn_workflowactionstatus
mspp_columnpermission
mspp_contentsnippet
mspp_entityformmetadata
mspp_entitypermission
mspp_pollplacement
mspp_publishingstate
mspp_redirect
mspp_sitemarker
mspp_webfile
mspp_webformmetadata
mspp_weblink
mspp_webpage
mspp_webrole
mspp_websiteaccess
mspp_webtemplate
newprocess
optionset
picklistmapping
pluginpackage
plugintype
powerbidataset
powerbireport
powerpagecomponent
powerpagesite
powerpageslog
principalentitymap
privilegesremovalsetting
processtrigger
publisheraddress
queue
recentlyused
recurringappointmentmaster
relationship
report
retaineddataexcel
ribbonmetadatatoprocess
roleeditorlayout
roletemplate
runtimedependency
savedqueryvisualization
sdkmessagefilter
sdkmessageprocessingstepimage
searchtelemetry
serviceendpoint
serviceplanappmodules
serviceplanmapping
sharedworkspaceaccesstoken
sharepointsite
sitemap
slaitem
solutioncomponent
solutioncomponentbatchconfiguration
solutioncomponentrelationshipconfiguration
subscriptionstatisticsoffline
synapsedatabase
synapselinkprofileentity
syncerror
systemuser
systemuserprofiles
teammobileofflineprofilemembership
teamroles
template
tracelog
transformationparametermapping
userform
userquery
virtualentitymetadata
webwizard
workflowbinary
workflowlog
workqueueitem

activityfileattachment
aicopilot_aiplugin
aipluginconversationstarter
aipluginexternalschema
aiplugininstance
aipluginoperationparameter
aiplugintitle
appaction
appactionmigration
appactionrule_webresource_scripts
appconfiginstance
applicationroles
applicationuserprofile
appmodule
appmoduleroles
appointment
attribute
attributemaskingrule
bot
bot_botcomponentcollection
botcomponent
botcomponent_botcomponent
botcomponent_dvtablesearch
botcomponent_msdyn_aimodel
botcomponentcollection
bulkdeleteoperation
calendar
canvasapp
cardentityconnections
catalog
channelaccessprofileentityaccesslevel
complexcontrol
componentversiondatasource
connectioninstance
connectionrole
connector
copilotglossaryterm
credential
customapirequestparameter
customcontrol
customcontrolresource
datalakefolderpermission
datalakeworkspacepermission
dependency
desktopflowmodule
duplicaterule
dvfilesearch
dvfilesearchentity
dvtablesearchattribute
elasticfileattachment
entityanalyticsconfig
entityimageconfig
entitykey
entityrelationship
environmentvariablevalue
exchangesyncidmapping
exportedexcel
featurecontrolsetting
fieldsecurityprofile
flowcredentialapplication
flowmachine
flowmachineimage
flowmachinenetwork
fxexpression
import
importfile
importlog
indexattributes
invaliddependency
keyvaultreference
mailmergetemplate
managedidentity
maskingrule
mobileofflineprofile
mobileofflineprofileitemassociation
monthlyfiscalcalendar
msdyn_aimodel
msdyn_appinsightsmetadata
msdyn_componentlayerdatasource
msdyn_dataflow
msdyn_dataflowconnectionreference
msdyn_datalakeds
msdyn_dmsrequeststatus
msdyn_helppage
msdyn_knowledgeassetconfiguration
msdyn_knowledgesearchfilter
msdyn_modulerundetail
msdyn_odatav4ds
msdyn_pmbusinessruleautomationconfig
msdyn_pmcalendarversion
msdyn_pmprocessextendedmetadataversion
msdyn_pmprocessusersettings
msdyn_pmrecording
msdyn_pmview
msdyn_salesforcestructuredqnaconfig
msdyn_slakpi
msdyn_solutioncomponentcountsummary
msdyn_solutioncomponentsummary
msdyn_solutionhistorydatasource
msdyn_tour
mspp_adplacement
mspp_columnpermissionprofile
mspp_entityform
mspp_entitylist
mspp_pagetemplate
mspp_powerpagescoreentityds
mspp_publishingstatetransitionrule
mspp_shortcut
mspp_sitesetting
mspp_webform
mspp_webformstep
mspp_weblinkset
mspp_webpageaccesscontrolrule
mspp_website
mspp_websitelanguage
navigationsetting
nlsqregistration
ownermapping
pluginassembly
plugintracelog
position
powerbimashupparameter
powerfxrule
powerpagecomponent_powerpagecomponent
powerpagesitelanguage
principalentitybusinessunitmap
privilege
processstage
publisher
quarterlyfiscalcalendar
queuemembership
recordfilter
recyclebinconfig
relationshipattribute
reportcategory
retentionconfig
role
roleprivileges
roletemplateprivileges
savedquery
sdkmessage
sdkmessageprocessingstep
searchresultscache
semiannualfiscalcalendar
serviceplan
serviceplancustomcontrol
sharedlinksetting
sharedworkspacenr
similarityrule
sla
solution
solutioncomponentattributeconfiguration
solutioncomponentconfiguration
solutionhistorydata
subscriptionsyncentryoffline
synapselinkprofile
synapselinkschedule
systemform
systemuserauthorizationchangetracker
systemuserroles
teamprofiles
teamtemplate
textanalyticsentitymapping
transformationmapping
translationprocess
usermobileofflineprofilemembership
userqueryvisualization
webresource
workflow
workflowcardconnections
workqueue

See also

Restore deleted Microsoft Dataverse table records (preview)