web
You’re offline. This is a read only version of the page.
close
Skip to main content

Announcements

News and Announcements icon
Community site session details

Community site session details

Session Id :
Microsoft Dynamics AX (Archived)
Active Discussion

BUG: Print Archive delete cascades to wrong DocuRef records

(0) ShareShare
ReportReport
Posted on by 17,790

I noticed tonight that I had a larger number of needless Print archive records, i.e. PrintJobHeader, so I thought I would do some cleanup within the UX.

As I was deleting records, I wondered where the actual printed image (probably a PDF) was stored.  I assumed DocuRef/DocuValue, but checked anyway.  PrintJobHeader is a hidden table in the AOT, so I checked the SysPrintArchive form, which has a PrintJobHeader datasource, and surprisingly a delete() data source method.  That method has the following code.

public void delete()
{
    DocuRef     docuRef;
    DocuValue   docuValue;
    RefRecId    docuRefId;
    SysWindowsAppReportsMapping reportsMapping;

    ttsbegin;
    select forupdate docuRef where docuRef.RefRecId == PrintJobHeader.RecId;
    if (docuRef)
    {
        docuRefId = docuRef.ValueRecId;
        // If print archive is archived to a file share, delete the archived file
        docuRef.delete();
        delete_from docuValue where docuValue.RecId == docuRefId;
        ttscommit;
    }

    ttsBegin;
    delete_from reportsMapping
        where reportsMapping.PrintJobHeaderRecId == PrintJobHeader.RecId &&
              reportsMapping.PrintJobHeaderDataAreaId == PrintJobHeader.dataAreaId;
    ttsCommit;
    super();
}

The thing to notice is the select forupdate statement where DocuRef.RefRecId == PrintJobHeader.RecId, followed shortly afterward by a delete.

That's right, just RefRecId.  Not RefTableId.  Not RefCompanyId.  Just RefRecId.

Of course, there may be MANY records that share a given RefRecId, so how does it guarantee that it deletes the correct one?

Let's test that.

Here's a PrintJobHeader record that refers to 4 different DocuRef records, when joined by DocuRefId only.

PrintJobHeader-Delete-Before.jpg

From the UX for SysPrintArchive, I deleted the PrintJobHeader record, and then found this.

Indeed, it deleted the WRONG DocuRef record.  The one with RefTableId 65525 is properly related to PrintJobHeader, and it survives, and meanwhile a DocuRef record related to table 62 (CustInvoiceJour) is now deleted.

PrintJobHeader-Delete-After.JPG

What a mind-numbingly stupid programmatic mistake and bug.

*This post is locked for comments

  • Brandon Wiese Profile Picture
    17,790 on at

    And the other thing I just noticed by merely glancing at the code is that the ttsbegin occurs outside the if (docuref) statement, but the ttscommitt occurs inside.

    Thus, if for some odd reason you have no DocuRef record attached, you will get an imbalanced tts.

    Finally, as if it matters at this point, the effort to delete the docuValue record is fully pointless because docuRef.delete() automatically does it.

  • Brandon Wiese Profile Picture
    17,790 on at
    
    

    So here is one fix to the problem.

    In form SysPrintArchive, data source PrintJobHeader, method delete(), the highlighted code does the correct job.

    public void delete()
    {
        DocuRef     docuRef;
        DocuValue   docuValue;
        RefRecId    docuRefId;
        SysWindowsAppReportsMapping reportsMapping;
    
        ttsbegin;
        delete_from docuRef
            where docuRef.RefRecId == PrintJobHeader.RecId &&
                  docuRef.RefTableId == PrintJobHeader.TableId &&
                  docuRef.RefCompanyId == '' &&
                  docuRef.ActualCompanyId == PrintJobHeader.dataAreaId;
        ttscommit;
    
        ttsBegin;
        delete_from reportsMapping
            where reportsMapping.PrintJobHeaderRecId == PrintJobHeader.RecId &&
                  reportsMapping.PrintJobHeaderDataAreaId == PrintJobHeader.dataAreaId;
        ttsCommit;
        super();
    }

    Note that RefCompanyId == '', which seems strange at first that it should be blank on those records, but it is.
    The reason that RefCompanyId is blank is thanks to the following code in class SRSPrintArchiveContract method createDocuRef.  Notice how ActualCompanyId is set, but RefCompanyId is not.  While it may seem trivial, RefCompanyId is properly indexed, and ActualCompanyId is not, and thus adding RefCompanyId == '' to the delete_from above improves the performance tremendously to a millisecond response time.  And now I know why deleting 100 Print archive records took 5 minutes instead of a couple of seconds.

    private docuRef createDocuRef(RefRecId _refPrintJob)
    {
        DocuRef     docuRef;
    
        docuRef.ActualCompanyId = curext();
        docuRef.Author = DirPersonUser::current().PersonParty;
        docuRef.RefRecId = _refPrintJob;
        docuRef.RefTableId = tablenum(PrintJobHeader);
        docuRef.TypeId = #SRSArchiveDocument;
        docuRef.doInsert();
        return docuRef;
    }
  • Brandon Wiese Profile Picture
    17,790 on at

    And here I thought we were done.

    In class SRSPrintArchiveContract method savePrintArchiveDetails, we find the following code.  In this case, the catch {} occurs should there be any issue attaching the file to the DocuValue record, and if so attempts to clean up by deleting the DocuRef record.  But, once again it (the highlighted code) indiscriminately does a delete_from where RefRecId == docuRefId;  The worst part of this calamity is that you can simply do a docuRef.delete().

    public void savePrintArchiveDetails()
    {
        DocuActionSSRSReport    docuArchive = new DocuActionSSRSReport();
        DocuRef                 docuRef;
        PrintJobHeader          printJobHeader;
        RecId                   docuRefId;
    
    
        docuRef = this.createDocuRef(this.createPrintJobHeader());
    
        try
        {
            if (docuRef)
            {
                ttsbegin;
                docuArchive.add(docuRef, this.parmTmpFilePath());
                ttscommit;
            }
        }
        catch
        {
            // Abort transaction
            ttsAbort;
            if (!docuArchive.parmArchiveFileCreated())
            {
                ttsbegin;
                docuRefId = docuRef.RefRecId;
                // Clear docuRef as it was selected for update.
                docuRef.clear();
                delete_from printJobHeader where printJobHeader.RecId == docuRefId;
                delete_from docuRef where docuRef.RefRecId == docuRefId;
                ttscommit;


Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Season of Sharing Community Challenge Launch!

Jump in, show your community spirit, and win prizes!

Women in Power Builds Momentum

Expanding mentorship, skilling, and AI innovation

Congratulations to the May Top 10 Community Leaders

These are the community rock stars!

Leaderboard > 🔒一 Microsoft Dynamics AX (Archived)

#1
Sanhthosh.Kumar.K Profile Picture

Sanhthosh.Kumar.K 2

#2
Raed Salah Bzour Profile Picture

Raed Salah Bzour 1

Last 30 days Overall leaderboard

Featured topics

Product updates

Dynamics 365 release plans