пятница, 4 января 2013 г.

Работа с Database в AutoCAD: с использованием транзакций и без них


Сегодня, разгребая мусор в "Моих Документах" нашёл когда-то написанную мною маленькую шпаргалку на тему работы с объектом Database в AutoCAD. Поскольку текст может быть полезен - опубликовываю его здесь.

Редактировать объекты базы данных можно как с использованием транзакции (Transaction), так и без неё.

Редактирование базы данных чертежа без использование транзакции
Для работы с объектами базы данных чертежа не прибегая к использованию транзакции, можно использовать два подхода:

  1. Работать с объектами напрямую, без эмуляции транзакции (старый, не рекомендуемый способ).
  2. Использовать эмуляцию транзакции, т. е. работать в контексте объекта OpenCloseTransaction (рекомендуется компанией Autodesk взамен способа, указанного в п.1).
Если свойство DBObject.ObjectId не равно ObjectId.Null, то при вызове DBObject.Dispose() автоматически будет вызван и DBObject.Close(). В противном же случае идет освобождение памяти.

Способ работы с объектами без эмуляции транзакции представлен следующими методами:
  • ObjectId.Open() - получить объект из базы данных на основе его идентификатора.
  • DBObject.Close() - сохранить в базу данных изменения, выполненные над объектом и освободить выделенную под объект память.
  • DBObject.Cancel() - отказаться от изменений, выполненных над объектом и освободить выделенную под объект память.
Примечание:
В первых двух примерах кода не используется объект OpenCloseTransaction, однако в реальных условиях, при работе с базой данных чертежа без использования транзакции, настоятельно рекомендуется пользоваться этим объектом (это будет показано ниже, в третьем примере).

Во всех примерах, приведённых ниже, я буду использовать следующие префиксы пространств имён:

   1:  using cad = Autodesk.AutoCAD.ApplicationServices.Application;
   2:  using AppSrv = Autodesk.AutoCAD.ApplicationServices;
   3:  using DbSrv = Autodesk.AutoCAD.DatabaseServices;
   4:  using EdInp = Autodesk.AutoCAD.EditorInput;
   5:  using Geom = Autodesk.AutoCAD.Geometry;
   6:  using Rtm = Autodesk.AutoCAD.Runtime;


Пример №1 (не рекомендуемый вариант):  работа с базой данных чертежа без использования транзакции и без её эмуляции

   1:  // Пример №1 (НЕРЕКОМЕНДУЕМЫЙ ВАРИАНТ):
   2:  // работа с базой данных чертежа без использования 
   3:  // транзакции и без ее эмуляции
   4:  [Rtm.CommandMethod("AddCircleWithoutTransaction")]
   5:  public void AddCircleWithoutTransaction() {
   6:      AppSrv.Document doc =
   7:        cad.DocumentManager.MdiActiveDocument;
   8:      DbSrv.Database db = doc.Database;
   9:      EdInp.Editor ed = doc.Editor;
  10:   
  11:      // Добавим окружность в пространство Model
  12:      DbSrv.Circle circle = new DbSrv.Circle();
  13:      circle.SetDatabaseDefaults(db);
  14:      circle.Center = new Geom.Point3d(
  15:        10.0, 10.0, 10.0);
  16:      circle.Radius = 30.0;
  17:   
  18:      DbSrv.BlockTable bt = db.BlockTableId.Open(
  19:        DbSrv.OpenMode.ForRead) as DbSrv.BlockTable;
  20:      DbSrv.BlockTableRecord ms = bt[
  21:        DbSrv.BlockTableRecord.ModelSpace]
  22:        .Open(DbSrv.OpenMode.ForWrite) as
  23:          DbSrv.BlockTableRecord;
  24:   
  25:      ms.AppendEntity(circle);
  26:   
  27:      // Не забываем сохранить выполненные
  28:      // изменения вызывая либо методы Close(), 
  29:      // либо методы Dispose(): в обоих случаях 
  30:      // произойдёт сохранение выполненных изменений.
  31:   
  32:      // Т.о. сохранить выполненные изменения можно 
  33:      // либо так:
  34:      // bt.Close();
  35:      // circle.Close();
  36:      // ms.Close();            
  37:   
  38:      // либо так:
  39:      bt.Dispose();
  40:      circle.Dispose();
  41:      ms.Dispose();
  42:   
  43:      // Если вместо Close() вызывать Cancel(),
  44:      // то изменения не будут сохранены.
  45:   
  46:      // После вызова метода Close() или Cancel()
  47:      // Вызывать метод Dispose() не нужно, т.к. 
  48:      // он вызывается автоматически в коде методов
  49:      // Close() и Cancel():            
  50:   
  51:      ed.WriteMessage("\ncircle.IsDisposed = {0}",
  52:        circle.IsDisposed); // True
  53:      ed.WriteMessage("\nms.IsDisposed = {0}",
  54:        ms.IsDisposed); // True
  55:      ed.WriteMessage("\nbt.IsDisposed = {0}\n",
  56:        bt.IsDisposed); // True
  57:  }

Если Вы хотите сохранить выполненные изменения, то вместо метода DBObject.Close() можно вызывать сразу метод DBObject.Dispose() - в этом случае метод DBObject.Close() будет вызываться автоматически, перед уничтожением объекта.

Если вы завершаете работу с объектом посредством вызова метода DBObject.Dispose(), то вызывать после этого метод DBObject.Close() не следует, поскольку этот метод  автоматически вызывается в процессе работы DBObject.Dispose() и его повторный вызов приведёт к возникновению Fatal Error.

Следующий код по смыслу идентичен предыдущему:

Пример №2 (не рекомендуемый вариант):  работа с базой данных чертежа без использования транзакции и без её эмуляции

   1:  // Пример №2 (НЕРЕКОМЕНДУЕМЫЙ ВАРИАНТ):
   2:  // работа с базой данных чертежа без использования
   3:  // транзакции и без ее эмуляции. Смысл кода идентичен
   4:  // предыдущему примеру
   5:  [Rtm.CommandMethod("AddCircleWithoutTransaction2")]
   6:  public void AddCircleWithoutTransaction2() {
   7:      AppSrv.Document doc =
   8:        cad.DocumentManager.MdiActiveDocument;
   9:      DbSrv.Database db = doc.Database;
  10:      EdInp.Editor ed = doc.Editor;
  11:   
  12:      // Внимание! Если работа с объектами ведётся без 
  13:      // использования транзакции, то их инициализацию 
  14:      // лучше выполнять в блоке using. В этом случае
  15:      // по выходу из блока using автоматически вызывается
  16:      // метод Dispose() и произойдёт сохранение всех
  17:      // изменений.            
  18:   
  19:      // Добавим окружность в пространство Model
  20:      using (DbSrv.Circle circle =
  21:        new DbSrv.Circle()) {
  22:          circle.SetDatabaseDefaults(db);
  23:          circle.Center = new Geom.Point3d(
  24:            10.0, 10.0, 10.0);
  25:          circle.Radius = 30.0;
  26:   
  27:          using (DbSrv.BlockTable bt =
  28:            db.BlockTableId.Open(DbSrv.OpenMode
  29:              .ForRead) as DbSrv.BlockTable) {
  30:              using (DbSrv.BlockTableRecord ms =
  31:                bt[DbSrv.BlockTableRecord.ModelSpace]
  32:                  .Open(DbSrv.OpenMode.ForWrite)
  33:                  as DbSrv.BlockTableRecord) {
  34:                  ms.AppendEntity(circle);
  35:              } // Здесь будет вызван ms.Dispose()
  36:          }  // Здесь будет вызван bt.Dispose()
  37:      } // Здесь будет вызван circle.Dispose()
  38:  }

Использование методов ObjectId.Open()и DBObject.Close(), в некоторых случаях может быть опасным...

Первый пример: если вы держите одновременно открытыми несколько объектов, то вам нужно будет либо для каждого из них создать свою конструкцию using(), либо try/catch, иначе, если у вас что-то пойдёт не так, и произойдёт исключение (Exception), то другие, ранее открытые вами вне транзакции объекты, могут остаться не закрытыми.

Второй пример опасного использования ObjectId.Open()и DBObject.Close() - это когда данный способ применяется внутри блока using() и открытые объекты были изменены: когда вы используете конструкцию using() для автоматического уничтожения объектов, созданных с помощью ObjectId.Open(), то во время их уничтожения происходит вызов метода DBObject.Close()ФИКСИРУЯ тем самым любые выполненные над объектом изменения. Это равносильно вызову метода Transaction.Commit(). Использование конструкции using() приводит к автоматическому вызову метода DBObject.Dispose() объекта независимо от того, завершилось ли выполнение кода успешно, или же оно было завершено преждевременно, в результате возникновение исключения (Exception).

Для того, чтобы избежать возможных проблем, описанных выше, при работе с базой данных чертеже без использования транзакции, следует пользоваться объектом OpenCloseTransaction, который возвращается методом TransactionManager.StartOpenCloseTransaction() и разработан для предоставления механизму ObjectId.Open()/DBObject.Close() поведения, подобному транзакции. Экземпляры класса OpenCloseTransaction лишь имитируют поведение настоящей транзакции, в отличие от TransactionManager.StartTransaction(), который реально запускает её.

Использование объекта OpenCloseTransaction гарантирует, что если  имитированная транзакция OpenCloseTransaction будет прервана, то все объекты, открытые в рамках этой имитированной транзакции с помощью ObjectId.Open(), будут закрыты с помощью метода DBObject.Cancel(), что является более предпочтительным, чем закрытие с использованием метода DBObject.Close(). В то же время, если имитированная транзакция не будет прервана, то все открытые объекты будут закрыты с помощью метода DBObject.Close(), тем самым применив все выполненные изменения.

Пример №3 (не рекомендуемый вариант):  работа с базой данных чертежа с использованием эмуляции транзакции


   1:  // Пример №3 (НЕРЕКОМЕНДУЕМЫЙ ВАРИАНТ):
   2:  // В данном примере показана смесь работы без 
   3:  // транзакции (объект Circle создан в блоке using)
   4:  // и её эмуляции (используется объект 
   5:  // OpenCloseTransaction).
   6:  [Rtm.CommandMethod("AddCircle")]
   7:  public void AddCircle() {
   8:      AppSrv.Document doc =
   9:        cad.DocumentManager.MdiActiveDocument;
  10:      DbSrv.Database db = doc.Database;
  11:      EdInp.Editor ed = doc.Editor;
  12:   
  13:      // Добавим окружность в пространство Model
  14:      using (DbSrv.Circle circle =
  15:        new DbSrv.Circle()) {
  16:          circle.SetDatabaseDefaults(db);
  17:          circle.Center = new Geom.Point3d(
  18:            10.0, 10.0, 10.0);
  19:          circle.Radius = 30.0;
  20:   
  21:          using (DbSrv.OpenCloseTransaction tr =
  22:            db.TransactionManager
  23:            .StartOpenCloseTransaction()) {
  24:   
  25:              using (DbSrv.BlockTable bt =
  26:                 db.BlockTableId.Open(DbSrv.OpenMode
  27:                 .ForRead) as DbSrv.BlockTable) {
  28:   
  29:                  using (DbSrv.BlockTableRecord ms =
  30:                    bt[DbSrv.BlockTableRecord.ModelSpace]
  31:                      .Open(DbSrv.OpenMode.ForWrite)
  32:                      as DbSrv.BlockTableRecord) {
  33:   
  34:                      ms.AppendEntity(circle);
  35:                      tr.AddNewlyCreatedDBObject(circle, true);
  36:   
  37:                      tr.Commit(); // Если закомментировать 
  38:                      // вызов Commit, то изменения выполнены
  39:                      // не будут.
  40:                  }
  41:              }
  42:          }
  43:      } // Здесь будет вызван circle.Dispose().
  44:  }

Согласно заявлению разработчиков Autodesk, чтение и редактирование отдельных объектов указанным выше способом, т.е. без использования транзакции, происходит быстрее, чем с ней. Однако, на практике эта разница в скорости не будет ощущаться конечным пользователем, причём в случае обработки большого набора данных, использование объекта транзакции позволит выполнить эту обработку даже быстрее.

Компания Autodesk рекомендует использовать "классический" способ редактирования объектов - открывая их посредством объекта транзакции (Transaction).

Внимание! 
Существующие методы DBObject.Close() и DBObject.Cancel() в документации ObjectARX 2013 помечены атрибутом [Obsolete("Use Transaction instead")].
Метод ObjectId.Open(), согласно той же документации, не содержит атрибута Obsolete, однако по факту, если в программе ILSpy посмотреть для AutoCAD 2013 SP1.1 содержимое настоящей библиотеки AcDbMgd.dll, а не её заглушку из ObjectARX 2013, то мы увидим, что этот атрибут всё же имеется: [Obsolete("For advanced use only. Use GetObject instead")]. Т.о. для получения объекта, вместо метода  ObjectId.Open() рекомендуется использовать метод ObjectId.GetObject(). Использование метода 
ObjectId.GetObject()будет безопасным например тогда, когда вам нужно быстро открыть объект только для чтения.

Здесь, пожалуй, будет уместно процитировать Александра Ривилиса, несколько разъясняющего этот момент:
Есть варианты работы с транзакцией, и есть варианты работы без транзакции. В стиле .NET варианты работы без транзакции описаны с атрибутом Obsolete, но это не устаревший, а скорее - небезопасный, более сложный и т.д. Это не значит, что его уберут в следующих версиях - кое что вообще нельзя сделать используя транзакции. В частности HandOverTo. Они [Autodesk] просто неудачно выбрали атрибут.

В заключении приведу самый удобный способ работы без транзакции:

Пример №3.1 (рекомендуемый вариант в случаях, когда нужно работать без использования транзакции):  работа с базой данных чертежа с использованием эмуляции транзакции

   1:  // Пример №3.1 (РЕКОМЕНДУЕМЫЙ ВАРИАНТ В СЛУЧАЯХ,
   2:  // КОГДА НУЖНО РАБОТАТЬ БЕЗ ИСПОЛЬЗОВАНИЯ ТРАНЗАКЦИИ):
   3:  // работа с базой данных чертежа с использованием 
   4:  // эмуляции транзакции.
   5:  [Rtm.CommandMethod("AddCircle1")]
   6:  public void AddCircle1() {
   7:      AppSrv.Document doc =
   8:        cad.DocumentManager.MdiActiveDocument;
   9:      DbSrv.Database db = doc.Database;
  10:      EdInp.Editor ed = doc.Editor;
  11:   
  12:      DbSrv.Circle circle = null;
  13:   
  14:      try {
  15:          using (DbSrv.OpenCloseTransaction tr =
  16:            db.TransactionManager
  17:            .StartOpenCloseTransaction()) {
  18:   
  19:              circle = new DbSrv.Circle();
  20:              circle.SetDatabaseDefaults(db);
  21:              circle.Center = new Geom.Point3d(
  22:                10.0, 10.0, 10.0);
  23:              circle.Radius = 30.0;
  24:   
  25:              DbSrv.BlockTable bt = tr.GetObject(
  26:                db.BlockTableId, DbSrv.OpenMode.ForRead)
  27:                  as DbSrv.BlockTable;
  28:   
  29:              DbSrv.BlockTableRecord ms =
  30:                tr.GetObject(bt[DbSrv.BlockTableRecord
  31:                .ModelSpace],
  32:                  DbSrv.OpenMode.ForWrite)
  33:                  as DbSrv.BlockTableRecord;
  34:   
  35:              ms.AppendEntity(circle);
  36:              tr.AddNewlyCreatedDBObject(circle, true);
  37:              tr.Commit(); // Если закомментировать 
  38:              // вызов Commit, то изменения выполнены
  39:              // не будут.
  40:   
  41:              // Если вместо Commit() вызывать Abort(),
  42:              // то нужно не забыть вызвать Dispose()
  43:              // для всех созданных, но не добавленных
  44:              // в базу данных объектов (в нашем случае
  45:              // это circle).
  46:          }
  47:      }
  48:      catch (Exception ex) {
  49:          // Вызываем Dispose() для объектов, не 
  50:          // добавленных в базу данных:
  51:          circle.Dispose();
  52:          ed.WriteMessage("\nError: {0}\n", ex.Message);
  53:      }
  54:  }

В приведённом выше коде красным цветом выделено то, на что стоит обратить внимание. Поскольку все объекты извлекаются из базы данных посредством экземпляра OpenCloseTransaction, то "за кулисами" он берёт на себя вызовы методов Open, Close и Cancel. При этом работа с самим OpenCloseTransaction выглядит полностью идентичной работе с привычным нам объектом Transaction.

Редактирование базы данных чертежа с использованием транзакции

Пример №4:  работа с базой данных чертежа с использованием транзакции

   1:  // Пример №4: работа с базой данных чертежа
   2:  // с использованием транзакции
   3:  [Rtm.CommandMethod("AddCircle2")]
   4:  public void AddCircle2() {
   5:      AppSrv.Document doc =
   6:        cad.DocumentManager.MdiActiveDocument;
   7:      DbSrv.Database db = doc.Database;
   8:      EdInp.Editor ed = doc.Editor;
   9:   
  10:      // Добавим окружность в пространство Model
  11:      DbSrv.Circle circle = null;
  12:   
  13:      try {
  14:          circle = new DbSrv.Circle();
  15:          circle.SetDatabaseDefaults(db);
  16:          circle.Center = new Geom.Point3d(
  17:            10.0, 10.0, 10.0);
  18:          circle.Radius = 30.0;
  19:   
  20:          // Запускаем транзакцию
  21:          using (DbSrv.Transaction tr =
  22:            db.TransactionManager.StartTransaction()) {
  23:              // С помощью транзакции получаем
  24:              // нужный объект
  25:              DbSrv.BlockTable bt = tr.GetObject(
  26:                db.BlockTableId, DbSrv.OpenMode.ForRead)
  27:                  as DbSrv.BlockTable;
  28:              // С помощью транзакции получаем
  29:              // нужный объект
  30:              DbSrv.BlockTableRecord ms = tr.GetObject(
  31:                bt[DbSrv.BlockTableRecord.ModelSpace],
  32:                  DbSrv.OpenMode.ForWrite)
  33:                  as DbSrv.BlockTableRecord;
  34:              ms.AppendEntity(circle);
  35:              tr.AddNewlyCreatedDBObject(circle, true);
  36:              tr.Commit(); // Сохраняем изменения
  37:   
  38:              // или:
  39:              // tr.Abort(); // Отменяем выполненные
  40:              // изменения. В этом случае нужно не забыть
  41:              // вызвать Dispose для Circle:
  42:              // circle.Dispose();            
  43:          }
  44:      }
  45:      catch (Exception ex) {
  46:          // Вызываем Dispose() для объектов, не 
  47:          // добавленных в базу данных:
  48:          circle.Dispose();
  49:          ed.WriteMessage("\nError: {0}\n", ex.Message);
  50:      }
  51:  }

Транзакции могут быть вложенными друг в друга. Менеджер транзакций автоматически ассоциирует все ранее полученные объекты (и операции, производимые над ними) с самой последней транзакцией из стека имеющихся транзакций.

Когда запущена вложенная транзакция - объекты, полученные во внешней транзакции, будут также доступны для работы во вложенной. Если вложенная транзакция будет прервана, то и все операции, выполненные в рамках этой транзакции, над объектами (не важно в какой транзакции эти объекты были получены: во внешней или во внутренней) так же будут отменены и объекты откатываются к тому состоянию, в котором они находились в момент начала работы вложенной транзакции.

Объекты, полученные во вложенной транзакции, станут недействительными, как только она будет прервана. Если же для вложенной транзакции был выполнен метод Commit(), а для внешней - Abort(), то никакие изменения не будут применены, поскольку в результате отката изменений все объекты возвращаются в то состояние, в котором они были на момент начала работы внешней транзакции.

Пример №5:  работа с базой данных чертежа с использованием транзакции (откат вложенных транзакций):

   1:  // Пример №5: работа с базой данных чертежа
   2:  // с использованием транзакции:
   3:  // демонстрация отмены изменений в случае
   4:  // использования вложенных транзакций.
   5:  [Rtm.CommandMethod("AddCircle3")]
   6:  public void AddCircle3() {
   7:      AppSrv.Document doc =
   8:        cad.DocumentManager.MdiActiveDocument;
   9:      DbSrv.Database db = doc.Database;
  10:      EdInp.Editor ed = doc.Editor;
  11:   
  12:      // Добавим окружность в пространство Model
  13:      DbSrv.Circle circle = new DbSrv.Circle();
  14:      circle.SetDatabaseDefaults(db);
  15:      circle.Center = new Geom.Point3d(
  16:        10.0, 10.0, 10.0);
  17:      circle.Radius = 30.0;
  18:   
  19:      // Запускаем транзакцию
  20:      using (DbSrv.Transaction tr_1 =
  21:        db.TransactionManager.StartTransaction()) {
  22:          using (DbSrv.Transaction tr_2 =
  23:            db.TransactionManager.StartTransaction()) {
  24:              // С помощью транзакции получаем
  25:              // нужный объект
  26:              DbSrv.BlockTable bt = tr_2.GetObject(
  27:                db.BlockTableId, DbSrv.OpenMode.ForRead
  28:                )
  29:                  as DbSrv.BlockTable;
  30:              // С помощью транзакции получаем
  31:              // нужный объект
  32:              DbSrv.BlockTableRecord ms =
  33:                tr_2.GetObject(bt[DbSrv
  34:                .BlockTableRecord.ModelSpace],
  35:                  DbSrv.OpenMode.ForWrite)
  36:                  as DbSrv.BlockTableRecord;
  37:              ms.AppendEntity(circle);
  38:              tr_2.AddNewlyCreatedDBObject(circle, true);
  39:              tr_2.Commit(); // Сохраняем изменения
  40:              // во вложенной транзакции
  41:          }
  42:          tr_1.Abort(); // Отменяем выполненные
  43:          // изменения в родительской транзакции.
  44:   
  45:          // В результате все изменения будут отменены,
  46:          // несмотря на то, что во вложенной транзакции
  47:          // они были сохранены посредством Commit().
  48:   
  49:          // не забываем почистить объекты, которые
  50:          // так и не были добавлены в базу данных
  51:          circle.Dispose();
  52:      }
  53:  }

В приведённом выше коде, транзакция tr_1 имеет приоритет над вложенной в неё транзакцией tr_2, поэтому в конечном счёте все изменения будут отменены.

Не смотря на то, что метод ObjectId.GetObject() внешне выглядит как получение объекта без использования транзакции, на самом деле это не так:

Пример №6 (неправильный вариант):  работа с базой данных чертежа с использованием транзакции

   1:  //Пример НЕПРАВИЛЬНОГО использования метода ObjectId.GetObject().
   2:  [Rtm.CommandMethod("AddCircle4")]
   3:  public void AddCircle4() {
   4:   
   5:      AppSrv.Document doc = cad.DocumentManager.MdiActiveDocument;
   6:      DbSrv.Database db = doc.Database;
   7:      EdInp.Editor ed = doc.Editor;
   8:   
   9:      // Добавим окружность в пространство Model...
  10:   
  11:      DbSrv.Circle circle = new DbSrv.Circle();
  12:      circle.SetDatabaseDefaults();
  13:      circle.Center = new Geom.Point3d(10.0, 10.0, 10.0);
  14:      circle.Radius = 30.0;
  15:   
  16:      // В следующей строке кода мы получим Fatal Error:
  17:      DbSrv.BlockTable bt = db.BlockTableId.GetObject(DbSrv.OpenMode.ForRead)
  18:          as DbSrv.BlockTable;
  19:   
  20:      DbSrv.BlockTableRecord ms = bt[DbSrv.BlockTableRecord.ModelSpace]
  21:          .GetObject(DbSrv.OpenMode.ForWrite) as DbSrv.BlockTableRecord;
  22:      ms.AppendEntity(circle);
  23:  }

В строке 17 приведённого выше кода мы получим Fatal Error - это обусловлено тем, что на самом деле метод ObjectId.GetObject() работает в контексте транзакции. "Под капотом", указанный метод прибегает к помощи TransactionManager.GetObject() самого верхнего объекта транзакции. Т.о. для того, чтобы приведённый выше код работал корректно, вызов метода ObjectId.GetObject() следует размещать в контексте транзакции:

Пример №7 (исправленный вариант):  работа с базой данных чертежа с использованием транзакции

   1:  // Пример №7 (РЕКОМЕНДУЕМЫЙ ВАРИАНТ):
   2:  // работа с базой данных чертежа
   3:  // с использованием транзакции
   4:  [Rtm.CommandMethod("AddCircle5")]
   5:  public void AddCircle5() {
   6:   
   7:      AppSrv.Document doc =
   8:        cad.DocumentManager.MdiActiveDocument;
   9:      DbSrv.Database db = doc.Database;
  10:      EdInp.Editor ed = doc.Editor;
  11:   
  12:      // Добавим окружность в пространство Model
  13:      DbSrv.Circle circle = null;
  14:   
  15:      try {
  16:          circle = new DbSrv.Circle();
  17:          circle.SetDatabaseDefaults(db);
  18:          circle.Center = new Geom.Point3d(
  19:            10.0, 10.0, 10.0);
  20:          circle.Radius = 30.0;
  21:   
  22:          using (DbSrv.Transaction tr =
  23:            db.TransactionManager.StartTransaction()) {
  24:              DbSrv.BlockTable bt = db.BlockTableId
  25:                .GetObject(DbSrv.OpenMode.ForRead)
  26:                  as DbSrv.BlockTable;
  27:              DbSrv.BlockTableRecord ms =
  28:                bt[DbSrv.BlockTableRecord.ModelSpace]
  29:                  .GetObject(DbSrv.OpenMode.ForWrite)
  30:                  as DbSrv.BlockTableRecord;
  31:              ms.AppendEntity(circle);
  32:              tr.AddNewlyCreatedDBObject(circle, true);
  33:              tr.Commit(); // Сохраняем выполненные
  34:              // изменения
  35:          }
  36:      }
  37:      catch (Exception ex) {
  38:          // Вызываем Dispose() для объектов, не 
  39:          // добавленных в базу данных:
  40:          circle.Dispose();
  41:          ed.WriteMessage("\nError: {0}\n", ex.Message);
  42:      }
  43:  }

Обратите внимание на то, что для сохранения изменений в приведённом выше коде используется метод Transaction.Commit(). Если закомментировать строку 22, то вместо указанного метода, по умолчанию будет вызваться метод Transaction.Abort(), отменяя тем самым все выполненные изменения.

Т.о. в случае использования метода ObjectId.GetObject(), для сохранения выполненных изменений, или же для отказа от них, следует пользоваться соответствующими методами объекта Transaction, в контексте которого происходит работа.

Если объекты для редактирования вы открыли с помощью объекта транзакции и по каким-либо причинам произойдёт исключение раньше, чем вы вызовете метод Transaction.Commit(), то автоматически будет вызван Transaction.Abort(), в результате чего все незавершённые изменения, выполненные в рамках данной транзакции, будут отменены. Завершённые изменения - это изменения, зафиксированные вызовом метода Transaction.Commit().

Согласно сведениям компании Autodesk, существуют некоторые издержки, связанные с прерыванием транзакции, т.е. издержки, возникающие при вызове метода Transaction.Abort(). Если в своём коде вы в явном виде не вызываете метод Transaction.Commit(), то транзакция прерывается в методе Transaction.Dispose(). Соответственно, даже если вы открывали транзакцию только для чтения, то использование Transaction.Commit() позволит вашему коду работать быстрее.

Комментариев нет: