Маленький пример на тему того, как не меняя программный код можно управлять способом работы с базой данных чертежа: с использованием транзакции, или же с её эмуляцией. Используя такой подход можно моментально поменять способ работы сразу во многих местах, не внося правки в исходный код.
// © Андрей Бушман, 2014 // Commands.cs // AutoCAD 2009 SP3 // Код предназначен для демонстрации того, как можно, не внося изменений в // исходный код, менять способ работы с базой данных чертежа: с использованием // транзакции или же с её эмуляцией. using System; #if AUTOCAD using cad = Autodesk.AutoCAD.ApplicationServices.Application; using Ap = Autodesk.AutoCAD.ApplicationServices; using Db = Autodesk.AutoCAD.DatabaseServices; using Ed = Autodesk.AutoCAD.EditorInput; using Gm = Autodesk.AutoCAD.Geometry; using Rt = Autodesk.AutoCAD.Runtime; #endif [assembly: Rt.CommandClass(typeof(Bushman.CAD.Sandbox.TransactionGames.Commands ))] namespace Bushman.CAD.Sandbox.TransactionGames { public sealed class Commands { /// <summary> /// Тестовая команда /// </summary> [Rt.CommandMethod("SwitchTransactionSample")] public void SwitchTransactionSample() { Ap.Document doc = cad.DocumentManager.MdiActiveDocument; if (null == doc) return; Db.Database db = doc.Database; Db.TransactionManager tm = db.TransactionManager; Ed.Editor ed = doc.Editor; Ed.PromptKeywordOptions pso = new Ed.PromptKeywordOptions( "Использовать транзакцию?"); String yes = "Yes"; String no = "No"; pso.Keywords.Add(yes); pso.Keywords.Add(no); pso.AppendKeywordsToMessage = true; pso.Keywords.Default = yes; Ed.PromptResult pr = ed.GetKeywords(pso); ed.WriteMessage("Вы выбрали: {0}\n", pr.StringResult); if (Ed.PromptStatus.OK != pr.Status) { ed.WriteMessage("Выполнение команды прервано.\n"); } Boolean transaction_use = pr.StringResult == yes; // Т.к. OpenCloseTransaction наследуется от Transaction, то // получить Transaction можно следующим образом: using (Db.Transaction tr = GetTransaction(tm, transaction_use)) { Db.DBText text = new Db.DBText(); text.SetDatabaseDefaults(db); text.Height = 30; Double y = 100; String text_string = String.Empty; if (transaction_use) { text_string = "Текст создан с использованием транзакции."; } else { y = 200; text_string = "Текст создан с эмуляцией транзакции."; } text.Position = new Gm.Point3d(100, y, 0); text.TextString = text_string; Db.BlockTableRecord model_space = tr.GetObject( db.CurrentSpaceId, Db.OpenMode.ForWrite) as Db.BlockTableRecord; model_space.AppendEntity(text); tr.AddNewlyCreatedDBObject(text, true); tr.Commit(); } } /// <summary> /// Данный метод позволяет выбрать способ работы с базой данных /// чертежа: с использованием транзакции или же с её эмуляцией. /// </summary> /// <param name="tm">Менеджер транзакций</param> /// <param name="emulate"><c>true</c> - использовать транзакцию; /// <c>false</c> - эмулировать транзакцию. /// </param> /// <returns>Возвращается объект <c>Transaction</c> ( /// <c>OpenCloseTransaction</c> унаследован от этого класса).</returns> Db.Transaction GetTransaction(Db.TransactionManager tm, Boolean transaction_use) { Db.Transaction tr = transaction_use ? tm.StartTransaction() : tm.StartOpenCloseTransaction(); return tr; } } }
3 комментария:
В чём преимущество работы без транзакции? В Автокаде работал только с транзакциями, а в Теиге только без них. Так же во втором случае не требуется блокировать документ. Поэтому использовал подобный подход:
У меня есть библиотека CADEngine в виде класса с функциями для более удобной работы в разных кад-системах.
public Transaction StartTransaction(bool lock_document = false) {
if (lock_document) {
DocUnlock();
var doc = Application.DocumentManager.MdiActiveDocument;
LastDocLoc = doc.LockDocument();
}
return Transaction.StartTransaction();
}
public void DocUnlock() {
if (LastDocLoc == null) return;
LastDocLoc.Dispose();
LastDocLoc = null;
}
public void Commit(Transaction tr) {
tr.Commit();
DocUnlock();
}
Но после этого я решил не городить огород с лишними "using cad.StartTransaction"... и сделал такой метод:
public void MakeInTransaction(Action action) {
MakeInTransaction(null, action);
}
public void MakeInTransaction(Transaction tr, Action action) {
var need_commiting = tr == null;
if (UseTransactions) {
tr = tr ?? StartTransaction(!WithoutDocument);
}
try {
action(tr);
} finally {
if (need_commiting && UseTransactions && tr != null) {
Commit(tr);
tr.Dispose();
}
}
}
Таким образом, я в коде не беспокоясь о блокировке документа, о создании и об отправке транзакции пишу:
cad.MakeInTransaction(tr => {
// код, использующий транзакцию
});
Всё остальное делается само.
Если же мне нужно в особых случаях не создавать новую транзакцию, а действовать в рамках старой, я пишу:
cad.MakeInTransaction(trn, tr => {
// Даже если существующая, вроде бы, транзакция trn окажется == null, tr будет создана новая.
});
Изначально, исторически, никаких транзакций не было. Позднее компания Autodesk добавила механизм транзакций и стала рекомендовать использовать именно его в подавляющем большинстве случаев. Использование транзакции в значительной степени снижает количество ошибок, допускаемое прикладными разработчиками. Например, без использования транзакции, может происходить утечка памяти в случае необработанных исключений, а если объект создан в рамках транзакции, инициализированной в блоке using, то даже в случае необработанного исключения память, выделенная под этот объект, будет освобождена. Подробней читай здесь.
Некоторые вещи сделать с транзакцией невозможно в принципе. Пример: использование HandOwerTo.
Александр Наумович меня поправил:
Транзакции были изначально. Просто в ObjectARX в 99% случаев все примеры без использования транзакций, а в AutoCAD .NET API с точностью до наоборот.
Отправить комментарий