Transactions inside event handlers

Transactions inside event handlers

nshupeFMPE3
Advocate Advocate
679 Views
3 Replies
Message 1 of 4

Transactions inside event handlers

nshupeFMPE3
Advocate
Advocate

I've been having a problem with Event Handlers and Transactions lately. I have attributes in a block that I am attaching an event handler to their Modified event. 

The problem occurs when I move the block, the event handlers for the attributes trigger. But since the block is open for write since its being moved, I was getting errors when trying to open the block using OwnerId on the attribute.

Using OpenCloseTransaction's seems to through an error. But if I use StartTransaction instead, there is no error.

I thought I had read that OpenClose transactions were prefered for event handlers, but I guess not in this case.

Any light that could be shed on Transactions and when to use some kind over others would be greatly appreciated.



Here is some testing code I used to figure this out. It uses ExtensionMethods that aren't provided, but they are just wrappers that do all the transaction starting and getting of the object behind the scenes.

[CommandMethod(nameof(TestMarkerEvents))]
public void TestMarkerEvents()
{
    ObjectId markerId = Active.Editor.GetUser<BlockReference>("Select Marker:");
    if(markerId == ObjectId.Null) return;

    //open the block in a StartOpenCloseTransaction
    markerId.Get<BlockReference>((marker, tr) =>
    {
        //open each att using the Transaction tr
        marker.AttributeCollection.ForEach(att =>
        {
            att.Modified += AttOnModified;
        }, tr);
    });
}

private void AttOnModified(object sender, EventArgs e)
{
    AttributeReference att = sender as AttributeReference;
    
    //works when using StartTransaction, does not work with StartOpenCloseTransaction
    //does not work with tr = new OpenCloseTransaction
    using (Transaction tr = Active.Database.TransactionManager.StartTransaction())
    {
        att.OwnerId.Get<BlockReference>(block =>
        {
            Active.WriteMessage($"\ntest");
        }, tr);
    }
}
0 Likes
Accepted solutions (1)
680 Views
3 Replies
Replies (3)
Message 2 of 4

ActivistInvestor
Mentor
Mentor

I really can't tell you much, because you are using your own extension methods that open objects, but you're not showing the extension methods and how they are opening the objects.

 

If the block reference is already open in a transaction then you will most likely need to use a transaction to use it, but you should check to see if there is a transaction active already (TopTransction) and use that one instead of starting another one.

 

Also you do not mention where exactly in the code the exception occurs and what the exception is.

0 Likes
Message 3 of 4

nshupeFMPE3
Advocate
Advocate

My question was more centered around the Event Handler not the process of adding the event handler. 

 

The error I would get would be at line 13 below, and it would be eWasOpenForWrite. But I wouldn't have it open in any other code I wrote. So I figured I would check TransactionManager.NumberOfActiveTransactions. If I check that right after line 6 below, before opening a transaction of my own, I would get 0. But if I check after opening the transaction, of coarse it goes to 1.

Here is some example code without extension methods

public static void EventHandlerExample(object sender, EventArgs e)
{
    if (!(sender is AttributeReference att)) return;
    if (att.IsErased) return;
    if (att.OwnerId == ObjectId.Null) return;
    if(att.IsUndoing) return;

    var doc = Application.DocumentManager.MdiActiveDocument;
    var db = doc.Database;


    using (Transaction tr = db.TransactionManager.StartTransaction())
    using (BlockReference block = tr.GetObject(att.OwnerId, OpenMode.ForRead, false, true ) as BlockReference)
    {
        try
        {
            //do work

            tr.Commit();
        }
        catch
        {
            tr.Abort();
            //print error to command line
        }
    }
}


this works because its using StartTransaction(), instead of StartOpenCloseTransaction(). My understanding had been that StartTransaction() wasn't good for use during event handlers. 

I think my real question is what is the difference between 

1. TransactionManager.StartTransaction()
2. TransactionManager.StartOpenCloseTransaction()
3. new OpenCloseTransaction()

I've read blog posts about performance being best with #2 above, but it wont let you do nested opens of the same item. Where as #1 will let you open the same object in a nest transaction even if it was already open in an outer layer.


0 Likes
Message 4 of 4

ActivistInvestor
Mentor
Mentor
Accepted solution

As a general rule of thumb, yes an OpenCloseTransaction would not interfere with what is going on in the editor, but there are exceptions and it really depends on the context. If the block reference was not opened in a transaction but is currently open for write, then I don't think you can use OpenCloseTransaction, as doing that is essentially the same as using ObjectId.Open().