Author Topic: Transaction during a close event?  (Read 5913 times)

0 Members and 1 Guest are viewing this topic.

Keith Brown

  • Swamp Rat
  • Posts: 601
Transaction during a close event?
« on: September 12, 2014, 08:54:59 PM »
I am overriding the close event of a Pipe object in AutoCAD MEP and i have a question about a transaction during that event.  Basically what is the best way to get a transaction?  I was under the impression that if it was open for write then there had to be an active transaction.  Apparently this is an incorrect assumption.  Here is the code that I have where I am trying to get the top transaction but it is null.


Code - C#: [Select]
  1. public override void Close(DBObject dbObject)
  2. {
  3.  
  4.  
  5.         if (dbObject.IsWriteEnabled && dbObject.IsModified && !dbObject.IsUndoing)
  6.         {
  7.                 var pipe = (Pipe) dbObject;
  8.  
  9.  
  10.                 var database = pipe.Database;
  11.                 var transaction = database.TransactionManager.TopTransaction();
  12.                 if(transaction !=null)
  13.                 {
  14.                         var pipeSystemId = transaction.GetObject(pipe.SystemId, OpenMode.ForRead) as PipeSystemDefinition;
  15.                         if (pipeSystemId == null)
  16.                         {
  17.                                 return;
  18.                         }
  19.  
  20.  
  21.                         if (pipeSystemId.Name == "Heating Hot Water - Supply")
  22.                         {
  23.                                 var diameter = 0.0;
  24.                                 if (TryGetValueDouble(pipe, "ND1", ref diameter))
  25.                                 {
  26.                                         pipe.InsulationThickness = diameter <= 1.5 ? 1.0 : 2.0;
  27.                                 }
  28.                         }
  29.                 }
  30.         }
  31.         base.Close(dbObject);
  32. }


Is checking for the top transaction the correct way?  Then when it is equal to null i should just get a new transaction?  For some reason I was just under the impression that there should already be a transaction.
« Last Edit: September 12, 2014, 08:59:45 PM by Keith Brown »
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Transaction during a close event?
« Reply #1 on: September 12, 2014, 10:02:11 PM »
Hi Keith,

I often just pass a transaction around to methods that need them saving me to create new transactions all the time, particularly for when calling a method in a loop.

Would this be a valid solution in this case?

eg:
Code - C#: [Select]
  1. public void MainMethod(ObjectIdCollection idCol){
  2.         using ( Transaction tr = db.TransactionManager.StartTransaction()){
  3.                 foreach (objectId id in idCol){
  4.                         DoThingOnEntity(tr, id);
  5.                 }
  6.                 tr.Commit();
  7.         }
  8. }
  9.  
  10. private void DoThingOnEntity(Transaction tr, ObjectId id){
  11.         Entity ent = tr.GetObject(id, OpenMode.ForWrite) as Entity;
  12.         // etc.
  13. }
  14.  
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Transaction during a close event?
« Reply #2 on: September 13, 2014, 10:41:01 AM »
Autocad doesn't always use transactions. In cases like this I don't like the idea of injecting the overhead of a transaction within the close event but rather a using statement on the directly opened object

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Transaction during a close event?
« Reply #3 on: September 14, 2014, 07:45:08 AM »
The whole idea of what I was trying to do was to use an object overrule on the close event to take advantage of having the object already opened for write.  I have some code that I created some time ago that uses events that creates and passes the transaction to the correct methods and works reliably.  I thought that this might be more efficient.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

n.yuan

  • Bull Frog
  • Posts: 348
Re: Transaction during a close event?
« Reply #4 on: September 14, 2014, 11:00:22 AM »
The "Close()" method in the ObjectOverrule, as we know, is called when the overruled DBObject is closed after being opened for reading/writing. Well, it sounds so obvious: first opened, then closed. However, we all write code countless times like this:

using (Transaction tran=myDb.TransactionManager,StartTransaction())
{
    Entity ent1=tran.GetObject(entId1, OpenMode.ForRead) As Entity;
    //Do something
   
    Entity ent2=tran.GetObject(entId2, OpenMode.ForWrite) As Entity;
    //Do something

    ......
    tran.Commit();
}

One would notice that our code only opens object (for reading/writing), but never bothers to close it. The tran.Commit() closes all the open objects. That is, the objects are only closed when the Transaction is committed: this is when the Close() method in the ObjectOverule is called. So, this this moment, the current transaction has been commited (or is committing, maybe?), therefore it is not available any more (even it is still committing, it is on its way out, so it makes sense being not available).

So, even the DBObject in the ObjectOverrule's Close() method is still open before base.Close() is called, the Transaction that governs the DBObject has already gone out of reach.

So, in your case, if you need get other information via transaction, you need to start your own transaction. Here is my quick sample code (where when an DBText is closing, I want to get some information on the DBText's TextStyle, therefore I need to open the TextStyle object in the overridden Close() method):

Code: [Select]
public override void Close(DBObject dbObject)
        {
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;

            Database db = dbObject.Database;
            DBText txt = dbObject as DBText;
           
            //We need to start a new transaction to open other object, when needed
            using (Transaction tran = db.TransactionManager.StartTransaction())
            {
                TextStyleTableRecord style = (TextStyleTableRecord)
                    db.TransactionManager.GetObject(txt.TextStyleId, OpenMode.ForRead);

                ed.WriteMessage("\nText Style: {0}", style.TextSize.ToString());

                tran.Commit();
            }

            base.Close(dbObject);
        }


Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Transaction during a close event?
« Reply #5 on: September 15, 2014, 08:44:12 AM »
Hi Norman,


Thank you for the great explanation!  After a bit of testing and contemplation I have decided to forego this route anyway.  The reason being is that the objects that I am trying to control using this method (AutoCAD MEP objects) are just opened and closed too many times during a simple command.  I modified my close overrule to include just a counter that printed to the screen everytime the method was ran.  I then ran the program and created a simple run of pipe that would be used in normal operation.  The close command was called over 400 times for just a short run of pipe that included approximately 5 lengths of pipe and 5 fittings.  I guess i could put some checks in place that did some monitoring and made sure that my code only ran once during the command but at that point i should probably just use events that I already have up and running correctly that will only run one time after the command is completed.


Alternatively i could wrap everything up in a command and run it that way but I am looking for a fairly high degree of automation at this point.


Thanks again.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Transaction during a close event?
« Reply #6 on: September 15, 2014, 09:29:00 AM »
I have some similar events where I need the system from the the pipe during the event.  Most of the time you can use a StartOpenCloseTransaction but I usually just use the Open/Close methods inside events and forego the whole transaction overhead.

On a side note, I wish Autodesk would remove the "Obsolete" comment from the Open/Close methods so I wouldn't have to turn off the ReSharper warning everytime I use them.
Revit 2019, AMEP 2019 64bit Win 10

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Transaction during a close event?
« Reply #7 on: September 15, 2014, 11:32:52 AM »
It is already opened so just check if it is write enabled then cast to it whatever type you registered the overrule on, and the object calling the overrule Close is the object itself.
I have a command called READABLEATTRIBUTE that you can set on a AttributeDefinition for "readable" or always rotation 0 with respect to UCS.
Me and 10 other people have this overrule registered at runtime and never had a problem with it but here is the close method.
Code - C#: [Select]
  1.        public override void Close(DBObject dbObject)
  2.         {
  3.             if (dbObject.IsWriteEnabled && !dbObject.IsErased)
  4.             {
  5.                 var attRef = dbObject as AttributeReference;
  6.                 if (attRef != null)
  7.                 {
  8.                     int rotCode = HorizontalAttributeOverrule.RotationCode(attRef);
  9.                     if (rotCode == 0)
  10.                     {
  11.                         attRef.Rotation = Vector3d.XAxis.GetAngleTo(dbObject.Database.Ucsxdir, Vector3d.ZAxis);
  12.                     }
  13.                     else if(rotCode == 1)
  14.                     {
  15.                         double ang = attRef.Rotation; //Vector3d.XAxis.GetAngleTo(dbObject.Database.Ucsxdir, Vector3d.ZAxis);
  16.                         if (ang > 1.745329251994329 && ang < 4.886921905584122)
  17.                         {
  18.                             Matrix3d matrix = Matrix3d.Rotation(Math.PI, Vector3d.ZAxis, attRef.AlignmentPoint);
  19.                             attRef.TransformBy(matrix);
  20.                         }
  21.                     }
  22.                    
  23.                 }
  24.                
  25.             }
  26.             base.Close(dbObject);
  27.         }
  28.  
  29.  

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Transaction during a close event?
« Reply #8 on: September 15, 2014, 12:11:15 PM »
Hi Jeff,


I did a little test using the code below to see how many times the object was open/closed during normal routing.  I posted a very short video of the results.  The object was opened/closed 569 times!  It looks like that whenever you create a pipe and stretch it, the object is opened and closed for each unit length as you are stretching.  Here is my code most of which is taken from a post from Tony T.


First the Generic Object Overrule

Code - C#: [Select]
  1. using System;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.Runtime;
  4.  
  5. namespace TheSwamp.Mep.OverRules
  6. {
  7.         public abstract class ObjectOverrule<T> : ObjectOverrule where T: DBObject
  8.         {
  9.                 static RXClass rxclass = RXClass.GetClass( typeof( T ) );
  10.  
  11.                 public ObjectOverrule()
  12.                         : this( true )
  13.                 {
  14.                 }
  15.  
  16.                 public ObjectOverrule( bool enable )
  17.                 {
  18.                         this.Enabled = enable;
  19.                         Overruling = true;
  20.                 }
  21.  
  22.                 public virtual bool Enabled
  23.                 {
  24.                         get
  25.                         {
  26.                                 return this.enabled;
  27.                         }
  28.                         set
  29.                         {
  30.                                 if( value ^ this.enabled )
  31.                                 {
  32.                                         if( value )
  33.                                                 AddOverrule( rxclass, this, true );
  34.                                         else
  35.                                                 RemoveOverrule( rxclass, this );
  36.                                         this.enabled = value;
  37.                                 }
  38.                         }
  39.                 }
  40.  
  41.                 public RXClass RXClass
  42.                 {
  43.                         get
  44.                         {
  45.                                 return rxclass;
  46.                         }
  47.                 }
  48.  
  49.                 protected override void Dispose( bool disposing )
  50.                 {
  51.                         if( disposing && !disposed )
  52.                         {
  53.                                 this.Enabled = false;
  54.                                 GC.SuppressFinalize( this );
  55.                         }
  56.                         disposed = true;
  57.                         base.Dispose( disposing );
  58.                 }
  59.  
  60.                 private bool enabled = false;
  61.                 private bool disposed = false;
  62.         }
  63. }

Then the specific object overrule

Code - C#: [Select]
  1. using Autodesk.Aec.Building.Piping.DatabaseServices;
  2. using Autodesk.AutoCAD.ApplicationServices.Core;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4.  
  5. namespace TheSwamp.Mep.OverRules
  6.  
  7. {
  8.     public class PipeOverrule : ObjectOverrule<Pipe>
  9.     {
  10.         private int counter = 1;
  11.         public override void Close(DBObject dbObject)
  12.         {
  13.             if (dbObject.IsWriteEnabled && dbObject.IsModified && !dbObject.IsUndoing && !dbObject.IsErased)
  14.             {
  15.                 Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(counter + " time object opened\n");
  16.                 ++counter;
  17.             }
  18.             base.Close(dbObject);
  19.         }
  20.     }
  21. }

and finally my command

Code - C#: [Select]
  1. using Autodesk.AutoCAD.Runtime;
  2.  
  3. [assembly: CommandClass(typeof(TheSwamp.Mep.OverRules.Commands))]
  4.  
  5. namespace TheSwamp.Mep.OverRules
  6. {
  7.     class Commands
  8.     {
  9.         static PipeOverrule _pipeOverrule= null;
  10.  
  11.         [CommandMethod("AutomaticPipeInsulation")]
  12.         public static void LockLayerCommand()
  13.         {
  14.             if (_pipeOverrule == null)
  15.             {
  16.                 _pipeOverrule = new PipeOverrule();
  17.                 _pipeOverrule.Enabled = true;
  18.                 Overrule.Overruling = true;
  19.             }
  20.         }
  21.     }
  22. }


and finally a link to the video on youtube showing the pipe being routed.


https://www.youtube.com/watch?v=oi64v5TFmUw&feature=youtu.be


I think i will just need to do some more testing and code creating and come up with a way that i can get my code to not run and still be understandable and maintainable.
« Last Edit: September 15, 2014, 04:16:34 PM by Keith Brown »
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

n.yuan

  • Bull Frog
  • Posts: 348
Re: Transaction during a close event?
« Reply #9 on: September 15, 2014, 03:30:03 PM »
Yes, the overridden Close() methods will be called many times, depending what AutoCAD is doing. Simply hover mouse over an overruled object may causes AutoCAD to open/close the object due to the auto tool tip, quick property view...and so on. The object can be open for notifying/reading/writing. As your video shows when you drag the pipe with some sort of jig action, of course the pipe is opened repeatedly with any small delta of mouse move. it is not surprising that if you drag the pipe longer, the pipe object would be open/close thousands times.

When overriding Close() method, one can use one of the DBObject's "IsXxxxx" properties to determine which state when the DBObject is in as the target state to deal with. For example, if you only want to do something when the DBObject is modified, then you add:

public override void Close(...)
{
    if (dbObject.IsModified &&[||] dnObject.IsWriteEnabled &&[||] dbObject.IsXxxx &&[||] ....)
   {

   }
}

This way you can limits your actual working code only executed when it is really needed inside the "if" bracket. You may extend the filtering mechanism on your own criteria to make sure your heavy lifting code only executed when really necessary (well, the extended filtering code itself could be very much heavy lifting).

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Transaction during a close event?
« Reply #10 on: September 15, 2014, 06:41:21 PM »
I went ahead and did some more testing and implimented the close overrule to where it is working to my satisfaction.  I did have to change

Code - C#: [Select]
  1. DocumentManager.StartTransaction()

to

Code - C#: [Select]
  1. DocumentManager.StartOpenCloseTransaction()

If i didnt then the code would give a fatal error the second time it was ran.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Transaction during a close event?
« Reply #11 on: September 15, 2014, 07:17:37 PM »
Great to see you were able to get it sorted Keith.

Can you post a revised code sample for anyone using MEP who comes across this issue?

Regards,
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Transaction during a close event?
« Reply #12 on: September 15, 2014, 09:57:53 PM »
The final answer was just summation of everyone's suggestions that were posted above.  The final thing for me was to change the StartTransaction to the StartOpenCloseTransaction.  Here is the final PipeOverrule class.


Code - C#: [Select]
  1. using Autodesk.Aec.Building.Piping.DatabaseServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3.  
  4.  
  5. namespace TheSwamp.Mep.OverRules
  6. {
  7.     public class PipeOverrule : ObjectOverrule<Pipe>
  8.     {
  9.         public override void Close(DBObject dbObject)
  10.         {
  11.             if (dbObject.IsWriteEnabled && dbObject.IsModified && !dbObject.IsUndoing && !dbObject.IsErased)
  12.             {
  13.                 var pipe = dbObject as Pipe;
  14.  
  15.                 if(pipe != null)
  16.                 {
  17.                     var database = dbObject.Database;
  18.  
  19.  
  20.                     using (var transaction = database.TransactionManager.StartOpenCloseTransaction())
  21.                     {
  22.                         var pipeSystemId = transaction.GetObject(pipe.SystemId, OpenMode.ForRead) as PipeSystemDefinition;
  23.                         if (pipeSystemId != null)
  24.                         {
  25.                             PipeOverRuleHelper.SetInsulationThickness(pipeSystemId, pipe);
  26.                         }
  27.                         else
  28.                         {
  29.                             PipeOverRuleHelper.ErrorMessage("** Error ** Unable to determine piping system.");
  30.                         }
  31.                         transaction.commit();
  32.                     }
  33.                 }
  34.                 else
  35.                 {
  36.                     PipeOverRuleHelper.ErrorMessage("** Error ** Unable to obtain pipe information.");
  37.                 }
  38.             }
  39.             base.Close(dbObject);
  40.         }
  41.     }
  42. }
« Last Edit: September 15, 2014, 10:06:30 PM by Keith Brown »
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Transaction during a close event?
« Reply #13 on: September 15, 2014, 10:11:32 PM »
Thanks Keith.
Having a complete class will save newer users the (possible) error prone cobbling together of disparate snatches of code. 
Regards,
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Transaction during a close event?
« Reply #14 on: September 16, 2014, 08:42:11 AM »
Sorry Keith I missed you were opening a PipeSystemDefinition and thought you were modifying the pipe.  :uglystupid2: