.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Issues with Orphaned XData Reappearing After Undo and Reopening Drawing

8 REPLIES 8
Reply
Message 1 of 9
fabian_bous
517 Views, 8 Replies

Issues with Orphaned XData Reappearing After Undo and Reopening Drawing

Hi everyone,I'm encountering an issue with managing XData in AutoCAD using .NET. Here's the scenario:

  1. I open a new AutoCAD drawing and create a rectangle.
  2. I run a custom method that adds two XData entries (KEY1 and KEY2) to the entity.
  3. I verify that the XData entries exist.
  4. I run the method again, which adds two more XData entries (KEY3 and KEY4).
  5. I verify that all four XData entries exist.
  6. I hit Ctrl+Z (Undo), and the last added XData entries (KEY3 and KEY4) are removed as expected.
  7. I save the drawing and close it.
  8. Upon reopening the drawing, I get the following message and the data is present again with the _APP0 / _APP1 Names

 

 

A registered application _APP0 was added to preserve orphaned entity xdata.
A registered application _APP1 was added to preserve orphaned entity xdata.

 

 

 

Here is the code I'm using:

 

 

 

public static void Test_XD()
    {
        var doc = _AcAp.DocumentManager.MdiActiveDocument;
        using var lockDocument = doc.LockDocument();
        using var tr = doc.TransactionManager.StartTransaction();

        var sel = doc.Editor.GetSelection(new PromptSelectionOptions() { SingleOnly = true });
        if (sel.Status != PromptStatus.OK) return;

        using var first = tr.GetObject(sel.Value.GetObjectIds().First(), OpenMode.ForWrite);


        using var xdata = first.XData ?? new();

        var key1 = "KEY1";
        var key2 = "KEY2";

        if (xdata.AsArray().Any(x => x.TypeCode == (int)DxfCode.ExtendedDataRegAppName && string.Equals(x.Value.ToString(), key1, StringComparison.OrdinalIgnoreCase)))
        {
            key1 = "KEY3";
            key2 = "KEY4";
        }

        var value1 = "value1";
        var value2 = "value2";

        void AddRegAppName(Transaction trans, string name)
        {
            using var regAppTable = (RegAppTable)trans.GetObject(Db.RegAppTableId, OpenMode.ForWrite);
            if (regAppTable.Has(name)) return;

            using var regApp = new RegAppTableRecord();
            regApp.Name = name;
            
            regAppTable.Add(regApp);
            trans.AddNewlyCreatedDBObject(regApp, true);
        }

        AddRegAppName(tr, key1);
        AddRegAppName(tr, key2);

        using var newXData = new ResultBuffer(
            new TypedValue((int)DxfCode.ExtendedDataRegAppName, key1),
            new TypedValue((int)DxfCode.ExtendedDataAsciiString, value1),
            new TypedValue((int)DxfCode.ExtendedDataRegAppName, key2),
            new TypedValue((int)DxfCode.ExtendedDataAsciiString, value2)
        );

        using var mergeXData = MergeXData(xdata, newXData);

        first.XData = mergeXData;

        tr.Commit();
    }

    private static ResultBuffer MergeXData(ResultBuffer existingXData, ResultBuffer newXData)
    {
        var existingTypedValues = existingXData.AsArray().Chunk(2).ToDictionary(x => x[0].Value.ToString(), x => x[1]);
        var newTypedValues = newXData.AsArray().Chunk(2).ToDictionary(x => x[0].Value.ToString(), x => x[1]);

        // Merge existing and new XData, avoiding duplicates
        var mergedTypedValues = existingTypedValues
            .Concat(newTypedValues)
            .GroupBy(x => x.Key)
            .Select(x => x.First())
            .SelectMany(x => new[] { new TypedValue((int)DxfCode.ExtendedDataRegAppName, x.Key), x.Value })
            .ToArray();
        return new ResultBuffer(mergedTypedValues);
    }

 

 

 

Despite registering the application names before adding XData, I still encounter orphaned XData upon reopening the drawing. Has anyone experienced similar issues or have insights on how to resolve this?

 

Additional Information:

  • AutoCAD Version: AutoCAD 2025.0.1 (V.72.0.0)
  • .NET Framework: .net8.0
  • Steps to Reproduce: Detailed above
8 REPLIES 8
Message 2 of 9

My guess (and that's all it is) is that you need to register the appids in a separate transaction prior to the one that you use to add the xdata.

 

Another possible problem is:

 

 

regAppTable.Has(name)

 

 

 

This will return true, if there is an erased entry in the regapp table with the given name.

 

This is a long-standing problem that I attribute to irrational API design. But, it is what it is and don't expect it to change.  You can't rely on Has() alone because of that reason.

 

For longer than I can remember, my own code has used the following APIs. They catch the exception thrown by the indexer if the entry is either erased or doesn't exist, and return ObjectId.Null or false.

 

 

public static ObjectId TryGetRecordId(this SymbolTable table, string key)
{
   try
   {
      return table[key];
   }
   catch(AcRx.Exception)
   {
   }
   return ObjectId.Null;
}

public static bool Contains(this SymbolTable table, string key)
{
   try
   {
      return !table[key].IsErased;
   }
   catch(AcRx.Exception)
   {
   }
   return false;
}

 

 

 

Message 3 of 9
fabian_bous
in reply to: fabian_bous

Hey @ActivistInvestor,

 

Thanks for your reply and the extension methods. I think I tested your solution (please correct me if I am wrong).
Unfortunately the code attached produces the same result:

 

 

public static void Test_XD()
    {
        var key1 = "KEY1";
        var key2 = "KEY2";

        ObjectId firstId;
        
        var doc = _AcAp.DocumentManager.MdiActiveDocument;
        using (var lockDocument = doc.LockDocument())
        using (var tr = doc.TransactionManager.StartTransaction())
        {
            var sel = doc.Editor.GetSelection(new PromptSelectionOptions() { SingleOnly = true });
            if (sel.Status != PromptStatus.OK) return;

            firstId = sel.Value.GetObjectIds().First();
            
            using var first = tr.GetObject(firstId, OpenMode.ForWrite);
            
            using var xdata = first.XData ?? new();

            

            if (xdata.AsArray().Any(x => x.TypeCode == (int)DxfCode.ExtendedDataRegAppName && string.Equals(x.Value.ToString(), key1, StringComparison.OrdinalIgnoreCase)))
            {
                key1 = "KEY3";
                key2 = "KEY4";
            }
            
            tr.Abort();
        }


        AddRegAppName(key1);
        AddRegAppName(key2);

        var value1 = "value1";
        var value2 = "value2";
        
        using var newXData = new ResultBuffer(
            new TypedValue((int)DxfCode.ExtendedDataRegAppName, key1),
            new TypedValue((int)DxfCode.ExtendedDataAsciiString, value1),
            new TypedValue((int)DxfCode.ExtendedDataRegAppName, key2),
            new TypedValue((int)DxfCode.ExtendedDataAsciiString, value2)
        );

        using (var lockDocument = doc.LockDocument())
        using (var tr2 = doc.TransactionManager.StartTransaction())
        {
            using var first = tr2.GetObject(firstId, OpenMode.ForWrite);
            using var xdata = first.XData ?? new();

            using var mergeXData = MergeXData(xdata, newXData);

            first.XData = mergeXData;

            tr2.Commit();
        }
    }
    
    static void AddRegAppName(string name)
    {
        var doc = _AcAp.DocumentManager.MdiActiveDocument;
        using var lockDocument = doc.LockDocument();
        using var tr = doc.TransactionManager.StartTransaction();
        
        using var regAppTable = (RegAppTable)tr.GetObject(Db.RegAppTableId, OpenMode.ForWrite);
        if (regAppTable.Contains(name)) return;

        using var regApp = new RegAppTableRecord();
        regApp.Name = name;
            
        regAppTable.Add(regApp);
        tr.AddNewlyCreatedDBObject(regApp, true);
        
        tr.Commit();
    }

    private static ResultBuffer MergeXData(ResultBuffer existingXData, ResultBuffer newXData)
    {
        var existingTypedValues = existingXData.AsArray().Chunk(2).ToDictionary(x => x[0].Value.ToString(), x => x[1]);
        var newTypedValues = newXData.AsArray().Chunk(2).ToDictionary(x => x[0].Value.ToString(), x => x[1]);

        // Merge existing and new XData, avoiding duplicates
        var mergedTypedValues = existingTypedValues
            .Concat(newTypedValues)
            .GroupBy(x => x.Key)
            .Select(x => x.First())
            .SelectMany(x => new[] { new TypedValue((int)DxfCode.ExtendedDataRegAppName, x.Key), x.Value })
            .ToArray();
        return new ResultBuffer(mergedTypedValues);
    }

 

 

On another note: given the fact we are using a brand new drawing, the SymbolTable.Has(string) returning true for erased records should not be an issue here. But in production this might be a game changer.

 

Message 4 of 9
Izhar_Azati
in reply to: fabian_bous

It seems to me that you are using XDATA incorrectly
RegAppTable contains a list of data structure "templates" names that you want to save.
Only you know the "structure" of the data you store there.
AutoCAD knows how to retrieve and write your data that is defined at the beginning by the particular RegApp and ends at the end of the data or another RegApp.

Message 5 of 9
fabian_bous
in reply to: Izhar_Azati

My knowledge about XData is:
- I want to add a XData Entry in the ResultBuffer object containing a Key and a Value
- I first need to register the RegAppName in the RegAppTable (if not already done so)
- I can add the corresponding entries to the ResultBuffer

In which way is it different to the code snippet above?
Message 6 of 9
Izhar_Azati
in reply to: fabian_bous

You should use GetXDataForApplication and not take all the XData !

The information that other apps have written should not interest you.

Message 7 of 9
fabian_bous
in reply to: Izhar_Azati

This does in no way explain the behaviour of AutoCAD. In my example I created a new rectangle as you can read in my first statement. There is no xData attached to the entity before my method. GetXDataForApplication has nothing to do with the fact that AutoCAD seemingly is corrupting the xData of this Entity when Ctrl+Z (Undo) is hit.
Message 8 of 9

At this point I would suggest that you submit a reproducible case showing the bug.

Message 9 of 9
Izhar_Azati
in reply to: fabian_bous

In my opinion ResultBuffer should contain only one DxfCode.ExtendedDataRegAppName at the top,

and at placement time

 

aaa.XData=rb;

 

The software (acad) manages the pointers only for the first RegAppName.

I have only been working with XData for about 30 years.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

AutoCAD Inside the Factory


Autodesk Design & Make Report