I hope you are having a great time and enjoying life post-Revit!
I am creating an application that must do the following steps:
In order to load the FamilySymbol into the project, I have to have a transaction running. Then, in order to open the Family document, the transaction has to be closed. In order to place an instance of the component, a transaction has to be running.
This causes the application to create two undo points - one for each transaction.
Is there a way to combine the two transactions into a single undo point so the entire action of the command can be undone?
I am using the Manual TransactionMode.
Solved! Go to Solution.
I am creating an application that must do the following steps:
In order to load the FamilySymbol into the project, I have to have a transaction running. Then, in order to open the Family document, the transaction has to be closed. In order to place an instance of the component, a transaction has to be running.
This causes the application to create two undo points - one for each transaction.
Is there a way to combine the two transactions into a single undo point so the entire action of the command can be undone?
I am using the Manual TransactionMode.
Solved! Go to Solution.
Solved by arnostlobel. Go to Solution.
Hello Zoltan Ferenczy,
I am glad you’ve asked. Yes, it can certainly be done. It is what Transaction Groups are for. The steps are simple:
The last operation will merge all transaction that have been committed within the group into just one transaction. It will bear the name of the transaction group.
When working with transaction group, make sure you scope them by the “using” block, just like you would with transactions.
I hope this helps. There is more info if needed in the RevitAPI.chm file. Simply lookup the TransactionGroup class.
Hello Zoltan Ferenczy,
I am glad you’ve asked. Yes, it can certainly be done. It is what Transaction Groups are for. The steps are simple:
The last operation will merge all transaction that have been committed within the group into just one transaction. It will bear the name of the transaction group.
When working with transaction group, make sure you scope them by the “using” block, just like you would with transactions.
I hope this helps. There is more info if needed in the RevitAPI.chm file. Simply lookup the TransactionGroup class.
Ok, Let me make sure I'm doing this correctly when dealing with exeptions.
When the TransactionGroup is rolled back, all of the commited and uncommited Transactions inside it are rolled back?
When a TransactionGroup is commited or assimilated, all of the Transactions inside of it must already be commited?
using (TransactionGroup transGroup = new TransactionGroup(document))
{
transGroup.Start("Transaction Group");
using (Transaction firstTrans = new Transaction(document))
{
try
{
firstTrans.Start("First Transaction");
// do some stuff
firstTrans.Commit();
}
catch
{
transGroup.Rollback(); // <-- We do not have to roll back firstTrans?
return Result.Failed;
}
}
using (Transaction secondTrans = new Transaction(document))
{
try
{
secondTrans.Start("Second Transaction");
// do some stuff
secondTrans.Commit();
}
catch
{
transGroup.Rollback(); // <-- We do not have to roll back secondTrans?
return Result.Failed;
}
}
transGroup.Assimilate();
return Result.Succeeded;
}
Ok, Let me make sure I'm doing this correctly when dealing with exeptions.
When the TransactionGroup is rolled back, all of the commited and uncommited Transactions inside it are rolled back?
When a TransactionGroup is commited or assimilated, all of the Transactions inside of it must already be commited?
using (TransactionGroup transGroup = new TransactionGroup(document))
{
transGroup.Start("Transaction Group");
using (Transaction firstTrans = new Transaction(document))
{
try
{
firstTrans.Start("First Transaction");
// do some stuff
firstTrans.Commit();
}
catch
{
transGroup.Rollback(); // <-- We do not have to roll back firstTrans?
return Result.Failed;
}
}
using (Transaction secondTrans = new Transaction(document))
{
try
{
secondTrans.Start("Second Transaction");
// do some stuff
secondTrans.Commit();
}
catch
{
transGroup.Rollback(); // <-- We do not have to roll back secondTrans?
return Result.Failed;
}
}
transGroup.Assimilate();
return Result.Succeeded;
}
Yes ** 2.
There is no need to roll back in the catch block, beacuse Commit has not been called if you land there.
Correct, Arnošt?
By the way, you can make your code more readable by using the 'Insert Code' button.
Cheers,
Jeremy
Yes ** 2.
There is no need to roll back in the catch block, beacuse Commit has not been called if you land there.
Correct, Arnošt?
By the way, you can make your code more readable by using the 'Insert Code' button.
Cheers,
Jeremy
Hello Zoltan,
Regarding to your two questions:
As for you code snippet, it is quite all right, except for two details, one more important than the other. I’ll start with that one:
Putting all the above comments into action, I’ve rewritten your simple snippet as follows:
using (TransactionGroup transGroup = new TransactionGroup(document)) { using (Transaction trans = new Transaction(document)) { try { transGroup.Start("Action"); trans.Start("First Transaction"); // do some stuff if (trans.Commit() != TransactionStatus.Committed) { return Result.Failed; } trans.Start("Second Transaction"); // do some more stuff trans.Commit(); if (trans.Commit() != TransactionStatus.Committed) { return Result.Failed; } transGroup.Assimilate(); } catch { return Result.Failed; } } return Result.Succeeded; }
Cheers!
Hello Zoltan,
Regarding to your two questions:
As for you code snippet, it is quite all right, except for two details, one more important than the other. I’ll start with that one:
Putting all the above comments into action, I’ve rewritten your simple snippet as follows:
using (TransactionGroup transGroup = new TransactionGroup(document)) { using (Transaction trans = new Transaction(document)) { try { transGroup.Start("Action"); trans.Start("First Transaction"); // do some stuff if (trans.Commit() != TransactionStatus.Committed) { return Result.Failed; } trans.Start("Second Transaction"); // do some more stuff trans.Commit(); if (trans.Commit() != TransactionStatus.Committed) { return Result.Failed; } transGroup.Assimilate(); } catch { return Result.Failed; } } return Result.Succeeded; }
Cheers!
Dear Arnošt,
Thank you very much once again for such a complete and exhaustive answer.
Yet again, I captured it for future reference on The Building Coder:
http://thebuildingcoder.typepad.com/blog/2015/02/using-transaction-groups.html
Cheers,
Jeremy
Dear Arnošt,
Thank you very much once again for such a complete and exhaustive answer.
Yet again, I captured it for future reference on The Building Coder:
http://thebuildingcoder.typepad.com/blog/2015/02/using-transaction-groups.html
Cheers,
Jeremy
Thank you Arnošt and Jeremy; this has been very helpfull.
One more question:
In the same logic as checking that Transaction.Commit() has returned TransactionStatus.Committed, is it a good idea to check that Transaction.Start() has returned TransactionStatus.Started? Is there a reason why it wouldn't?
Thank you Arnošt and Jeremy; this has been very helpfull.
One more question:
In the same logic as checking that Transaction.Commit() has returned TransactionStatus.Committed, is it a good idea to check that Transaction.Start() has returned TransactionStatus.Started? Is there a reason why it wouldn't?
Hello Zoltan,
I have been asked that very question many times in the past. My answer is that even though it is indeed possible in theory that the Start method returns a status other then Started, it is rather unlikely that it actually happens, if is it all possible in the public API. (Note: We, Revit programmers use the same classes internally and for us it is a tiny bit more likely that such a situation may happen.) In most situations that come to my mind the Start method would raise an exception if it is not possible (or permitted) to start a transaction, or any one of the three transaction-phase objects for that matter.
And since I am back on this particular case, there is one small detail I could have changed in the snippet I wrote in my post above. I could have explicitly roll back the transaction group in the two places where I return with a failure due to a transaction failing to commit. Although this explicit closing of the group is not necessary (for it will be closed implicitly upon leaving its using block), I do tend to write my code that way. The reason is more style related; I simply prefer to make it clear to anyone who comes across my code that I knew what I was doing (here, I am aware that the group must be rolled back.)
Hello Zoltan,
I have been asked that very question many times in the past. My answer is that even though it is indeed possible in theory that the Start method returns a status other then Started, it is rather unlikely that it actually happens, if is it all possible in the public API. (Note: We, Revit programmers use the same classes internally and for us it is a tiny bit more likely that such a situation may happen.) In most situations that come to my mind the Start method would raise an exception if it is not possible (or permitted) to start a transaction, or any one of the three transaction-phase objects for that matter.
And since I am back on this particular case, there is one small detail I could have changed in the snippet I wrote in my post above. I could have explicitly roll back the transaction group in the two places where I return with a failure due to a transaction failing to commit. Although this explicit closing of the group is not necessary (for it will be closed implicitly upon leaving its using block), I do tend to write my code that way. The reason is more style related; I simply prefer to make it clear to anyone who comes across my code that I knew what I was doing (here, I am aware that the group must be rolled back.)
Hi Arnost Lobel:
I have read your reply, and looked into the TransactionGroup.Assimilate() method in help document .chm file.
I found that this method takes no parameters.
What I want to ask it:
public void Execute(RvtUiApplication app) { if(WpfTarget.Transactions.ContainsKey(this.GetName())) { Transaction = WpfTarget.Transactions[GetName()]; if(Transaction == null) { Transaction = new Transaction(WpfTarget.CmdVars.DbDoc, GetName()); WpfTarget.Transactions[GetName()] = this.Transaction; } } else { WpfTarget.Transactions.Add(GetName(), new Transaction(WpfTarget.CmdVars.DbDoc, GetName())); } WpfTarget.TransGroup.Start(); Transaction.Start(); Level.Create(WpfTarget.CmdVars.DbDoc, 30); Transaction.Commit(); WpfTarget.TransGroup.Assimilate(); }
Just ignore the head part of this Execute() method, they are for my own WPF window. Notice that before leaving this external event handler context, I called only Assimilated(), but not Rollback() or Commit().
I ran these codes without debugging, and Revit threw no errors.
Tested in API ver 2018. And if running in VS debugger, and single step out of this method, there goes a page like this:
Does it mean that I can 'modelessly' use TransactionGroup?
Also expecting Jeremy to have a discussion about this. 🙂
Hi Arnost Lobel:
I have read your reply, and looked into the TransactionGroup.Assimilate() method in help document .chm file.
I found that this method takes no parameters.
What I want to ask it:
public void Execute(RvtUiApplication app) { if(WpfTarget.Transactions.ContainsKey(this.GetName())) { Transaction = WpfTarget.Transactions[GetName()]; if(Transaction == null) { Transaction = new Transaction(WpfTarget.CmdVars.DbDoc, GetName()); WpfTarget.Transactions[GetName()] = this.Transaction; } } else { WpfTarget.Transactions.Add(GetName(), new Transaction(WpfTarget.CmdVars.DbDoc, GetName())); } WpfTarget.TransGroup.Start(); Transaction.Start(); Level.Create(WpfTarget.CmdVars.DbDoc, 30); Transaction.Commit(); WpfTarget.TransGroup.Assimilate(); }
Just ignore the head part of this Execute() method, they are for my own WPF window. Notice that before leaving this external event handler context, I called only Assimilated(), but not Rollback() or Commit().
I ran these codes without debugging, and Revit threw no errors.
Tested in API ver 2018. And if running in VS debugger, and single step out of this method, there goes a page like this:
Does it mean that I can 'modelessly' use TransactionGroup?
Also expecting Jeremy to have a discussion about this. 🙂
Now this is rather funny, I think. I have found this post accidentally while googling up something else which had my name in it. I was surprised to see a new question to a very old post I had participated in, and even more I was surprised to find the question unanswered. I do not work for Autodesk anymore, but since the transaction API was kind of a favorite of mine when I worked on the API, I kind of feel somehow obligated to answer such questions. I mean – I would not look for them purposely, of course, but if I come across one, I’ll answer it if I can.
Julong.Lin, to your questions:
I hope it make sense what I wrote.
I also have one comment about your code. One thing that is not quite clear to me is what happens if your container of transactions does not have an entry for this.GetName(); The code would proceed to the Else block in which a new transaction is instantiated and added to your container of Transactions. However, the member this.Transaction is not updated, which seem strange with respect to the rest of your code. After you leave your if-else block and you start your transaction group you then call Transaction.start() – which transaction would that be? Do you have a global Transaction instance? I assume you do, because in this particular case your this.Transaction would not be set to the new transaction you instantiated inside the Else block. My guess is that you would start and commit a different transaction (if there was one instantiated before), which would have a different name. Of course, you would not notice that if you successfully assimilated the transaction group, which is probably why that detail escaped you.
Arnošt Löbel
Now this is rather funny, I think. I have found this post accidentally while googling up something else which had my name in it. I was surprised to see a new question to a very old post I had participated in, and even more I was surprised to find the question unanswered. I do not work for Autodesk anymore, but since the transaction API was kind of a favorite of mine when I worked on the API, I kind of feel somehow obligated to answer such questions. I mean – I would not look for them purposely, of course, but if I come across one, I’ll answer it if I can.
Julong.Lin, to your questions:
I hope it make sense what I wrote.
I also have one comment about your code. One thing that is not quite clear to me is what happens if your container of transactions does not have an entry for this.GetName(); The code would proceed to the Else block in which a new transaction is instantiated and added to your container of Transactions. However, the member this.Transaction is not updated, which seem strange with respect to the rest of your code. After you leave your if-else block and you start your transaction group you then call Transaction.start() – which transaction would that be? Do you have a global Transaction instance? I assume you do, because in this particular case your this.Transaction would not be set to the new transaction you instantiated inside the Else block. My guess is that you would start and commit a different transaction (if there was one instantiated before), which would have a different name. Of course, you would not notice that if you successfully assimilated the transaction group, which is probably why that detail escaped you.
Arnošt Löbel
Dear Arnošt,
Thank you very much for jumping in and picking this up once again!
I am happy to hear from you again and took this opportunity to immediately promote your answer to a new post on The Building Coder:
https://thebuildingcoder.typepad.com/blog/2018/11/more-on-transaction-groups-and-assimilation.html
I hope you are having a great time and enjoying life post-Revit!
Dear Arnošt,
Thank you very much for jumping in and picking this up once again!
I am happy to hear from you again and took this opportunity to immediately promote your answer to a new post on The Building Coder:
https://thebuildingcoder.typepad.com/blog/2018/11/more-on-transaction-groups-and-assimilation.html
I hope you are having a great time and enjoying life post-Revit!
@Anonymous
Hi! Thank you for your answer, which is quite clear.
Sorry for this rather delayed reply. Have not seen your fresh answer is this forum for a while, so I thought it might sink 🙂
What I asked was really amateur. In the recent several months, I strictly stick to the Revit API best practise, which means always closing a Transiction before leaving a safe API context.
Thank you again, and gald to hear from you again in this Forum.
@Anonymous
Hi! Thank you for your answer, which is quite clear.
Sorry for this rather delayed reply. Have not seen your fresh answer is this forum for a while, so I thought it might sink 🙂
What I asked was really amateur. In the recent several months, I strictly stick to the Revit API best practise, which means always closing a Transiction before leaving a safe API context.
Thank you again, and gald to hear from you again in this Forum.
Can't find what you're looking for? Ask the community or share your knowledge.