DocumentChanged and TransactionGroupRolledBack problem

DocumentChanged and TransactionGroupRolledBack problem

ssdea
Explorer Explorer
1,171 Views
11 Replies
Message 1 of 12

DocumentChanged and TransactionGroupRolledBack problem

ssdea
Explorer
Explorer

I'm implementing Revit add-in that monitors how user modifies Revit document. My code relies on ControlledApplication.DocumentChanged event to act in correspondence with added, modified, and deleted elements. Unfortunately, I found the scenario when elements are changed, but DocumentChangedEventArgs doesn't contain any added, modified, or deleted element ids.

At first, I noticed such a scenario in the Manage Links window. After a while, I was able to implement the code that reproduces this problem.

I have a command that creates transaction group with single transaction in it. Note, that there is transaction group rollback in the end. The code is the following:

 

 

 

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
    var doc = commandData.Application.ActiveUIDocument.Document;
    using (var trGroup = new TransactionGroup(doc))
    {
        trGroup.Start("Test group");
 
        using (var firstTransaction = new Transaction(doc))
        {
            firstTransaction.Start("Test transaction");
 
            doc.ProjectInformation.Name = "Test Value";
 
            firstTransaction.Commit();
        }
 
        trGroup.RollBack();
    }
 
    return Result.Succeeded;
}

 

 

 

Also, I have DocumentChanged event handler that shows message box with data from the event args:

 

 

 

private void ControlledApplication_DocumentChanged(object sender, DocumentChangedEventArgs e)
{
    var sb = new StringBuilder();
    e.GetTransactionNames().ToList().ForEach(t => sb.AppendLine(t));
    MessageBox.Show($"Added ids count: {e.GetAddedElementIds().Count}, Modified ids count: {e.GetModifiedElementIds().Count}, " +
        $"Deleted ids count: {e.GetDeletedElementIds().Count}, Operation: {e.Operation}, Transactions: {sb}", "DocumentChanged");
}

 

 

 

When the command is executed, 2 message boxes appear one after another:

The first message box is shown after firstTransaction.Commit():

i1.png

 

This is correct. Modified ids contain the id of the ProjectInfo that was modified.

The second message box is shown after trGroup.RollBack():

i2.png

 

Note, that after the group rollback DocumentChanged receives no element ids. Expected behavior is that there should be element id of the element that was rollbacked. This is critical to my application because it gets unsynchronized with the document. Thus, I have the following questions:

  1. The first idea I have is to record transaction names with affected element ids when the transaction group is started. I couldn’t find anything useful event or flag that indicates that the group transaction is started in Revit API. Could you suggest any API that could help me?
  2. If there is no such API, I should record all the transactions to be able to recall what elements were affected by the group rollback. I can’t hold infinite list in memory. Thus, I need to record some quantity of transactions. The question is what is the maximum number of transactions I should record in this case?

Reproduces on Revit 2022, 2023, and 2024.

 

0 Likes
1,172 Views
11 Replies
Replies (11)
Message 2 of 12

jeremy_tammik
Alumni
Alumni

Sorry, but I think the behaviour you describe is exactly as intended.

  

The interior transaction is completely irrelevant as long as it is wrapped in a transaction group and the latter is rolled back.

  

The call to DocumentChanged made before the transaction group is committed (which it never is) is also invalid. 

  

You could implement code to flag your open transaction group and use that to postpone processing of all DocumentChanged information until after the group has been successfully committed.

  

Here is a discussion of how to check for open Transactions, Sub-transactions, or Group Transactions that might come in handy for such purposes:

  

https://stackoverflow.com/questions/62224410/revit-api-how-to-check-for-open-transactions-sub-transa...

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 3 of 12

ssdea
Explorer
Explorer

Thanks for the quick response.

 

The idea of flagging that the transaction group has started would work if my add-in was the only source of transaction groups in Revit. The transaction group may be started by any command in Revit (like Manage Links) or external add-ins. My add-in will not know that the transaction group has started in such cases.

 

Also, when the transaction group is committed (or assimilated) the DocumentChanged is not raised at all. I replaced trGroup.RollBack() with trGroup.Commit(). When the command is executed, I see only one message box after firstTransaction.Commit():

 

i3.png

 

No message boxes after trGroup.Commit().

 

IsModifiable property doesn’t work for me, because Revit sets it to true only when the transaction is started, not the transaction group.

 

Thus, I require one of the following workarounds:

  1. Flag in Revit API that indicates that transaction group (not transaction) is started.
  2. Implement my own TransactionRecorder that will record all transaction names and IDs received by DocumentChanged handler. When the transaction group is rollbacked, DocumentChanged is raised with event args containing transaction names that were rollbacked. Thus, I can find (theoretically) which IDs were rollbacked. However, this will require to record some number of transactions. Is there a constant in Revit that indicates the maximum number of transactions in a transaction group?

Could you suggest anything for the workarounds above, please?

0 Likes
Message 4 of 12

jeremy_tammik
Alumni
Alumni

OK, I can see how that can be problematic and unnecessarily hard to handle or work around. I would like to discuss this in more detail with the development team. However, in order for them to be able to take a look at all and analyse the behaviour in depth, they will require a complete and minimal reproducible case. Can you please supply that? Thank you!

  

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 5 of 12

ricaun
Advisor
Advisor

I had the same problem last year, after noticing the rollback group with no ID in the event DocumentChanged.

 

I'm using the Idling event to know if the TransactionGroup is assimilated, the Idling event is not gonna trigger inside a Transaction/TransactionGroup, only the DocumentChanged event gonna trigger.

 

After some DocumentChanged events without the TransactionGroupRollback, an Idling happened means all the DocumentChanged was committed without a group rollback.

 

In my case, I didn't create a TransactionRecorder yet. But you could record all the DocumentChanged transactions and in the Idling clear the list. If a TransactionGroupRollback happens you could roll back each transaction in the TransactionRecorder until matches the transactions in the TransactionGroupRollback event.

 

A way to check if the TransactionGroup is open would be nicer, to know when to start recording the transactions.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 6 of 12

ssdea
Explorer
Explorer

I'm implementing Revit add-in that monitors how user modifies Revit document. My code relies on ControlledApplication.DocumentChanged event to act in correspondence with added, modified, and deleted elements.

 

My add-in work well for cases when modifications are committed by regular Transactions. I’m facing a problem for scenarios related to TransactionGroup. When the TransactionGroup is rolled back with trGroup.RollBack(), DocumentChangedEventArgs doesn't contain any added, modified, or deleted element ids. I was expecting that event args would contain rolled back element ids, because the rollback changes the elements state which should be processed by my add-in. Thus, my add-in gets unsynchronized with elements state when a transaction group is rolled back. In case when transaction group is committed (or assimilated), my add-in is fine, because the event args sent after inner transactions commitment are valid.

 

This behavior can be reproduced in any Revit project. Just create a new project from any template.

 

The VS solution can be found in zip attached. Steps to launch the sample add-in:

  1. Open “TestAddin.sln” in VS.
  2. Build the project TestAddin.
  3. Copy “TestAddin.dll” and “TestAddin.addin” from “TestAddin\bin\Debug” to “%APPDATA%\Autodesk\Revit\Addins\2024”.
  4. Launch Revit.
  5. Make sure that the add-in is loaded.
  6. Open any Revit project or create a new one.
  7. There should be a new tab “TEST TAB” on the ribbon.
  8. Click “Test” command.
  9. Two message boxes should appear one after another:
    1. The first one indicates that one element was modified. It appears after firstTransaction.Commit() and contains data from DocumentChangedEventArgs received.

      i1.png

    2. The second message box indicates that DocumentChanged was fired after trGroup.RollBack(). The problem is it doesn’t contain any modified ids. This is the case when my application gets unsynchronized. In my opinion, this is incorrect.

i2.png

 

This behavior can be reproduced in Revit 2022, 2023 and 2024. The zip archive contains “RevitAPI.dll” and “RevitAPIUI.dll” from Revit 2024.

 

Message 7 of 12

ssdea
Explorer
Explorer

Thanks for great idea.

 

I tested Idling event with Manage Links window. When the window appears, Idling stops being raised. This will simplify transactions recording for me, if there is no other way.

0 Likes
Message 8 of 12

ricaun
Advisor
Advisor

The TransactionRecorder may not work 😐

 

I was messing with TransactionGroup and found out that is possible to start a TransactionGroup inside a TransactionGroup. In the TransactionGroup the method called Assimilate, this method merges all the transactions committed inside the group in one single undo item.

 

Here is the setup, open a TransactionGroup called 'Rollback', then open another TransactionGroup called 'Assimilate', then do some Transactions and commit in the document, assimilate and rollback.

 

In the end, nothing gonna change in the document, but the event DocumentChanged gonna show the event TransactionGroupRolledBack with no elements and with the transaction name 'Assimilate'.

How I can figure out what was inside this 'Assimilate', the DocumentChanged event never shows that all the transactions were merged into a new one.

 

Here is a full sample of the problem, that creates two transactions assimilates them, and rollback everything. The code records and shows the DocumentChanged events.

 

CommandTransactionGroupProblem.PNG

 

using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Events;
using Autodesk.Revit.UI;
using System;
using System.Text;

namespace RevitAddin.Commands
{
    [Transaction(TransactionMode.Manual)]
    public class CommandTransactionGroupProblem : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elementSet)
        {
            UIApplication uiapp = commandData.Application;
            Document document = uiapp.ActiveUIDocument.Document;

            uiapp.Application.DocumentChanged += Application_DocumentChanged;
            try
            {
                using (TransactionGroup transactionGroupMaster = new TransactionGroup(document))
                {
                    transactionGroupMaster.Start("RollBack");

                    using (TransactionGroup transactionGroup = new TransactionGroup(document))
                    {
                        transactionGroup.Start("Assimilate");

                        using (Transaction transaction = new Transaction(document))
                        {
                            transaction.Start("Author");
                            document.ProjectInformation.Author = $"{DateTime.Now}";
                            transaction.Commit(); // TransactionCommitted Author
                        }

                        using (Transaction transaction = new Transaction(document))
                        {
                            transaction.Start("ClientName");
                            document.ProjectInformation.ClientName = $"{DateTime.Now}";
                            transaction.Commit(); // TransactionCommitted ClientName
                        }

                        transactionGroup.Assimilate(); // No event
                    }

                    transactionGroupMaster.RollBack(); // TransactionGroupRolledBack Assimilate
                }

            }
            finally
            {
                uiapp.Application.DocumentChanged -= Application_DocumentChanged;
            }

            TaskDialog.Show("TransactionGroupProblem", DocumentChangedString.ToString());

            return Result.Succeeded;
        }

        StringBuilder DocumentChangedString = new StringBuilder();

        private void Application_DocumentChanged(object sender, DocumentChangedEventArgs e)
        {
            DocumentChangedString.Append($"{e.Operation} \t {string.Join(" ", e.GetTransactionNames())}\n");
        }
    }

}

 

 

I have no idea how to figure out what is inside the 'Assimilate'.


You could edit the title of this post with something like: DocumentChanged and TransactionGroupRolledBack problem.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 9 of 12

ricaun
Advisor
Advisor

Looks like Revit 'Project Browser' does not update well when using TransactionGroup with Rollback, after changing the name of the View the rollback does not update in the 'Project Browser'. After closing and opening everything refreshes.

 

TransactionGroupRolledBack - 2024-02-14 13-46-28.gif

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 10 of 12

ttanev
Explorer
Explorer

I am having the exact same problem with version 2025. Compound transaction groups open by built-in commands report Elements added and modified during intermediate steps, however when the entire command is cancelled (group transaction rolled-back) by the user, the final DocumentChanged event contains 0 added, 0 modified and 0 deleted elements

 

So for example, start creating a Floor object, you get the SketchPlane created, then the ModelLines representing the contour are added, some internal elements are modified and... if you complete the command you will get also the Floor object itself as added in the final transaction. Everything is fine, ... unless the user cancells and discards the floor midway. Then, behind the scenes, the Sketch, ModelLines and everything in-between is actually deleted from the Document, however... DocumentChanged says 0, 0, 0 - and you have to resort to hacks of 'recording' transactions (which don't even have unique Id's, just some non-unique Names), keep collections of Elements, performing validation per document, etc, etc, etc.

 

Nobody can convince me this is "expected" behavior, because either (a) you mark all intermediate objects as Transient until finally commited, or (b) provide a clear mechanism to monitor transactions with their hierarchy and progress (not only the ones you started, all of them!) or (c) when rolling back you report everything that was added and then removed as actually deleted to preserve symmetry and actual Document history for anyone synchronizing external monitoring systems. 
EDIT: If not clear enough - it is not correct to report in DocumentChanged something as persistently added, if you do not also report it as deleted at the end when it is not there upon cancelling the command.

 

This has been hinted already in 2018 and marked 'Solved' (which it is NOT, rather 'left for dead', just when the actual problem was identified)... this thread, again - dead end, nice.

0 Likes
Message 11 of 12

ricaun
Advisor
Advisor

@ttanev wrote:

This has been hinted already in 2018 and marked 'Solved' (which it is NOT, rather 'left for dead', just when the actual problem was identified)... this thread, again - dead end, nice.


By looking in the DocumentChanged reports no ids after cancelling assembly edit is clear that Revit is not tracking all the id changes when the rollback happen.

 

The development team analysed the internal issue REVIT-127211 [DocumentChanged reports no ids after cancelling assembly edit -- 13917399] that I raised for you and say:

It is by design when TransactionRolledBack or TransactionGroupRolledBack - no change was actually accepted at end.

The DocumentChanged event report no ids in such cases. Is the requirement to retrieve the temporary changes before the rollback? If so we may need to design a new mechanism.

DocumentChanged is designed to only report actual changes that were accepted once the transaction or transaction group ended.

That is pretty evident, actually, and I am sorry that I did not think of telling you so myself before raising the issue with the development team.

I hope this clarifies.

If you would wish such an enhancement to be made for future versions, please raise a wish list item for it in the Revit Idea station.

 

My guess the Revit Idea was never created and the issue was closed, so every body is happy. 

 

Even after creating the Revit Idea to enhance the DocumentChanged RolledBack with ids, you still need to convince Autodesk to do it.

And if Autodesk decide to improve that, the enhancement only gonna affect future versions of Revit.

 

Most of the time makes more sense to find a workaround to you specific problem and implement in your plugin, Autodesk usually is really slow to improve Revit API stuff.

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 12 of 12

ttanev
Explorer
Explorer

Yes, it would seem that way. At least I have the comfort that it's not just me, and my general approach is loosely 'in-sync' with what others have come up in the course of time.


For reference, my workaround - (1) collect and cache ElementId's (UniqueId's actually) from DocumentChanged inbetween IdlingEvents; (2) perform a 'hard' check that instances are indeed present in the model (and/or indeed gone in case of delete operations) after the command completes; (3) flag all such instances as transient/temporary in the database during the deferred synchronization from an IExternalEvent - this leaves a debug trace until maintanance is performed on the DB.

Gray areas: modified elements - if a proper user-defined diff algorithm is in place, the same 'hard' validation applies, though I don't deal with this at the moment; 

Gotcha's: element cache needs to be stored (a) detached from the API context - not a good idea to rely on Document and Element instances stored in persistent collections (b) per-document (GetHashCode as TKey to lookup Application.Documents) - you need to know where to check each element UID/Id afterwards; tracking UniqueId vs ElementId requires a separate mapping again per document - because UniqueId is not available for Deleted elements (also discussed in multiple threads around here) and not guaranteed as globally unique accross documents (also discussed multiple times);

As @ricaun has noted - the TransactionGroup dependent model is very fragile, due to nesting and missing relational information, weak identification of Transactions (some general names) and the absence of a global utility class managing these releations and states. I am out of better ideas at the moment, hope this helps someone as @ricaun and @ssdea 's research helped me.

0 Likes