Hello!
I know how to get/set attribute within loop at block's AttributeCollection.
But I'm wonder if I can set value for specified attribute without loop. Or I always have to iterate on each attribute in block's AttributeCollection?
Thanks.
Solved! Go to Solution.
Solved by _gile. Go to Solution.
Only if you store the ids somehow, after the first time you find them, or when you create them, but I can't think of very many scenarios where that would be beneficial.
Thanks for the reply.
I've written own functions to get/set specified attribute (of course with loop using):
private static string GetAttribute(Transaction tr, BlockReference blkRef, string attribute) { AttributeCollection attCol = blkRef.AttributeCollection; foreach (ObjectId attId in attCol) { AttributeReference attRef = (AttributeReference)tr.GetObject(attId, OpenMode.ForWrite); if (attRef.Tag.ToUpper() == attribute.ToUpper()) { return attRef.TextString; } } return string.Empty; } private static void SetAttribute(Transaction tr, BlockReference blkRef, string attribute, string value) { AttributeCollection attCol = blkRef.AttributeCollection; foreach (ObjectId attId in attCol) { AttributeReference attRef = (AttributeReference)tr.GetObject(attId, OpenMode.ForWrite); if (attRef.Tag.ToUpper() == attribute.ToUpper()) { attRef.UpgradeOpen(); attRef.TextString = value; attRef.DowngradeOpen(); } } }
If someone see some bad code, please let me know 🙂
I personally don't like passing transactions around, and would prefer to open a new transaction inside these functions, but that is my personal preference.
You should, however, be disposing the Attribute References that you open.
Otherwise, I have two functions just like these (In VB), and a couple more that overload them where I can pass in a Dictionary(of String, String) to the Set method, and set the values of multiple attributes in the same transaction, or I can get all the tags and values at once by returning the same kind of dictionary from the Get function.
AttributeCollection is implemented from the interface ICollection, not from IDictionary. So there is no key-value pair to jump right away to the specified attribute. We expect to have blockReference.AttributeCollection[attributeTagName] = attributeReference, to get a specified attribute without using loop. But AttributeCollection is a .NET collection that is an enumerable list having to deal with iteration loop.
We can write code to create an attribute dictionary of a block, then use it to randomly find the attribute (from ObjectId) based on its unique tag:
public static Dictionary<string, ObjectId> GetAttributeDictionary(BlockReference blockRef)
{
var dictionary = new Dictionary<string, ObjectId>();
using (Transaction tr = blockRef.Database.TransactionManager.StartTransaction())
{
AttributeCollection collection = blockRef.AttributeCollection;
foreach (ObjectId objectId in collection)
{
var attribute = (AttributeReference)tr.GetObject(objectId, OpenMode.ForRead);
dictionary.Add(attribute.Tag, objectId);
}
tr.Commit();
}
return dictionary;
}
public static ObjectId GetAttributeFromTagName(BlockReference blockRef, string attributeTag)
{
Dictionary<string, ObjectId> dictionary = GetAttributeDictionary(blockRef);
return dictionary[attributeTag];
}
-Khoa
It is late, and I am a little bleary eyed, and a little tipsy, and I don't mean to be adversarial, or controversial, but that does not help to prevent the iteration of the attribute collection. It might be the underlying code for storing the IDs as I had originally proposed, but as written it does not store the IDs, it just iterates the collection every time the wrapper function is called, so it saves you nothing.
I've got to get to bed, and trying to read C# code right now is making me dizzy. I'll look at it again tommorrow and see if I feel differently about it.
Hi,
chiefbraincloud,
I too don't like passing transactions around but I won't open a new while a TopTransaction is running.
Disposing the Attribute References that you open is unnecessary because it's opened with the transaction which will dispose it for you.
By my side, I use some extension methods with Dictionary<string, string>.
Here's an extract:
public static class AttributeExtensions { public static IEnumerable<AttributeReference> GetAttributes(this AttributeCollection attribs) { foreach (ObjectId id in attribs) { yield return (AttributeReference)id.GetObject(OpenMode.ForRead, false, false); } } public static Dictionary<string, string> GetAttributesValues(this BlockReference br) { return br.AttributeCollection .GetAttributes() .ToDictionary(att => att.Tag, att => att.TextString); } public static void SetAttributesValues(this BlockReference br, Dictionary<string, string> atts) { foreach (AttributeReference attRef in br.AttributeCollection.GetAttributes()) { if (atts.ContainsKey(attRef.Tag)) { attRef.UpgradeOpen(); attRef.TextString = atts[attRef.Tag]; } } } }
Definitely we call the method GetAttributeDictionary() only one time, store in a variable, and use it many times based on specified attribute tag names. The second method (GetAttributeFromTagName) just shows how to find an attribute in the dictionary.
ObjectId is more generic to present an attribute reference. Then we can call GetObject() method to get or update attribute values. AttributeCollection is also a collection of ObjectIds.
-Khoa
gile
I also try not to have nested transactions, and I should have also mentioned I don't like passing around open objects, either. I always pass ObjectIds and open them in the function that is doing the work.
As I mentioned above, I also have two methods that use String, String dictionaries to get or set attributes. I love dictionaries, but it is not feasable for me to create a String, ObjectId dictionary and leave it memory resident to use later. I just don't think that is a good idea at all. My code deals with far too many blocks and attributes to load up the memory like that.
@chiefbraincloud wrote:
You should, however, be disposing the Attribute References that you open.
Sorry, but that's not the case.
There's absolutely no need to dispose any DBObject that was opened from a transaction. If you've been reading the adndevblog, you may have misunderstood some recent advice offered there regarding disposing AutoCAD objects.
@chiefbraincloud wrote:
I also try not to have nested transactions, and I should have also mentioned I don't like passing around open objects, either. I always pass ObjectIds and open them in the function that is doing the work.
Hate to disagree.
I see no legitimate reason not to pass open DBObjects as parameters, especially if you follow consistent design patterns where APIs that take open DBObjects as parameters typically assume there is an active transaction that is managed at the entry point (e.g., a command method, event handler, etc).
The other problem with your idea that passing ObjectIds is preferable, is that it complicates code-reusability because APIs that take DBObjects can also accept DBObjects that are not database-resident, have no valid ObjectId, and can't be opened via GetObject().
Additionally, some events pass arguments that include open DBObjects that are not transaction-resident and can't be safely opened with a transaction from the handler of the event, further complicating reusability of APIs that require ObjectIds.
So.... If writing good sound reusable code is important, then writing APIs that take DBObjects is far better than ones that take ObjectIds, for many reasons aside from it being far more efficient..
Hello!
I know how to get/set attribute within loop at block's AttributeCollection.
But I'm wonder if I can set value for specified attribute without loop. Or I always have to iterate on each attribute in block's AttributeCollection?
Thanks.
Have you played with DATAEXTRACTION command ?
~'J'~
Can't find what you're looking for? Ask the community or share your knowledge.