AutoCAD .NET API provides Transaction as the main mechanism to manipulate AutoCAD DBObject and Entity instances such as opening, reading, writing, notifying, committing, aborting, closing, and disposing.
The transaction mechanism in AutoCAD .NET API has a clear use pattern, let us take the scenario of creating a new entity for example. The following steps are generally good practices:
• Starting a transaction (through calling the TransactionManager.StartTransaction() method from a Database, Document, or possibly somewhere else);
• Opening the container (represented by the BlockTable) of the drawing spaces (such as Model Space and Paper Spaces) and the block definitions (from which those AutoCAD INSERTs are created) for READ (indicated by the OpenMode.ForRead enumeration value);
• Finding the particular drawing space (e.g. ModelSpace) or a specific block definition, which is represented by a BlockTableRecord entry regardless, and opening it for WRITE (indicated by the OpenMode.ForWrite enumeration value);
• Creating the new Entity (e.g. Circle) and setting its necessary properties;
• Appending the entity to the drawing space or block definition (i.e. the API term BlockTableRecord) through calling its AppendEntity() method;
• Adding the entity to the transaction as well through calling the AddNewlyCreatedDBObject() method against the transaction itself;
• Committing the transaction through calling the Commit() method of the transaction instance;
• Finally disposing of the transaction through either calling the Dispose() method of the transaction explicitly or wrapping all the above operations into a using statement to let the .NET Framework do the work for us automatically no matter whether everything goes well or something goes wrong.
Here is a typical use for such a situation:
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
using (Circle cir = new Circle())
{
cir.Center = new Point3d(10.0, 0.0, 0.0);
cir.Radius = 5.0;
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
}
tr.Commit();
}
Now we know what AutoCAD .NET Transaction is and what steps we generally should follow as far as entity creation is concerned. However, these are not the major concern of this article. It will be talking about something deep, much deeper actually, Transaction and Performance.
Supposing we would like to create thousands of entities (e.g. circles) into the same Model Space of the same Database, what shall we do as far as transaction is concerned?
Most of times, people would have already made a method to help create such an entity (Circle here):
public static void CreateCircle(Point3d center, double radius)
{
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
using (Circle cir = new Circle())
{
cir.Center = center;
cir.Radius = radius;
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
}
tr.Commit();
}
}
As can be noticed, the circle creation is wrapped into a transaction. Then likely, it seems natural to reuse the above code and add some more to create the thousands of circles:
[CommandMethod("CreateCircles2")]
public static void CreateCircles2_Method()
{
Editor ed = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
DateTime begin = DateTime.Now;
for (int index = 0; index < 5000; index++)
{
Point3d cen = new Point3d(index / 10.0, 20.0, 0.0);
CreateCircle(cen, 5.0);
}
TimeSpan elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in CreateCircles2: {0}\n", elapsed.TotalMilliseconds);
}
It looks pretty structural, reusable and readable, doesn’t it?
It certainly does and really is. However, it sacrifices the performance, which is not the major concern many times but surely is many others.
Let us look at another way to do the same thing:
[CommandMethod("CreateCircles1")]
public static void CreateCircles1_Method()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
DateTime begin = DateTime.Now;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
for (int index = 0; index < 5000; index++)
{
using (Circle cir = new Circle())
{
cir.Center = new Point3d(index / 10.0, 0.0, 0.0);
cir.Radius = 5.0;
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
}
}
tr.Commit();
}
TimeSpan elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in CreateCircles1: {0}\n", elapsed.TotalMilliseconds);
}
As can be noticed, all the circle creations are wrapped into a single transaction this time. Except for this point, all other operations are the same.
What good is it?
It improves the performance. Let us run the two commands in the same environment and compare the elapsed time to get a concrete clear idea:
Command: CreateCircles1
Time elapsed in CreateCircles1: 577.201
Command: CREATECIRCLES2
Time elapsed in CreateCircles2: 1154.402
As can be seen, it took twice time for the second command to do the same thing as the first one.
Is it a big deal that half more second is consumed here?
Probably not in this simple sample case, but in most reality ones it likely will be, considering the fact that many other operations may be involved in between such as reading ids of layers and line types and assigning them to the circles may be necessary, transactions may be nested and such like.
Therefore, it is a good practice to use transactions as less as possible and wrap operations as many as possible into a single transaction since transactions are expensive especially nested ones, if the performance is the most concern.
Of course, that means more coding work and better design are necessary. Then another good practice is suggested here, always pairing entity creation helper methods and making sure one wraps everything in a complete transaction but the other accepts an additional transaction pointer. Here is the improved version of the first command:
[CommandMethod("CreateCircles1_New")]
public static void CreateCircles1_New_Method()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
DateTime begin = DateTime.Now;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
for (int index = 0; index < 5000; index++)
{
CreateCircle(tr, new Point3d(index / 10.0, 0.0, 0.0), 5.0);
}
tr.Commit();
}
TimeSpan elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in CreateCircles1_New: {0}\n", elapsed.TotalMilliseconds);
}
public static void CreateCircle(Transaction tr, Point3d center, double radius)
{
Database db = HostApplicationServices.WorkingDatabase;
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
using (Circle cir = new Circle())
{
cir.Center = center;
cir.Radius = radius;
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
}
}
If we run the new command, we will find it has the same good performance as the old one:
Command: CreateCircles1_New
Time elapsed in CreateCircles1_New: 577.201
And better, we can reuse the code to redefine the helper method which has not the Transaction argument:
public static void CreateCircle2(Point3d center, double radius)
{
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
CreateCircle(tr, center, radius);
tr.Commit();
}
}
In this way, all purposes can be met in balance. Code still looks concise enough, readable, maintainable, and structural and performance has been taken into account as well. Of course, the same ideas can be extended to cover other scenarios such as non-current databases, XREF databases and even long transactions.
The complete code has been appended below for convenience:
#region Namespaces
using System;
using System.Text;
using System.Linq;
using System.Xml;
using System.Reflection;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Forms;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Windows;
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
using AcadDocument = Autodesk.AutoCAD.ApplicationServices.Document;
using AcadWindows = Autodesk.AutoCAD.Windows;
#endregion
namespace AcadNetAddinWizard_Namespace
{
public class TestCommands
{
[CommandMethod("CreateCircles2")]
public static void CreateCircles2_Method()
{
Editor ed = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
DateTime begin = DateTime.Now;
for (int index = 0; index < 5000; index++)
{
Point3d cen = new Point3d(index / 10.0, 20.0, 0.0);
CreateCircle(cen, 5.0);
}
TimeSpan elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in CreateCircles2: {0}\n", elapsed.TotalMilliseconds);
}
public static void CreateCircle(Point3d center, double radius)
{
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
using (Circle cir = new Circle())
{
cir.Center = center;
cir.Radius = radius;
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
}
tr.Commit();
}
}
[CommandMethod("CreateCircles1")]
public static void CreateCircles1_Method()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
DateTime begin = DateTime.Now;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
for (int index = 0; index < 5000; index++)
{
using (Circle cir = new Circle())
{
cir.Center = new Point3d(index / 10.0, 0.0, 0.0);
cir.Radius = 5.0;
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
}
}
tr.Commit();
}
TimeSpan elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in CreateCircles1: {0}\n", elapsed.TotalMilliseconds);
}
[CommandMethod("CreateCircles1_New")]
public static void CreateCircles1_New_Method()
{
Database db = HostApplicationServices.WorkingDatabase;
Editor ed = AcadApplication.DocumentManager.MdiActiveDocument.Editor;
DateTime begin = DateTime.Now;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
for (int index = 0; index < 5000; index++)
{
CreateCircle(tr, new Point3d(index / 10.0, 0.0, 0.0), 5.0);
}
tr.Commit();
}
TimeSpan elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in CreateCircles1_New: {0}\n", elapsed.TotalMilliseconds);
}
public static void CreateCircle(Transaction tr, Point3d center, double radius)
{
Database db = HostApplicationServices.WorkingDatabase;
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
using (Circle cir = new Circle())
{
cir.Center = center;
cir.Radius = radius;
btr.AppendEntity(cir);
tr.AddNewlyCreatedDBObject(cir, true);
}
}
public static void CreateCircle2(Point3d center, double radius)
{
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
CreateCircle(tr, center, radius);
tr.Commit();
}
}
}
}
Enjoy it! More coding tips will be created and demonstrated in the future. Please stay tuned.
The leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) provides various project wizards, item wizards, coders and widgets to help program AutoCAD .NET addins.
Recent Comments