I have this extension method that adds attributes to a BlockReference. Its's a variation of Giles' extension posted here.
/// <summary>
/// Extension method that adds <see cref="AttributeReference"/>'s to a <see cref="BlockReference"/>.
/// AttributeReference properties are taken from the block being inserted.
/// </summary>
/// <returns>Void.</returns>
public static void AddAttributeReferences(this BlockReference target)
{
if (target == null)
throw new ArgumentNullException("target");
Transaction tr = target.Database.TransactionManager.TopTransaction;
if (tr == null)
throw new AcRx.Exception(ErrorStatus.NoActiveTransactions);
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(target.BlockTableRecord, OpenMode.ForRead);
if (btr.HasAttributeDefinitions)
{
foreach (AttributeDefinition attDef in btr.AttributeDefinitions())
{
if (!attDef.Constant)
{
using (AttributeReference attRef = new AttributeReference())
{
attRef.SetAttributeFromBlock(attDef, target.BlockTransform);
attRef.Position = attDef.Position.TransformBy(target.BlockTransform);
attRef.TextString = attDef.TextString;
target.AttributeCollection.AppendAttribute(attRef);
tr.AddNewlyCreatedDBObject(attRef, true);
}
}
}
}
}
It is being called by a sub that inserts a block. If I comment out the line to add the attributes, the block inserts fine. If I try to run AppendAttributeReferences(), it runs without error, but then I get this unhandled error in acad.exe when the calling sub trys to commit the transaction. Here is the stack trace.
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at Autodesk.AutoCAD.DatabaseServices.Transaction.CheckTopTransaction()
at Autodesk.AutoCAD.DatabaseServices.Transaction.DeleteUnmanagedObject()
at Autodesk.AutoCAD.Runtime.DisposableWrapper.!DisposableWrapper()
at Autodesk.AutoCAD.Runtime.DisposableWrapper.Dispose(Boolean )
at Autodesk.AutoCAD.Runtime.DisposableWrapper.Dispose()
at TID_.TID_Commands.TitleBlockInsert()
at Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorker(MethodInfo mi, Object commandObject, Boolean bLispFunction)
at Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorkerWithExceptionFilter(MethodInfo mi, Object commandObject, Boolean bLispFunction)
at Autodesk.AutoCAD.Runtime.PerDocumentCommandClass.Invoke(MethodInfo mi, Boolean bLispFunction)
at Autodesk.AutoCAD.Runtime.CommandClass.CommandThunk.Invoke()
Solved! Go to Solution.
Solved by _gile. Go to Solution.
If I comment out the call to this method, the block inserts fine. Its only when I try to add the atts that I get the error. I thought it may be due to the state of the BlockReference being added to so I stuck in an UpgradeOpen prior to calling AppendAttributeReferences, but that didn't help either. The calling method is itself an extension method, but the transaction was started with StartTransaction. I also tried replacing the call to the TopTransaction with a new one. This didn't help either. The atts get added to the blockref, but then I can't finish adding the blockref. I'm still trying to trace down what transaction is being used.
It looks like there is no active transaction when you run the extension method. I would check to make sure that you have not already commited the transaction as the previous poster already mentioned.
Keith, I was able to find out that there are 2 transactions going at the time. AppendAttributeReferences is able to get the top transaction and complete. In debug mode, the debugger doesn't show very much info on transactions. I don't know which transaction is which or whether its closed or open. I only know that there are 2. Still working on this to see what else I can find out. Thanks.
Is it possible to have 2 sequential transactions in the same command? i.e. the first executes and is committed then the second is started and then committed? I know you can have nested transactions. Due to some helper delegate functions I wrote for handling transactions, what is happening is that in the first transaction, the block gets imported from a file and then in the second transaction, the method to add the AttributeReferences nees to modify the BlockReference just created.
I've refactored the calling method so that everything happens in one transaction, but I still get the same error on the line to commit the transaction. Here is the calling method. UsingCurrentSpace is a delegate that starts/ends a transaction and passes a BlockTableRecord of the current space. If I comment out the line to add the atts, this runs fine. The error message refers to the state of an object but doesn't say what object.
public static BlockReference InsertFileAsBlock(string sourceFilePath, BlockInfo BI, string BlockName) { try { ObjectId btrId = ImportFileAsBlock(sourceFilePath, BlockName); BlockReference blkr = new BlockReference(BI.Position, btrId); blkr.Rotation = BI.Rotation; blkr.ScaleFactors = BI.ScaleFactors; Active.Database.UsingCurrentSpace( (Transaction tr, BlockTableRecord btr) => { btr.AppendEntity(blkr); tr.AddNewlyCreatedDBObject(blkr, true); blkr.AddAttributeReferences(tr); } ); return blkr; } catch { throw; }
Hi,
Did you try in a more classical way, without your transaction helper.
By my side, I use a quite large library of Helpers (mainly extension methods) but none to wrap the Open / Dispose transaction mechanism, I'd rather explicitly write it.
public static ObjectId InsertFileAsBlock(string sourceFilePath, BlockInfo BI, string BlockName) { ObjectId brId = ObjectId.Null; ObjectId btrId = ImportFileAsBlock(sourceFilePath, BlockName); if (btrId != ObjectId.Null) { using (Transaction tr = btrId.Database.TransactionManager.StartTransaction()) { blkr = new BlockReference(BI.Position, btrId) { Rotation = BI.Rotation, ScaleFactors = BI.ScaleFactors } brId = btr.AppendEntity(blkr); tr.AddNewlyCreatedDBObject(blkr, true); blkr.AddAttributeReferences(tr); tr.Commit() return blkr; } } return brId; }
One other thing, It's unsafe to return a DBObject which have been disposed: in your code, blkr is disposed when the transaction it has been added to is diposed, so your InsertFileAsBlock function returns a disposed object.
Return an ObjectId instead, as shown upper, or rewrite the function so that it uses a top transaction from where it is called.
@_gile wrote:Did you try in a more classical way, without your transaction helper.
Yes, but I still had the error so I put it back.
@_gile wrote:One other thing, It's unsafe to return a DBObject which have been disposed: in your code, blkr is disposed when the transaction it has been added to is diposed, so your InsertFileAsBlock function returns a disposed object.
Return an ObjectId instead, as shown upper, or rewrite the function so that it uses a top transaction from where it is called.
Thanks for catching that. As part of this troubleshooting, I tried something and forgot to change it back.
I used your last code. I had to clean it up a little and add a line to get the CurrentSpace. But I still get the error. Here is what I have now.
public static ObjectId InsertFileAsBlock(string sourceFilePath, BlockInfo BI, string BlockName) { ObjectId NewBlkRefId = ObjectId.Null; ObjectId NewBlkId = ImportFileAsBlock(sourceFilePath, BlockName); if (NewBlkId != ObjectId.Null) { using (Transaction tr = NewBlkId.Database.TransactionManager.StartTransaction()) { BlockTableRecord btrCurrentSpace = (BlockTableRecord)tr.GetObject(NewBlkId.Database.CurrentSpaceId, OpenMode.ForWrite); BlockReference brNewBlock = new BlockReference(BI.Position, NewBlkId) {Rotation = BI.Rotation, ScaleFactors = BI.ScaleFactors }; NewBlkRefId = btrCurrentSpace.AppendEntity(brNewBlock); tr.AddNewlyCreatedDBObject(brNewBlock, true); brNewBlock.AddAttributeReferences(tr); tr.Commit(); } } return NewBlkRefId; }
Again, this works fine if I comment out the line to add the atts. The block gets inserted, but without atts. Thanks for sticking with this problem.
@SENL1362 wrote:
So the UsingCurrentSpace create and return the Transaction?
I don't see the ByRef argument, so the Transaction might not be returned.
Its not really "returning" a transaction. Its working in the context of a delegate. The transaction isn't the problem, the block get inserted. The problem comes when you commit the transaction. There is some object that is not in a proper state when acad.exe does CheckTopTransaction(). That's why I was asking if you could have sequential transactions. I don't recall having seen it being done.
The code I posted here works for me and for Keith to who accepted it as solution. SENL1362 said he uses a similar code working for him too. So I think the problem is due to some modification you made the this code.
Did you try with the AddAttributeReferences() extension method as is in the related post ? You an call it passing null for the attValues argument if you do not want to set attributes values.
I can see you have an AttributeDefinitions() one, you don't show the code.
Here's an example of how I'd do it using some extension methods from my library.
Notice that all used extension methods need to be called from within a transaction and they are called from the same transaction opened in the main method.
The 'main' function
public static ObjectId InsertFileAsBlock(string sourceFilePath, Point3d position, double rotation, Scale3d scale, string blockName) { Database db = HostApplicationServices.WorkingDatabase; ObjectId brId; using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = db.BlockTableId.GetObject<BlockTable>(); ObjectId btrId = bt.GetBlock(blockName, sourceFilePath); if (btrId == ObjectId.Null) return ObjectId.Null; BlockReference br = new BlockReference(position, btrId) { Rotation = rotation, ScaleFactors = scale }; BlockTableRecord curSpace = db.CurrentSpaceId.GetObject<BlockTableRecord>(OpenMode.ForWrite); brId = curSpace.Add(br); br.AddAttributeReferences(); tr.Commit(); } return brId; }
Extension methods
public static class ExtensionMethods { // Opens the object of the specified type public static T GetObject<T>( this ObjectId id, OpenMode mode = OpenMode.ForRead, bool openErased = false, bool forceOpenOnLockedLayer = false) where T : DBObject { if (id == ObjectId.Null) throw new ArgumentNullException("id"); Transaction tr = id.Database.TransactionManager.TopTransaction; if (tr == null) throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions); return (T)tr.GetObject(id, mode, openErased, forceOpenOnLockedLayer); } // Opens the entities of the specified type contained in the block table record public static IEnumerable<T> GetObjects<T>( this BlockTableRecord btr, OpenMode mode = OpenMode.ForRead, bool openErased = false, bool forceOpenOnLockedLayers = false) where T : Entity { if (btr == null) throw new ArgumentNullException("btr"); Transaction tr = btr.Database.TransactionManager.TopTransaction; if (tr == null) throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions); BlockTableRecord source = openErased ? btr.IncludingErased : btr; RXClass rxClass = RXClass.GetClass(typeof(T)); foreach (ObjectId id in source) { if (id.ObjectClass == rxClass || id.ObjectClass.IsDerivedFrom(rxClass)) { yield return (T)tr.GetObject(id, mode, openErased, forceOpenOnLockedLayers); } } } // Adds an entity to the block table record public static ObjectId Add(this BlockTableRecord owner, Entity ent) { if (owner == null) throw new ArgumentNullException("owner"); if (ent == null) throw new ArgumentNullException("entity"); Transaction trans = owner.Database.TransactionManager.TopTransaction; if (trans == null) throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NotTopTransaction); try { ObjectId id = owner.AppendEntity(ent); trans.AddNewlyCreatedDBObject(ent, true); return id; } catch { ent.Dispose(); throw; } } // Gets the block table record ObjectId from its blockname if already contained by the block table // or imports a dwg file. public static ObjectId GetBlock(this BlockTable blockTable, string blockName, string fileName = null) { if (blockTable == null) throw new ArgumentNullException("blockTable"); Database db = blockTable.Database; if (blockTable.Has(blockName)) return blockTable[blockName]; try { if (string.IsNullOrEmpty(fileName)) fileName = blockName + ".dwg"; string blockPath; if (File.Exists(fileName)) blockPath = fileName; else blockPath = HostApplicationServices.Current.FindFile(fileName, db, FindFileHint.Default); blockTable.UpgradeOpen(); using (Database tmpDb = new Database(false, true)) { tmpDb.ReadDwgFile(blockPath, FileShare.Read, true, null); return blockTable.Database.Insert(blockName, tmpDb, true); } } catch { return ObjectId.Null; } } // Adds the attribute references to the block references and sets the values contained in the dictionary public static DBObjectCollection AddAttributeReferences(this BlockReference target, Dictionary<string, string> attValues = null) { if (target == null) throw new ArgumentNullException("target"); Transaction tr = target.Database.TransactionManager.TopTransaction; if (tr == null) throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions); BlockTableRecord btr = target.BlockTableRecord.GetObject<BlockTableRecord>(); DBObjectCollection retVal = new DBObjectCollection(); foreach (AttributeDefinition attDef in btr.GetObjects<AttributeDefinition>()) { AttributeReference attRef = new AttributeReference(); attRef.SetAttributeFromBlock(attDef, target.BlockTransform); if (attValues != null && attValues.ContainsKey(attDef.Tag.ToUpper())) { attRef.TextString = attValues[attDef.Tag.ToUpper()]; } ObjectId id = target.AttributeCollection.AppendAttribute(attRef); tr.AddNewlyCreatedDBObject(attRef, true); retVal.Add(attRef); } return retVal; } }
@_gile wrote:The code I posted here works for me and for Keith to who accepted it as solution. SENL1362 said he uses a similar code working for him too. So I think the problem is due to some modification you made the this code.
Did you try with the AddAttributeReferences() extension method as is in the related post ? You an call it passing null for the attValues argument if you do not want to set attributes values.
I can see you have an AttributeDefinitions() one, you don't show the code.
Thanks for taking the time to write some more code. Sorry I didn't get back sooner, I got tied up with something else. The problem was in the AttributeDefinitions() extension. I didn't suspect it because it seemed to work as intended and I was just using it to set properties. It seemed to work because I could inspect the properties during debug. FYI, here is the extension method. I rewrote AddAttributeReferences to filter like you did instead of calling AttributeDefinitions(), but it seems that I should be able to modify it to take a transaction as an argument or use the TopTransaction. That's something else I learned from your code.
/// <summary> /// Extension method that returns a <see cref="Collection"/> of <see cref="AttributeDefinition"/>'s from a <see cref="BlockTableRecord"/>. /// </summary> /// <param name="btr">This BlockTableRecord to use.</param> /// <returns>A Collection of AttributeDefinitions.</returns> public static Collection<AttributeDefinition> AttributeDefinitions(this BlockTableRecord btr) { Transaction trans = Active.Database.TransactionManager.StartTransaction(); Collection<AttributeDefinition> atts = new Collection<AttributeDefinition>(); RXClass theClass = RXObject.GetClass(typeof(AttributeDefinition)); // Loop through the entities in model space foreach (ObjectId objectId in btr.Cast<ObjectId>()) { AttributeDefinition attd = new AttributeDefinition(); // Look for entities of the correct type if (objectId.ObjectClass.IsDerivedFrom(theClass)) { attd = objectId.OpenAs<AttributeDefinition>(trans); atts.Add(attd); } } return atts; }
OpenAs is a delegate that uses the transaction to open the Id in read mode.