Datafixes in D365 Finance and Supply Chain Management: Common Solutions

By Deovandski Skibinski | May 29, 2025

When working in Dynamics 365 Finance and Supply Chain Management, there are several methods you can use to address data issues.

However, choosing and using the correct tool for a datafix can be a challenge, as it is often tempting to bring in a developer and have force through doUpdate(). This method, while maybe a bit easier, can be costly and oftentimes isn't necessary or recommended.

In this blog, we will guide you through various tools you can use to solve common pitfalls within your system.

1. Data Maintenance Portal

You can find this tool under System Administration > Period Tasks > Data Maintenance.

Using this portal takes a more proactive approach to data maintenance, focusing on performance and data integrity. Certain issues with caching, auditing, and missing source data can be resolved here as well. It can also be a good starting point if you're noticing performance issues or need to trim storage.

If there are certain issues that are scanned but not found, they will be reported to Microsoft.

D365 Datafixes Data Maintenance Portal

2. Consistency Check

You can use this feature by navigating to System Administration > Period Tasks > Database > Consistency Check

This dialog-driven runnable class should always be evaluated first as a way of fixing corruption or misalignment data issues before getting a developer involved. However, this tool is less effective the more customized your system is. That's why it's important to ensure you always run Check first before running the fix option. Runtimes can be long and intensive on your AOS as well, so not recommended to run during high-activity periods.

Datafixes Dynamics 365 F&SCM Consistency Check

An additional tip for this tool is you can filter down records under certain conditions.

Consistency Check Filter Down Certain Conditions

Inventory Tables Datafixes in D365 F&SCM

Filter down to production orders

Datafixes Dynamics 365 F&SCM Inquiry Production Orders

3. Custom Scripts

Found under System Administration > Period Tasks > Database > Custom Scripts

This tool lets you run once datafixes without the need for deployment, making it ideal for critical issues that require immediate resolution. However, there are significant caveats associated with its usage.

Custom Scripts

Custom scripts are suitable only for minor fixes, with runtimes lasting seconds at most due to the significant overhead that can extend runtimes to minutes. If too much is attempted in a single data fix, it will "run" until a timeout occurs, resulting in no changes. Additionally, many tables are inaccessible to custom scripts, such as Framework and system tables. While it is technically possible to use a flight to enable a specific table in Dev & UAT, only Microsoft can authorize flight changes in production environments.

Datafixes in Dynamics 365 Finance and Supply Chain Management

Another issue to consider is traceability. The datafix source code is only available on the development box where it was generated, and if your company lacks proper documentation procedures, this code can easily be lost, complicating debugging efforts later if a revisit is needed. Therefore, it is essential to save the source code in DevOps or another reliable location. Alternatively, use SysclassRunner through a changeset. (More on this later)

If your custom script is modifying base tables, it is advisable to select these models from the outset as references for the new datafix model. This approach helps prevent most build issues related to missing references and the need to continuously update model parameters such as:

  • Retail
  • Directory
  • Application Suite
  • Application Foundation
  • SourceDocumentation
  • SourceDocumentationTypes
  • ContactPerson
  • Ledger
  • FiscalBooks
  • Tax

It is recommended to create your datafix model with versioning appended to it such as "CAS005142V1" because once you upload the datafix to a given environment, you cannot reupload another datafix using the same model or you will get odd errors.

4. SysclassRunner (extending RunBase)

The simplest way to set up a runnable class, unless you need to update thousands of tables or run an intensive process. In such cases, you should use RunBaseBatch instead. The example below includes a dialog as a "speedbump" requiring the user to click "OK" before proceeding. Additionally, we have incorporated a sample dialog field. It is recommended that once the datafix is completed, you should create a rollback of the changeset in question.

Datafixes SysclassRunner extending RunBase

To run this class, you can access it through the SysClassRunner: //?mi=sysclassrunner&cls=TestClass&cmp=TEST

Here is the full code sample:

internal final class TestClass extends RunBase
{        
    DialogField parmInventSiteId;
    InventSiteId inventSiteId;


    #DEFINE.CurrentVersion(1)
    #LOCALMACRO.CurrentList
        inventSiteId
    #ENDMACRO


    public Object dialog()
    {
        #SysOperation
       
        DialogRunbase dialog = super();
        parmInventSiteId = dialog.addFieldValue(extendedTypeStr(InventSiteId), inventSiteId);
        dialog.allowUpdateOnSelectCtrl(true);
       
        return dialog;
    }
    public InventSiteId parmInventSiteId(InventSiteId _inventSiteId = inventSiteId)
    {
        inventSiteId = _inventSiteId;
        return inventSiteId;
    }
    public boolean getFromDialog()
    {
        inventSiteId = parmInventSiteId.value();


        return super();
    }


    public static void main(Args _args)
    {
        TestClass runBase;
        runBase = new TestClass();
        if(runBase.prompt())
        {
            runBase.run();
        }
    }


    public void run()
    {
        //Debugging helpers
        //?mi=sysclassrunner&cls=TestClass&cmp=TEST
        int recCount = 0;
        // Insert Processing code here
        Info(strFmt("Done. %1 processed records.",recCount));
    }


    public container pack()
    {
        return [#CurrentVersion,#CurrentList];
    }


    public boolean unpack(container _packedClass)
    {
        Version version = RunBase::getVersion(_packedClass);
        switch(version)
        {
            case(#CurrentVersion):
    [version,#CurrentList] = _packedClass;
                break;
            default:
                return false;
        }
        return true;
    }


}

5 - Sysclass Runner (extending RunBaseBatch)

For longer running classes, then RunBaseBatch is the way to go. The example below also has Query-based filtering backed in to allow data slicing through multiple batch jobs. To deactivate it, just remove the related code, or deactivate showQueryValues and show the QuerySelectButton.

It is recommended that once the datafix is completed, you delete the batch jobs, and then create a rollback of the changeset in question.

Sysclass Runner extending RunBaseBatch Batch Tasks

Datafixes SysclassRunner test batch class

Here is the code:

class TestBatch extends RunBaseBatch implements BatchRetryable
{        
    QueryRun  queryRun;
    private str DummyVar;

    #DEFINE.CurrentVersion(1)
    #LOCALMACRO.CurrentList
     DummyVar
    #ENDMACRO

    public static TestBatch construct()
     {
         return new TestBatch();
     }

     public static void main(Args args)
     {
         TestBatch runner = TestBatch::construct();
         runner.parmInBatch(true);

         if (runner.prompt())
         {
             runner.runOperation();
         }
     }

     public void run()
     {
         System.Exception ex;
         str infoLogMessages = ". ";
         int counterOK = 0, counterFail = 0;
 
         while (queryRun.next())
         {
             SalesTable salesTable;
             try
             {
                 salesTable   = queryRun.get(tableNum(SalesTable));
                 // SalesTable TODO
                 ++counterOK;
             }
             catch (ex)
             {
                 ++counterFail;
                 infoLogMessages = this.extractErrorMessage(ex);
                 error(strFmt("Unable to process %1 - %2",salesTable.SalesId, infoLogMessages));
               
             }
         }
       
         info(strFmt("Done. %1 Processed. %2 Failed", counterOK, counterFail));
     }

     // Boilerplate/structural code
         
     void new()
     {
         super();

         queryRun = new QueryRun(this.buildQuery());
     }

     private void prepareQuery()
     {
         if (!this.validate())
         {
             throw error("@SYS18447");
         }
     }

     public QueryRun queryRun()
     {
         return queryRun;
     }

     Query buildQuery()
     {
         Query                query;
         QueryBuildRange      qbr;
         QueryBuildDataSource salesDs;

         query = new Query();
         salesDs = query.addDataSource(tableNum(SalesTable));
         qbr = salesDs.addRange(fieldnum(SalesTable, SalesStatus));
         qbr.value(strFmt('((%1 = %2) || (%3 = %4))',
            fieldId2Name(tableNum(SalesTable), fieldNum(SalesTable, SalesStatus)),
            enum2int(SalesStatus::None),
            fieldId2Name(tableNum(SalesTable), fieldNum(SalesTable, SalesStatus)),
            enum2int(SalesStatus::Backorder)));
         qbr.status(RangeStatus::Locked);


         return query;
     }

     public boolean getFromDialog()
     {
         return super();
     }

     public Object dialog()
     {
         #SysOperation
       
         DialogRunbase dialog = super();
       
         return dialog;
     }

     private str extractErrorMessage(System.Exception ex)
     {
         if(ex != null)
         {
             return ex.Message;
         }
         // Fallback to at least try to capture a CLRError.
         else
         {
             System.Exception ex1 = CLRInterop::getLastException();
             if(ex1 != null)
             {
                 return ex.Message;
             }
         }
         return "Unable to capture error.";
     }

     public boolean showQueryValues()
     {
         return true;
     }

     public boolean showQuerySelectButton()
     {
         return true;
     }

     [Hookable(false)]
     final boolean isRetryable()
     {
         return true;
     }

     public static ClassDescription description()
     {
         return "Test Batch Class";
     }

     protected boolean canRunInNewSession()
     {
         return true;
     }

     public boolean canGoBatch()
     {
         return true;
     }

     public boolean canGoBatchJournal()
     {
         return true;
     }

     public container pack()
     {
         return [#CurrentVersion, queryRun.pack(), #CurrentList];
     }

     public Query parmQuery(Query _query = null)
     {
         if (!prmisDefault(_query))
         {
             queryRun = new QueryRun(_query);
         }

         return queryRun.query();
     }

     private boolean checkPackedQuery(container _packedQuery)
     {
         boolean ret;
         if (!_packedQuery)
         {
             ret = false;
         }
         else
         {
             ret = true;
             queryRun = new QueryRun(_packedQuery);
         }
         return ret;
     }

     public boolean unpack(container _packedClass)
     {
         Version     version = RunBase::getVersion(_packedClass);
         boolean     ret = true;
         container   packedQuery;

         switch (version)
         {
             case #CurrentVersion:
                 [version, packedQuery, #CurrentList] = _packedClass;
                 break;
             default:
                 ret = false;
         }

         if (ret)
         {
             ret = this.checkPackedQuery(packedQuery);
         }
       
         return ret;
     }

}

Need Additional Assistance with Datafixes in Dynamics 365 Finance and Supply Chain Management?

Talk to the Stoneridge team of experts! We can help you and your team address common data errors and come up with smart solutions to ensure your software systems are running as smoothly as possible.

Related Posts


Under the terms of this license, you are authorized to share and redistribute the content across various mediums, subject to adherence to the specified conditions: you must provide proper attribution to Stoneridge as the original creator in a manner that does not imply their endorsement of your use, the material is to be utilized solely for non-commercial purposes, and alterations, modifications, or derivative works based on the original material are strictly prohibited.

Responsibility rests with the licensee to ensure that their use of the material does not violate any other rights.

Start the Conversation

It’s our mission to help clients win. We’d love to talk to you about the right business solutions to help you achieve your goals.

Subscribe To Our Blog

Sign up to get periodic updates on the latest posts.

Thank you for subscribing!