Author Topic: Fenton Webb's advice re "To dispose or not to dispose"  (Read 74678 times)

0 Members and 1 Guest are viewing this topic.

TheMaster

  • Guest
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #15 on: August 04, 2012, 12:51:05 AM »
Do you think that total re-write could be done without significantly breaking existing code?  And in a timeframe consistent with the rate at which tools, .NET frameworks, etc. are released and retired?

There can't be a total re-write without it significantly breaking existing code, so I don't understand the nature of the question.

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #16 on: August 06, 2012, 08:46:11 AM »

If the DBObject is not database-resident, or is database-resident and you got it from ObjectId.Open(), then you must dispose it.


What about ObjectId.GetObject()?  I assumed that since I cannot use this outside of an active transaction that it's getting the top transaction and using that.  So, when I dispose of the top transaction and haven't implicitly disposed of the database resident object all is right with the world.  Am I right to make this assumption?
Revit 2019, AMEP 2019 64bit Win 10

fentonwebb

  • Guest
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #17 on: August 06, 2012, 01:59:41 PM »
Hey TT

Actually, I think you misunderstood what I said...

I do mention at the end of that sentence "to cleanup after yourself" - meaning, that you should clean up *YOUR* objects not AutoCAD's.

I noted your comments on the link you posted and I really think it's fairly obvious that you should not dispose of master property objects such as MdiActiveDocument? As common in any computer language, you don't dispose/delete/free objects that are not under your control.

Yes .NET is super cool, but you should still think about what you are doing a bit. Also, the bottom like is that the AutoCAD .NET API wraps ObjectARX, that means you absolutely cannot rely on the GC to Dispose AutoCAD Database objects, and, you shouldn't rely on the GC to Dispose all other AutoCAD .NET classes.
 
By the way, being careful about cleaning up after your self (not relying on the GC) can be said for alot of Microsofts own .NET API! There are so many things that can't be allowed to be disposed by the GC, how about a file for instance? if you open a file for write, do you rely on the GC to close it? The point is that our AutoCAD .NET API use age requirements are not special by any means.

About The Transaction object in AutoCAD... The Transaction model was invented way back when for a specific reason - transacting multiple writes on the same objects(s) and allowing layered rollbacks of these multi-write transactions. Back when AutoCAD V1 of .NET was invented, VB.NET did *not* have a using statement so we thought that if we don't allow VBA developers on VB.NET an easy way to control AutoCAD objects we would cost lots of crashes and support calls. This is when AutoCAD .NET Transactions were born, and why we use them.
       Now, unfortunately, there is a big overhead creating a Transaction and calling StartTransaction() - this is because it's initializing a whole subsystem which allows this layered writing and undoing mechanism... That's why we have another method called StartOpenCloseTransaction() which is much leaner and faster, you should use that.
       When using a Transaction, all objects obtained in the transaction using GetObject() or any objects added to the transaction using AddNewlyCreatedDBObject() do not need to be explicitly Dispose()'s - this is because the Transaction.Commit() does it for you.
       If you do not add a newly created object to the transaction using AddNewlyCreatedDBObject, yes, you will have to call Dispose(). However, by not adding it you break the rules of the transaction, so I don't advise that.

So "TT" - how about you come to Autodesk University this year? We can do a special fun coding class - "Fenton Webb vs TT in AutoCAD .NET Coding Head to Head" - the audience chooses what we code, let's see who does the best job in the shortest time? Come on, it will be fun! I'm sure you will win

BlackBox

  • King Gator
  • Posts: 3770
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #18 on: August 06, 2012, 03:45:28 PM »
<snip>

So "TT" - how about you come to Autodesk University this year? We can do a special fun coding class - "Fenton Webb vs TT in AutoCAD .NET Coding Head to Head" - the audience chooses what we code, let's see who does the best job in the shortest time? Come on, it will be fun! I'm sure you will win

"How we think determines what we do, and what we do determines what we get."

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #19 on: August 06, 2012, 04:54:46 PM »

Fenton,
Just to demonstrate your point ;

How about you you recode the samples in the SDK and Labs to conform with what you are advocating !

... that will give everyone some concrete examples.

Regards
Kerry.
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.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #20 on: August 06, 2012, 05:50:10 PM »

Fenton, I've been back to re-read the couple of blog posts in question.

http://adndevblog.typepad.com/autocad/2012/07/the-right-tools-for-the-job-autocad-part-3.html
http://adndevblog.typepad.com/autocad/2012/07/forcing-the-gc-to-run-on-the-main-thread.html

Quote
One question that came up was when to call Dispose(). The thing is that all of our .NET code wraps ObjectARX, in turn, all of our ObjectARX code is single threaded and the .NET Garbage Collector runs on a background thread... What this means is, if you don't call Dispose() on our AutoCAD .NET objects, you run the high risk of the GC garbage collecting our objects on a background thread, which in turn may cause AutoCAD to crash.


Quote
Therefore, I recommend you Dispose() all AutoCAD .NET objects using the .NET 'using' statement – that way you are not relying on the GC to clean up after yourself.



The logical result of these requirements is code similar to Tony's second example.

The average .NET developer/customiser does not have the benefit of having the AutoCAD  chief architect in the next room and therefore has to rely on samples and documentation.

I believe it's time this issue was cleared up.

Regards
Kerry
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: 6144
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #21 on: August 06, 2012, 07:32:07 PM »
A explanation of what is going on behind the scences would be nice.
Also what is the second parameter to Transaction.AddNewlyCreatedDBObject () exactly for?
When would it be ever be false?
Here is the only place I have ever seen it false, and is a OpenCloseTransaction
 
 
 
 
For learning LINQ Edulinq is a great learning experince as it shows what it takes to create your own.
 
In that spirit I just slapped this together and took fifteen minutes, but someone actually knowing what is going on would be nice?
 
 
Code - C#: [Select]
  1.  
  2.       [CommandMethod("AddLineToModel")]
  3.         public void AddLineToModel() // This method can have any name
  4.         {
  5.             Document doc = Application.DocumentManager.MdiActiveDocument;
  6.             Database db = doc.Database;
  7.             using (ITransaction trx = db.StartRollYourOwnTransaction())
  8.             {
  9.                 ObjectId msId = SymbolUtilityServices.GetBlockModelSpaceId(doc.Database);
  10.                 BlockTableRecord model = (BlockTableRecord)trx.GetObject(msId, OpenMode.ForWrite);
  11.                 Line line = trx.CreateNew<Line>();
  12.                 line.StartPoint = Point3d.Origin;
  13.                 line.EndPoint = new Point3d(2.0, 2.0, 0.0);
  14.                 line.SetDatabaseDefaults();
  15.                 model.AppendEntity(line);    
  16.  
  17.                 Line line2 = trx.CreateNew<Line>();
  18.                 line2.StartPoint = Point3d.Origin;
  19.                 line2.EndPoint = new Point3d(2.0, 2.0, 0.0);
  20.                 line2.SetDatabaseDefaults();
  21.                 //model.AppendEntity(line2);    
  22.                          
  23.                 trx.Commit();
  24.             }
  25.         }
  26.  

Code - C#: [Select]
  1.     public interface ITransaction : IDisposable
  2.     {
  3.         void AddObjectToDispose(IDisposable disposableObject);
  4.         void Abort();        
  5.         void Commit();      
  6.         DBObject GetObject(ObjectId id, OpenMode mode);
  7.         DBObject GetObject(ObjectId id, OpenMode mode, bool openErased);
  8.         DBObject GetObject(ObjectId id, OpenMode mode, bool openErased, bool forceOpenOnLockedLayer);
  9.         T CreateNew<T>() where T : DBObject, new();
  10.     }
  11.     public class RollYourOwnTransaction : ITransaction
  12.     {
  13.         Queue<IDisposable> disposables = new Queue<IDisposable>();
  14.         Queue<DBObject> dbobjectsDisposables = new Queue<DBObject>();
  15.  
  16.         private bool isDisposed = false;
  17.  
  18.         public RollYourOwnTransaction()
  19.         {
  20.         }
  21.         ~RollYourOwnTransaction()
  22.         {
  23.             this.Dispose(false);
  24.         }
  25.         public void AddObjectToDispose(IDisposable item)
  26.         {
  27.             disposables.Enqueue(item);
  28.         }
  29.         public void Abort()
  30.         {
  31.             this.Dispose(false);
  32.             GC.SuppressFinalize(this);
  33.         }
  34.         public void Commit()
  35.         {
  36.             this.Dispose();
  37.         }
  38.  
  39.         public DBObject GetObject(ObjectId id, OpenMode mode)
  40.         {
  41.             DBObject obj = id.Open(mode, false, false);
  42.             dbobjectsDisposables.Enqueue(obj);
  43.             return obj;
  44.         }
  45.         public DBObject GetObject(ObjectId id)
  46.         {
  47.             DBObject obj = id.Open(OpenMode.ForRead, false, false);
  48.             dbobjectsDisposables.Enqueue(obj);
  49.             return obj;
  50.         }
  51.         public DBObject GetObject(ObjectId id, OpenMode mode, bool openErased)
  52.         {
  53.             DBObject obj = id.Open(mode, openErased, false);
  54.             dbobjectsDisposables.Enqueue(obj);
  55.             return obj;
  56.         }
  57.         public DBObject GetObject(ObjectId id, OpenMode mode, bool openErased, bool forceOpenOnLockedLayer)
  58.         {
  59.             DBObject obj = id.Open(mode, openErased, forceOpenOnLockedLayer);
  60.             dbobjectsDisposables.Enqueue(obj);
  61.             return obj;
  62.         }
  63.         public void Dispose()
  64.         {
  65.             this.Dispose(true);
  66.             GC.SuppressFinalize(this);
  67.         }
  68.         void Dispose(bool disposing)
  69.         {
  70.             if (!isDisposed)
  71.             {
  72.                 if (disposing)
  73.                 {
  74.                     closeDbObjectQueue();
  75.                 }
  76.                 else
  77.                 {
  78.                     abortDBObjectQueue();
  79.                 }
  80.                 disposeQueue();
  81.             }
  82.             isDisposed = true;
  83.         }
  84.  
  85.         void disposeQueue()
  86.         {
  87.             while (disposables.Count > 0)
  88.             {
  89.                 disposables.Dequeue().Dispose();
  90.             }
  91.             disposables = null;
  92.         }
  93.  
  94.         void closeDbObjectQueue()
  95.         {
  96.             while (dbobjectsDisposables.Count > 0)
  97.             {
  98.                 if (dbobjectsDisposables.Peek().ObjectId.IsNull)
  99.                 {
  100.                     dbobjectsDisposables.Dequeue().Dispose();
  101.                 }
  102.                 else
  103.                 {
  104.                     dbobjectsDisposables.Dequeue().Close();
  105.                 }
  106.             }
  107.             dbobjectsDisposables = null;
  108.         }
  109.  
  110.         void abortDBObjectQueue()
  111.         {
  112.             while (dbobjectsDisposables.Count > 0)
  113.             {
  114.                 if (dbobjectsDisposables.Peek().ObjectId.IsNull)
  115.                 {
  116.                     dbobjectsDisposables.Dequeue().Dispose();
  117.                 }
  118.                 else
  119.                 {
  120.                     dbobjectsDisposables.Dequeue().Cancel();
  121.                 }
  122.             }
  123.             dbobjectsDisposables = null;
  124.         }
  125.  
  126.  
  127.         public T CreateNew<T>() where T : DBObject, new()
  128.         {
  129.             T obj = new T();
  130.             dbobjectsDisposables.Enqueue(obj);
  131.             return obj;
  132.         }
  133.  
  134.     }
  135.     public static class Extensions
  136.     {
  137.    
  138.         public static ITransaction StartRollYourOwnTransaction(this Database db)
  139.         {
  140.             return new RollYourOwnTransaction();
  141.         }
  142.    
  143.     }
  144.  

fentonwebb

  • Guest
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #22 on: August 06, 2012, 08:04:44 PM »
Hey Kerry

I believe in the context I was writing my post it was clear that I was not talking about Dispose()ing objects defined by AutoCAD itself, but if you believe it's misleading then I'll make the post more clear for you, no problem.

Hey Jeff,

that extra flag on the end of AddNewlyCreatedDBObject(): If add == True, the object passed is added to the top transaction. If add == False, then the object is removed from whatever transaction it's within. The object passed must point to an object that is newly created (that is, it has never been closed) and is already database resident (that is, it's been added to a Database so it has an objectId). If the object passed is not newly created, then an exception eNotNewlyCreated is thrown.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #23 on: August 06, 2012, 09:27:18 PM »
Hey Kerry

I believe in the context I was writing my post it was clear that I was not talking about Dispose()ing objects defined by AutoCAD itself, but if you believe it's misleading then I'll make the post more clear for you, no problem.

< .. >


Fenton,
I understand how difficult it is to write well because the writer usually cant help but assume that the reader has some knowledge that the writer takes for granted.

However, words have meanings and the quotes I made in  Reply #20  are explicit, which is what brought me into this discussion.

I do look forward to a re-stating of the requirements.
.... and I'd be appreciative if you could cover this concept explicitly somewhere in public documentation so that old lisp hackers like me are able to make the transition to .NET without too much confusion.

For the sake of completeness, how would you code the sample that Tony used at the start of this discussion. ??


Regards
Kerry.
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.

TheMaster

  • Guest
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #24 on: August 07, 2012, 01:32:31 AM »
Hey TT

Actually, I think you misunderstood what I said...

I do mention at the end of that sentence "to cleanup after yourself" - meaning, that you should clean up *YOUR* objects not AutoCAD's.

I noted your comments on the link you posted and I really think it's fairly obvious that you should not dispose of master property objects such as MdiActiveDocument? As common in any computer language, you don't dispose/delete/free objects that are not under your control.

Yes .NET is super cool, but you should still think about what you are doing a bit. Also, the bottom like is that the AutoCAD .NET API wraps ObjectARX, that means you absolutely cannot rely on the GC to Dispose AutoCAD Database objects, and, you shouldn't rely on the GC to Dispose all other AutoCAD .NET classes.
 
By the way, being careful about cleaning up after your self (not relying on the GC) can be said for alot of Microsofts own .NET API! There are so many things that can't be allowed to be disposed by the GC, how about a file for instance? if you open a file for write, do you rely on the GC to close it? The point is that our AutoCAD .NET API use age requirements are not special by any means.

About The Transaction object in AutoCAD... The Transaction model was invented way back when for a specific reason - transacting multiple writes on the same objects(s) and allowing layered rollbacks of these multi-write transactions. Back when AutoCAD V1 of .NET was invented, VB.NET did *not* have a using statement so we thought that if we don't allow VBA developers on VB.NET an easy way to control AutoCAD objects we would cost lots of crashes and support calls. This is when AutoCAD .NET Transactions were born, and why we use them.
       Now, unfortunately, there is a big overhead creating a Transaction and calling StartTransaction() - this is because it's initializing a whole subsystem which allows this layered writing and undoing mechanism... That's why we have another method called StartOpenCloseTransaction() which is much leaner and faster, you should use that.
       When using a Transaction, all objects obtained in the transaction using GetObject() or any objects added to the transaction using AddNewlyCreatedDBObject() do not need to be explicitly Dispose()'s - this is because the Transaction.Commit() does it for you.
       If you do not add a newly created object to the transaction using AddNewlyCreatedDBObject, yes, you will have to call Dispose(). However, by not adding it you break the rules of the transaction, so I don't advise that.

So "TT" - how about you come to Autodesk University this year? We can do a special fun coding class - "Fenton Webb vs TT in AutoCAD .NET Coding Head to Head" - the audience chooses what we code, let's see who does the best job in the shortest time? Come on, it will be fun! I'm sure you will win

Hi Fenton, and thanks for taking the time to respond here.

I think my point is that there are many people reading that blog who are just starting out with .NET, and have little or no prior experience with native ObjectARX/C++, something that those of us having that background sometimes mistakenly take for granted.

The fact that I misunderstood your post, should serve to make my point, which is that even if it wasn't intended, your advice will be taken literally and without regards for who owns an object. If the docs were as thorough as the native docs are, and clearly indicated what objects are owned and do not require disposal, then perhaps there would be a lot less confusion, and a lot less confusing code. A case in point, if you look at code from Jerry Winter's books and AU material, you will routinely see the TransactionManager being disposed, even though it wraps an object whose scope is the scope of the owning database.

So, it is not about whether your advice was technically correct or not, but rather how it is interpreted (or misinterpreted) by those who may not understand as much about who owns what. While it may be reasonable to assume that if you create an instance of something by calling its class constructor, then you almost certainly own the object and are responsible for disposing it, but in the case of objects that are returned by other API calls, the question of who owns the returned object is not all that clear, especially to the beginner.
« Last Edit: August 07, 2012, 02:10:22 AM by TT »

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #25 on: August 07, 2012, 02:45:30 AM »
have little or no prior experience with native ObjectARX/C  , something that those of us having that background sometimes mistakenly take for granted.

Tony and Fenton,
 
Sorry to sidetrack but I am not a professional programmer and most of my background is in electrical, but I found out I really enjoyed programming. I am about a year and half shy of getting my E.E but had to take break due to fact of being a single father and about to get back in and want to change to CS.
I have have been on fence about quitting and not spending anymore time on learning the AutoCAD API besides writing some quick commands to speeding up some task and spending the time on other programming technologies. If anything and has come close to materializing(something fairly large and 1000+ users) I would honestly just see if I could hire Tony to do it. As a last stitch effort should I just spend the next 6 months or so dong all coding using ObjectARX. It seems like I got a little familiar with concepts, then got a little better just how to search and find methods needed, but never got a deeper understanding of the core concepts required. I just do not feel comfortable trying anything to large due to fact I know enough to know I really do not know what the code is really doing.
 

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #26 on: August 07, 2012, 05:07:20 AM »
Hi,

I consider me as an intermediate .NET coder (not completely newby, but my knowledge is quite far from Fenton's, Tony's, Kerry's, ...). More, English is not my native language and I often have some doubts about my understanding of the docs (and forum discussions).

So, here's an 'intermediate' concrete question about "To dispose or not to dispose" with DBOBjectCollection.
The following code uses two DBOBjectCollection to create 3d solids from polylines. The first one (plineCollection) is fill with selected polylines (database-resident) the second one (regionCollection) contains the Region objects created with Region.CreateFromCurves() method with the first one. The regions in the second DBObjectCollection are only used to create the 3d solids and won't be added to the database.

Code - C#: [Select]
  1.         [CommandMethod("ExtrudePlines")]
  2.         public void ExtrudePlines()
  3.         {
  4.             Document doc = Application.DocumentManager.MdiActiveDocument;
  5.             Database db = doc.Database;
  6.             Editor ed = doc.Editor;
  7.             TypedValue[] filter = { new TypedValue(0, "LWPOLYLINE"), new TypedValue(-4, "&"), new TypedValue(70, 1) };
  8.             PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
  9.             if (psr.Status != PromptStatus.OK) return;
  10.             PromptDoubleResult pdr = ed.GetDistance("\nExtrusion height: ");
  11.             if (pdr.Status != PromptStatus.OK) return;
  12.             double height = pdr.Value;
  13.  
  14.             using (Transaction tr = db.TransactionManager.StartTransaction())
  15.             {
  16.                 BlockTableRecord currentSpace =
  17.                     (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  18.                 using (DBObjectCollection plineCollection = new DBObjectCollection())
  19.                 {
  20.                     foreach (ObjectId id in psr.Value.GetObjectIds())
  21.                     {
  22.                         Polyline pline = (Polyline)tr.GetObject(id, OpenMode.ForRead);
  23.                         plineCollection.Add(pline);
  24.                     }
  25.                     using (DBObjectCollection regionCollection = Region.CreateFromCurves(plineCollection))
  26.                     {
  27.                         foreach (Region region in regionCollection)
  28.                         {
  29.                             try
  30.                             {
  31.                                 using (Solid3d solid = new Solid3d())
  32.                                 {
  33.                                     solid.Extrude(region, height, 0.0);
  34.                                     currentSpace.AppendEntity(solid);
  35.                                     tr.AddNewlyCreatedDBObject(solid, true);
  36.                                 }
  37.                             }
  38.                             finally
  39.                             {
  40.                                 region.Dispose();
  41.                             }
  42.                         }
  43.                     }
  44.                     tr.Commit();
  45.                 }
  46.             }
  47.         }

1. Do I have to dispose the plineCollection (line 18) ?

2. Do I have to dispose the regionCollection (line 25) ? And if so, do I have to dispose each region too (line 40)

3. If I dispose each region (line 40), do I need to dispose the regionCollection too (line 25) ?

4. (subsidiary) May I have used StartOpenCloseTransaction() instead of StartTransaction() (line 14) ?
« Last Edit: August 07, 2012, 05:29:49 AM by gile »
Speaking English as a French Frog

ElpanovEvgeniy

  • Water Moccasin
  • Posts: 1569
  • Moscow (Russia)
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #27 on: August 07, 2012, 06:10:45 AM »

Fenton, I've been back to re-read the couple of blog posts in question.

http://adndevblog.typepad.com/autocad/2012/07/the-right-tools-for-the-job-autocad-part-3.html


Here, Fenton Webb wrote about the poor speed LISP.
In fact, it's not so bad - it all depends on the skill of the programmer and his language skills. Three years ago, I tested the dictionary in autocad - added dictionaries for 100 mb. My program to emulate the internal dictionary as the SQL database. Speed, compared to an external database, located on the same computer that was ten times faster!

I do not think that the author is equally good command of all the above languages​...

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #28 on: August 07, 2012, 08:58:27 AM »
About The Transaction object in AutoCAD... The Transaction model was invented way back when for a specific reason - transacting multiple writes on the same objects(s) and allowing layered rollbacks of these multi-write transactions. Back when AutoCAD V1 of .NET was invented, VB.NET did *not* have a using statement so we thought that if we don't allow VBA developers on VB.NET an easy way to control AutoCAD objects we would cost lots of crashes and support calls. This is when AutoCAD .NET Transactions were born, and why we use them.
       Now, unfortunately, there is a big overhead creating a Transaction and calling StartTransaction() - this is because it's initializing a whole subsystem which allows this layered writing and undoing mechanism... That's why we have another method called StartOpenCloseTransaction() which is much leaner and faster, you should use that.

So should we always be using StartOpenCloseTransaction() or are there times when we should use both?  I'm confused, almost every example on the the ADN blog, Keans Blog, and on here use StartTransaction().  If StartOpenCloseTransaction() is better why don't we see more examples using it?
Revit 2019, AMEP 2019 64bit Win 10

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Fenton Webb's advice re "To dispose or not to dispose"
« Reply #29 on: August 07, 2012, 09:01:31 AM »
Sorry
 
Also by multiple writes do you mean the transaction model is best when writing to an object that is then passed to another function or nested transaction  that writes to it and is helpful that it only it opens once instead of multiples times.