TheSwamp

Code Red => .NET => Topic started by: BillZndl on September 22, 2014, 07:29:12 AM

Title: Transaction within a loop.
Post by: BillZndl on September 22, 2014, 07:29:12 AM
Question:
I can't find a good example of how to do this.
Which would be better having the transaction inside the loop or outside? Or doesn't it matter?
First example inside, the second example is outside: I'm thinking the first example is the best because if you break out of the loop, it's outside the transaction
but then I'm creating a transaction each time through the loop, so I'm thinking this is not so good. TIA.
Code - C#: [Select]
  1.                using (document.LockDocument())
  2.                 {                  
  3.                
  4.                     for (int i = 0; i < listBox2.Items.Count; i++)
  5.                     {
  6.                         SetProgBarStatus(i);
  7.  
  8.                         string str = listBox2.Items[i].ToString();
  9.  
  10.                         using(Database database = new Database(false, false))
  11.                         {
  12.  
  13.                         database.ReadDwgFile(str, FileOpenMode.OpenForReadAndAllShare, false, "");                        
  14.  
  15.                         using (Transaction transaction = document.TransactionManager.StartTransaction())
  16.                         {
  17.  
  18.                             BlockTable table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead);
  19.                             BlockTableRecord btr = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.ModelSpace], OpenMode.ForRead);
  20.  
  21.                             foreach (ObjectId oid in btr)
  22.                             {
  23.                                 if (oid.ObjectClass.DxfName == "TEXT")
  24.                                 {
  25.                                     DBText dbt = (DBText)transaction.GetObject(oid, OpenMode.ForRead);
  26.  
  27.                                     if (dbt.TextString.Contains(pnum) || dbt.TextString.Contains(pnum.ToUpper()))
  28.                                     {
  29.                                         items.Add(str);
  30.                                         break;
  31.                                     }
  32.                                 }
  33.                             }
  34.  
  35.                             transaction.Commit();
  36.                         }
  37.                             database.CloseInput(true);                    
  38.                         }
  39.  
  40.                         if (cts.IsCancellationRequested)
  41.                           {
  42.                                 break;
  43.                           }
  44.                     }                    
  45.                 }
Code - C#: [Select]
  1.                using (document.LockDocument())
  2.                 {
  3.                using (Transaction transaction = document.TransactionManager.StartTransaction())
  4.                  {                  
  5.                
  6.                     for (int i = 0; i < listBox2.Items.Count; i++)
  7.                     {
  8.                             if (cts.IsCancellationRequested)
  9.                             {
  10.                                 break;
  11.                             }
  12.                         SetProgBarStatus(i);
  13.  
  14.                         string str = listBox2.Items[i].ToString();
  15.  
  16.                         using(Database database = new Database(false, false))
  17.                         {
  18.  
  19.                         database.ReadDwgFile(str, FileOpenMode.OpenForReadAndAllShare, false, "");                
  20.  
  21.                             BlockTable table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead);
  22.                             BlockTableRecord btr = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.ModelSpace], OpenMode.ForRead);
  23.  
  24.                             foreach (ObjectId oid in btr)
  25.                             {
  26.                                 if (oid.ObjectClass.DxfName == "TEXT")
  27.                                 {
  28.                                     DBText dbt = (DBText)transaction.GetObject(oid, OpenMode.ForRead);
  29.  
  30.                                     if (dbt.TextString.Contains(pnum) || dbt.TextString.Contains(pnum.ToUpper()))
  31.                                     {
  32.                                         items.Add(str);
  33.                                         break;
  34.                                     }
  35.                                 }
  36.                             }
  37.                                                    
  38.                             database.CloseInput(true);                          
  39.                         }                  
  40.                          
  41.                     }
  42.                         transaction.Commit();
  43.                   }                    
  44.                 }
Title: Re: Transaction within a loop.
Post by: MexicanCustard on September 22, 2014, 07:49:22 AM
Outside the loop.  Create as few transaction as you have to.  There is a lot of overhead in a transaction (opened objects, undo file, etc.) and even more for nested transaction since AutoCad keeps track of transaction on an internal stack.
Title: Re: Transaction within a loop.
Post by: BillZndl on September 22, 2014, 09:19:58 AM
ok, Thanks!
All the examples showed opening one drawing for read then starting the transaction.
It made me wonder when I enclosed the code in a loop.

Just checking with the wrist watch:
Using one transaction (outside the loop) and 1871 drawings, it made a difference of 30 seconds (from 2 minutes to 1-1/2 minutes) to do the search compared to inside the loop. :)
Title: Re: Transaction within a loop.
Post by: WILL HATCH on September 23, 2014, 09:31:00 AM
Just noticed you're using the document transaction to work inside a side database. Use the side database's transaction
Title: Re: Transaction within a loop.
Post by: WILL HATCH on September 23, 2014, 09:33:32 AM
Also since in this case you aren't modifying the side database the call to close input is adding extra time.
Title: Re: Transaction within a loop.
Post by: BillZndl on September 23, 2014, 11:12:13 AM
Just noticed you're using the document transaction to work inside a side database. Use the side database's transaction

I don't know how to do that.
I tried creating the new side database before the transaction and use it for the transaction,
but when I try to use that side database in the loop, it throws an "eRepeatedDwgRead" exception and subsequent fatal error.

Is there a way to reset the new side database so I could use it more than once inside the loop?

The only other way I can think of, is to create the transaction within the loop after creating a new side database each time through the loop,
but then I'm back to creating a new transaction each time through the loop.

Or, could I use : Database db = HostApplicationServices.WorkingDatabase; for the transaction and make a new side database for the ReadDwgFile?


TIA

Title: Re: Transaction within a loop.
Post by: BillZndl on September 23, 2014, 11:12:44 AM
Also since in this case you aren't modifying the side database the call to close input is adding extra time.

Ah, thanks! I will eliminate that.
Title: Re: Transaction within a loop.
Post by: Jeff H on September 23, 2014, 11:31:41 AM
Just noticed you're using the document transaction to work inside a side database. Use the side database's transaction

I don't know how to do that.
I tried creating the new side database before the transaction and use it for the transaction,
but when I try to use that side database in the loop, it throws an "eRepeatedDwgRead" exception and subsequent fatal error.

Is there a way to reset the new side database so I could use it more than once inside the loop?

The only other way I can think of, is to create the transaction within the loop after creating a new side database each time through the loop,
but then I'm back to creating a new transaction each time through the loop.

Or, could I use : Database db = HostApplicationServices.WorkingDatabase; for the transaction and make a new side database for the ReadDwgFile?


TIA

A document can have a database a number of side databases associated with it.

If your just reading information from a file; then in the Database constructor the second parameter is if you want to associate a document with it, and I guess only reason is for undo mechanism. So passing true as second argument in Constructor new Database(false, true). You do not need to lock the document and might solve error.
Then use the database you ReadDwgFile into to start a transaction then do whatever looping required.
Title: Re: Transaction within a loop.
Post by: BillZndl on September 23, 2014, 11:49:55 AM

ok, I can try that, Jeff.
The only problem I can see is that eventually, I will want to not only read (find the part number) but then replace what's found with a  new part number.
So it would be easier if I could use the same code to do one or the other... but not imperative..

I won't need an undo because the changes will be made on a list of drawings, then changes saved so the only way to "undo" would be to run the list again.


Just noticed you're using the document transaction to work inside a side database. Use the side database's transaction

I don't know how to do that.
I tried creating the new side database before the transaction and use it for the transaction,
but when I try to use that side database in the loop, it throws an "eRepeatedDwgRead" exception and subsequent fatal error.

Is there a way to reset the new side database so I could use it more than once inside the loop?

The only other way I can think of, is to create the transaction within the loop after creating a new side database each time through the loop,
but then I'm back to creating a new transaction each time through the loop.

Or, could I use : Database db = HostApplicationServices.WorkingDatabase; for the transaction and make a new side database for the ReadDwgFile?


TIA

A document can have a database a number of side databases associated with it.

If your just reading information from a file; then in the Database constructor the second parameter is if you want to associate a document with it, and I guess only reason is for undo mechanism. So passing true as second argument in Constructor new Database(false, true). You do not need to lock the document and might solve error.
Then use the database you ReadDwgFile into to start a transaction then do whatever looping required.
Title: Re: Transaction within a loop.
Post by: BillZndl on September 23, 2014, 03:08:26 PM
If your just reading information from a file; then in the Database constructor the second parameter is if you want to associate a document with it, and I guess only reason is for undo mechanism. So passing true as second argument in Constructor new Database(false, true). You do not need to lock the document and might solve error.

nope, got the same error. just an FYI.

Anyways, here's what I did for the search & replace.
I'll just run a separate block of code, no frills.
Comes in handy when they come up with a new part to replace an existing one that's the same in all respects except for the part number.  :?

Code - C#: [Select]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
  4.  
  5. namespace FileManagerDialog
  6. {
  7.  
  8.     class FixPartNumbers
  9.     {
  10.         /// <summary>
  11.         /// MassReplace, searches the database of drawings listed to find if string exists.
  12.         /// Replaces text with new text.
  13.         /// </summary>  
  14.  
  15.         public void MassReplace(string[] filelist, string strToFind, string strToUse)
  16.         {
  17.             Document document = AcadApp.DocumentManager.MdiActiveDocument;
  18.  
  19.             strToFind = strToFind.ToUpper();
  20.  
  21.             using (document.LockDocument())
  22.             {
  23.                 for (int i = 0; i < filelist.Length; i++)
  24.                 {
  25.                     string str = filelist[i].ToString();
  26.  
  27.                     using (Database database = new Database(false, false))
  28.                     {
  29.                         database.ReadDwgFile(str, FileOpenMode.OpenForReadAndWriteNoShare, false, "");
  30.                         database.CloseInput(true);
  31.  
  32.                         using (Transaction transaction = database.TransactionManager.StartTransaction())
  33.                         {
  34.                             BlockTable table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForWrite);
  35.                             BlockTableRecord btr = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
  36.  
  37.                             foreach (ObjectId oid in btr)
  38.                             {
  39.                                 if (oid.ObjectClass.DxfName == "TEXT")
  40.                                 {
  41.                                     DBText dbt = (DBText)transaction.GetObject(oid, OpenMode.ForRead);
  42.  
  43.                                     if (dbt.TextString.Contains(strToFind))
  44.                                     {
  45.                                         dbt.UpgradeOpen();
  46.                                         string oldStr = dbt.TextString;
  47.                                         string newStr = oldStr.Replace(strToFind, strToUse);
  48.                                         dbt.TextString = newStr;
  49.                                     }
  50.                                 }
  51.                             }
  52.  
  53.                             transaction.Commit();
  54.                         }
  55.  
  56.                         database.SaveAs(database.Filename, DwgVersion.Current);
  57.                     }                    
  58.                 }
  59.             }
  60.         }
  61.     }      
  62. }
  63.  
Title: Re: Transaction within a loop.
Post by: MexicanCustard on September 24, 2014, 08:14:41 AM
My two cents.

Why Lock the Document? You aren't doing anything with it.
Code - C#: [Select]
  1.             using (document.LockDocument())

Why ToString()? Elements of array are already strings?
Code - C#: [Select]
  1. string str = filelist[i].ToString();
Title: Re: Transaction within a loop.
Post by: BillZndl on September 24, 2014, 09:19:14 AM
My two cents.

Why Lock the Document? You aren't doing anything with it.
Code - C#: [Select]
  1.             using (document.LockDocument())

Good question. (This is for the code used for the "search only" code that doesn't change the databases.)
Jeff and all examples I could find said that if I used ReadDwgFile(false, true) I wouldn't need to lock the document.
But when I ran the code, i would get an "eNotFromThisDocument" error and subsequent fatal error with associated crash of Acad.
So I added the document lock back in.
After I read your post, I tried using ReadDwgFile(false, false) with no document lock and it ran fine. Why? dunno.
So I'm taking the lock out of the code.

Edit: I think I'll have to leave the doc lock in the code that changes the text strings , wouldn't I?
Edit#2: nope, not needed. Thanks!


Why ToString()? Elements of array are already strings?
Code - C#: [Select]
  1. string str = filelist[i].ToString();

Ah, good catch. This was copied from the other block of search code, where I was reading the listBox.Items, which needed that cast.
I get pulled in a lot of different directions around here so it can take me a while to go over the code and catch all that stuff.

Thanks!
Title: Re: Transaction within a loop.
Post by: Keith Brown on September 24, 2014, 09:47:06 AM
Why ToString()? Elements of array are already strings?
Code - C#: [Select]
  1. string str = filelist[i].ToString();

Ah, good catch. This was copied from the other block of search code, where I was reading the listBox.Items, which needed that cast.
I get pulled in a lot of different directions around here so it can take me a while to go over the code and catch all that stuff.

Thanks!


I know it costs alot of money ($250) but this is the kind of thing that ReSharper (http://www.jetbrains.com/resharper/?gclid=CjwKEAjw14mhBRC0vdSNkI2l7CASJAC8OFS0NLE1x7ArYZtcspyH8SvPQFeLfE4v3bO9O5qmcoNHVBoCnaPw_wcB) is really good at informing you of.   Some of the best money i ever spent.
Title: Re: Transaction within a loop.
Post by: BillZndl on September 24, 2014, 10:47:41 AM
I know it costs alot of money ($250) but this is the kind of thing that ReSharper (http://www.jetbrains.com/resharper/?gclid=CjwKEAjw14mhBRC0vdSNkI2l7CASJAC8OFS0NLE1x7ArYZtcspyH8SvPQFeLfE4v3bO9O5qmcoNHVBoCnaPw_wcB) is really good at informing you of.   Some of the best money i ever spent.

Thanks!
I would love to have tools like that!
My unofficial role as "CAD  manager" leaves me slam dunking things most of the time.
I never know when I'll be directed to go do something else, or for how long + alot of distractions.

My main job is mass producing AutoCAD drawings for assembly line models in the marine industry (25 yrs now since DOS version '89?).
I love codeing and drawing so finding ways to automate things has always intrigued me.

So until something else comes along, I'll be doing what I can.

Thanks, for all the help!

Title: Re: Transaction within a loop.
Post by: Jeff H on September 24, 2014, 04:03:11 PM
I have no problems using Database(false, true) constructor and the only time I have to lock the document when updating a side database is using 
Database(false, false) from Application context.
 
So if  Database(false, false)  used from a modeless dialog or a command with CommandFlags.Session then I have to lock document.
 
 
Title: Re: Transaction within a loop.
Post by: BillZndl on September 24, 2014, 04:56:25 PM
I have no problems using Database(false, true) constructor and the only time I have to lock the document when updating a side database is using 
Database(false, false) from Application context.
 
So if  Database(false, false)  used from a modeless dialog or a command with CommandFlags.Session then I have to lock document.

After un-defining the open command in my s::startup,

I initiate and instance of my modeless dialog with this command method:

Code - C#: [Select]
  1.  
  2. [CommandMethod("open", CommandFlags.Session)]
  3.        
  4.         public static void ShowDialogForm()
  5.         {
  6.             Autodesk.AutoCAD.ApplicationServices.Application.ShowModelessDialog(LundFileDialog.Instance);
  7.         }
  8.  

Then with a button on that dialog, I initiate a second modeless dialog instance and hide the first:

Code - C#: [Select]
  1. private void button7_Click(object sender, EventArgs e)
  2.         {          
  3.             AcadApp.ShowModelessDialog(DrawingSearchDialog.Instance);
  4.         }
  5.  

From there I run the code for searching and replaceing text in drawings using a button click.
The search thread is supposed to run on another thread from a control so it doesn't lock up the UI.
The search & replace is simply run on the main thread as i don't use it that often.
I removed the document lock from the search & replace and use:
Code - C#: [Select]
  1. using (Database database = new Database(false, false))
  2.                 {
  3.                     database.ReadDwgFile(str, FileOpenMode.OpenForReadAndWriteNoShare, false, "");
  4.                     database.CloseInput(true);
  5.  
  6.                     using (Transaction transaction = database.TransactionManager.StartTransaction())
  7.  
I replace parts of strings and then run database.SaveAs(database.Filename, DwgVersion.Current); with no document lock.

I'll play some more tommorrow, I see I still have the document lock in the search only block.
Like I said, all examples talked like false, true should work with out the doc lock but I've hit the limit of my knowlege on the subject.


Title: Re: Transaction within a loop.
Post by: nekitip on September 25, 2014, 02:34:26 AM
I have not read the whole tread since I find it hard to concentrate in the morning :D, but just as a thought since i saw this:
Quote
From there I run the code for searching and replaceing text in drawings using a button click.
The search thread is supposed to run on another thread from a control so it doesn't lock up the UI.
And the short comment (again, I have not read whole tread) is that the multithreading is not working in autocad and will not be supported any time soon (as per Stephen Preston), so EVERYTHING you do in autocad must be in the same thread, and the only thing you can do the multithreaded way, is to extract data from dbobjects you need in the main tread, and move only that data in different thread, continue with main UI, and when finished, move results back. Now, this is easy in .NET 4.5, with Await, Asyinc .NET commands, but that is 4.5 is acad2015 only for now.

Also, when returning data, remember to check if editor is still looking at the same mdicurrent document (or doc at all)
Title: Re: Transaction within a loop.
Post by: BillZndl on September 25, 2014, 07:05:43 AM
Jeff,
It does work as you said when I remove the doc lock, and use the new side database for the transaction and ReadDwgFile(false, true,.
I was hoping to get away from starting a transaction each time through the loop but it appears to me that, to do it right, I'll have to do just that.
Code - C#: [Select]
  1. for (int i = 0; i < listBox2.Items.Count; i++)
  2.                     {
  3.                         SetProgBarStatus(i);
  4.  
  5.                         string str = listBox2.Items[i].ToString();
  6.  
  7.                         using (Database database = new Database(false, true))
  8.                         {                                
  9.                             database.ReadDwgFile(str, FileOpenMode.OpenForReadAndAllShare, false, "");
  10.                             using (Transaction transaction = database.TransactionManager.StartTransaction())
  11.                             {
  12.  


@ nekitip,
Thanks!
That explains a lot of things to me, that I was fighting with last week.

The only reason I tried to run something on another thread,
was to keep the UI from "appearing to lock up" while a file search was going on.
Don't know if I accomplished running on a different thread
but by using the methods I found HERE:  (http://adndevblog.typepad.com/autocad/2012/06/use-thread-for-background-processing.html)
the search can run now and the dialog doesn't display the swirly Cursor and the screen going white.

I even managed to run a progress bar that looks like it's in sync with the search.




Title: Re: Transaction within a loop.
Post by: MexicanCustard on September 25, 2014, 07:54:50 AM
The only time I use a Document Lock is when working with databases open in the editor, never for a side database, and when I'm accessing them from methods other than "Command" methods, i.e. Dialog Boxes, Palettes, etc.  This pattern has never given me any problems.

I would second the ReSharper thing.  After using it for the last three years, not only has it done a great job of refactoring and cleaning up code but it also has taught me to be a better coder along the way.
Title: Re: Transaction within a loop.
Post by: BillZndl on September 25, 2014, 12:38:22 PM
Thanks MC!

Understood and I think we're all good on this end.

FWIW: I just got a new project assigned to me today.
Convert an old AutoLisp 3d boat parameter calculater containing a nice GUI, that I made years ago,  over to .net!  8-)




Title: Re: Transaction within a loop.
Post by: nekitip on September 25, 2014, 03:32:43 PM
Code: [Select]
The only reason I tried to run something on another thread,
was to keep the UI from "appearing to lock up" while a file search was going on.
Don't know if I accomplished running on a different thread
but by using the methods I found HERE:

I have not read carefully that code on that page, so I might be wrong, and if so, I might be missing a lot, so if anyone thinks I'm wrong, please correct me!!!
I claim that you can do whatever you want from .NET on any CPU core and thread you have, without any real worry or care, on yours app that is running in autocad BUT as long it is not dealing with autocad.
So, you can start three cpu intensive algorithms in paralell, search a lot on disk, open web page on usercontrol, and once in a while (when thread exits) set autocad editor to write a message: "it's ok" and the user or autocad will never know you have done anything wrong.
BUT, you cannot do even a simple task like open database and peek inside while you have other thread doing the same since only ONE, main thread, the one that was the first one when your app started can poke DB (or open some window or stuff).