Hi everyone,I'm encountering an issue with managing XData in AutoCAD using .NET. Here's the scenario:
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:
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;
}
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.
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.
You should use GetXDataForApplication and not take all the XData !
The information that other apps have written should not interest you.
At this point I would suggest that you submit a reproducible case showing the bug.
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.