Migrate document attachments to AX7


The Archive directory, Database and the Original location options for storing file attachments in the "New Dynamics AX" aka "Dynamics AX Online" aka AX7 are depreciated in favor of the Azure Storage or SharePoint. The option Archive directory (see Organization administration > Document management > Document types) now designates the Azure Blob Storage, the Database option is present but dysfunctional, and the Original location disappeared altogether.

What if you have to migrate existing attachments from an AX 2009 or AX 2012 system and leave them operational i. e. viewable from the original record?

In the cases Archive directory or Original location you are in deep trouble. You may try to copy the files to the Azure Storage by means of the Azure Storage Explorer, but the name of the file must be converted into a deterministic GUID, and a link to this GUID has to be re-established in the DocuValue table. It also seems that the DLL managing files in Azure for AX7 relies on some metadata in the blob, and fails to produce a download URL from a faked GUID. I suggest writing a script in the legacy AX 2012 to absorb the external files to the database first.

For the “Database”-stored attachments there is [yet] no official upgrade path, but the below runnable class can quickly dump the files preserved in the DocuValue table to the Azure Storage through a memory stream, and rejuvenate the DocuValue table with the GUID and a link to the blob. The script also updates the Location in all file document types from Database to Archive directory (meaining the Azure Storage).

class DocuFileMigrateDBtoAzure
{
    public static void main(Args _args)
    {
        DocuValue           docuValue;
        DocuRef             docuRef;
        DocuType            docType;
        System.IO.Stream    fileStream;
        Microsoft.Dynamics.AX.Framework.FileManagement.DocumentLocation         location;
        Microsoft.Dynamics.AX.Framework.FileManagement.IDocumentStorageProvider storageProvider;
        DataArea            dataArea;
        ;
        while select dataArea
        {
            changecompany(dataArea.id)
            {
                docType = null;
                docType.skipTTSCheck(true);
                while select forupdate docType
                    where docType.TypeGroup == DocuTypeGroup::File
                {
                    if (docType.FilePlace == DocuFilePlace::Database)
                    {
                        docType.FilePlace = DocuFilePlace::Archive;
                        docType.update();
                    }
                    storageProvider = Docu::GetStorageProvider(docType, false, curUserId());
                    if (storageProvider == null)
                        continue;
                    while select forupdate docuValue
                        where docuValue.StorageProviderId == 0
                           && docuValue.Type == DocuValueType::Others
                    join docuRef
                        where docuRef.ValueRecId      == docuValue.RecId
                           && docuRef.ActualCompanyId == docType.DataAreaId
                           && docuRef.TypeId          == docType.TypeId
                    {
                        fileStream = Binary::constructFromContainer(docuValue.File).getMemoryStream();
                        if (fileStream.Length == 0)
                            continue;
                        docuValue.FileId = newGuid();
                        location = storageProvider.SaveFile(
                                docuValue.FileId,
                                storageProvider.GenerateUniqueName(docuValue.OriginalFileName),
                                System.Web.MimeMapping::GetMimeMapping(docuValue.OriginalFileName),
                                fileStream);
                        fileStream.Close();
                        if (location == null)
                            continue;
                        if (location.NavigationUri)
                        {
                            docuValue.Path = location.get_NavigationUri().ToString();
                        }
                        if (location.AccessUri)
                        {
                            docuValue.AccessInformation = location.get_AccessUri().ToString();
                        }
                        docuValue.StorageProviderId = storageProvider.ProviderId;
                        docuValue.update();
                    }
                }
            }
        }
    }
}
Comments (3)

  1. Toby says:

    Hello,

    Is the GetStorageProvider method a standard AX method?

    If not can I see a copy of the code for it?

    Also, Microsoft.Dynamics.AX.Framework.FileManagement.DocumentLocation
    Microsoft.Dynamics.AX.Framework.FileManagement.IDocumentStorageProvider

    What version of AX are these objects in? I'm working on 2012 R2.

    Thanks!

    1. Eugen Glasow says:

      Hi Toby, the GetStorageProvider is a standard method... in AX7 🙂
      Microsoft.Dynamics.AX.Framework.FileManagement.XXX are closed code assemblies / DLLs.

  2. Madhan says:

    Hi Eugen,

    Thanks for the post.

    It would be grateful if you can help me to solve the below,

    I have some documents in my local drive. If i need to attach those documents to SharePoint online in one go by code means , how i can achieve. This requirement is in AX7.

    I did the above in 2012. Since the architecture is changed in AX7 , i am not able to proceed further. Could you please help.

    Regards,
    Madhan

Skip to main content