事务与数据库
事务是 AutoCAD .NET API 中最核心也最容易出错的概念。所有对 Database 的操作都必须在事务保护下进行。
为什么需要事务
AutoCAD 是一个多文档、支持撤销/重做的应用程序。事务机制提供:
- 原子性:多个操作要么全部提交,要么全部回滚
- 一致性:保证数据库始终处于有效状态
- 隔离性:事务之间的操作不相互干扰
- 持久性:提交后变更持久化到 DWG
🔬 事务本质上是 ObjectARX C++ 层的
AcDbTransaction的托管包装。每个打开的对象都注册到事务中,事务结束时自动清理。
Transaction 生命周期
Database db = doc.Database;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// 1. 在事务内打开/操作对象
Circle circle = tr.GetObject(id, OpenMode.ForRead) as Circle;
// 2. 显式提交
tr.Commit();
// 3. using 块结束时:
// - 如果已 Commit → 变更永久生效
// - 如果未 Commit → 自动回滚所有变更
// - Dispose 释放非托管资源
}关键状态图
StartTransaction
│
▼
活动状态 ←── 操作对象(GetObject, AppendEntity...)
│
├── Commit() → 变更生效
│
└── Dispose() → 未提交的变更回滚
(已提交则无操作)事务中的对象获取
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// GetObject —— 通过 ObjectId 获取对象
Line line = tr.GetObject(lineId, OpenMode.ForRead) as Line;
// 获取符号表
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
// 获取命名对象字典
DBDictionary dict = (DBDictionary)tr.GetObject(
db.NamedObjectsDictionaryId, OpenMode.ForRead);
}OpenMode 详解
| OpenMode | 含义 | 使撤销变大 | 阻止其他操作 |
|---|---|---|---|
ForRead |
只读 | 否 | 否 |
ForWrite |
读写 | 是 | 否(同文档不同事务可读) |
ForNotify |
通知 | — | — |
// 典型的先读后写升级
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent.Layer == "OLD")
{
ent.UpgradeOpen(); // 升级为写
ent.Layer = "NEW";
ent.DowngradeOpen(); // 降级回读(可选)
}添加新对象到事务
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// 方式 1:通过 BlockTableRecord 添加
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord modelSpace = (BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
Circle circle = new Circle(center, Vector3d.ZAxis, 50);
modelSpace.AppendEntity(circle);
tr.AddNewlyCreatedDBObject(circle, true); // ← 关键!纳入事务
tr.Commit();
}🚨 新创建的 DBObject 必须调用
tr.AddNewlyCreatedDBObject(obj, true)将其纳入事务管理。否则 Commit 时不会写入数据库,且可能在 Dispose 时崩溃。
事务嵌套
AutoCAD 支持嵌套事务:
using (Transaction outer = db.TransactionManager.StartTransaction())
{
// 外层操作
outer.AddNewlyCreatedDBObject(entity1, true);
using (Transaction inner = db.TransactionManager.StartTransaction())
{
// 内层操作
inner.AddNewlyCreatedDBObject(entity2, true);
inner.Commit(); // entity2 暂存(并不立即写入 DWG)
}
// 如果 outer 未 Commit → entity2 的变更也会回滚
outer.Commit(); // entity1 + entity2 同时写入
}🔬 内层事务提交后,变更写入"外层事务的缓冲区"而非直接写入 DWG。只有最外层事务 Commit 后,所有嵌套变更才同步写入数据库。
OpenCloseTransaction(性能优化)
// 传统事务:适用于修改操作
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// 产生撤销记录,开销较大
}
// 开放关闭事务:适用于只读查询、数据导出
using (Transaction tr = db.TransactionManager.StartOpenCloseTransaction())
{
// 更快、无撤销记录、内存占用更少
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord modelSpace = (BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
foreach (ObjectId id in modelSpace)
{
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent != null)
{
// 只读遍历大量实体 —— OpenCloseTransaction 更合适
}
}
}⚡ 对比:遍历 100,000 个实体时,
OpenCloseTransaction比普通Transaction快 2-3 倍,且不增加 DWG 文件大小(无撤销数据)。
Database 级别操作
获取数据库信息
// 单位
double insUnits = db.Insunits; // 插入单位
double measurement = db.Measurement; // 0=英制 1=公制
// 范围(需先更新)
db.UpdateExt(true); // true = 包含已删除实体占用的空间
Extents3d ext = db.Extmax;
Extents3d allExt = new Extents3d(db.Extmin, db.Extmax);
// 系统变量
object snapMode = Application.GetSystemVariable("OSMODE");
Application.SetSystemVariable("OSMODE", 0);
// 字典
DBDictionary layoutDict = (DBDictionary)tr.GetObject(
db.LayoutDictionaryId, OpenMode.ForRead);
// 自定义数据存储(命名对象字典)
DBDictionary nod = (DBDictionary)tr.GetObject(
db.NamedObjectsDictionaryId, OpenMode.ForRead);
if (!nod.Contains("MYAPPDATA"))
{
Xrecord xrec = new Xrecord();
xrec.Data = new ResultBuffer(
new TypedValue((int)DxfCode.Text, "My custom data"));
nod.UpgradeOpen();
ObjectId xrecId = nod.SetAt("MYAPPDATA", xrec);
tr.AddNewlyCreatedDBObject(xrec, true);
}读取/保存 DWG
// 保存当前文档
doc.Database.SaveAs(newPath, DwgVersion.Current);
// 以只读方式侧载另一个 DWG
using (Database extDb = new Database(false, true)) // false=不关联文档, true=无撤销
{
extDb.ReadDwgFile(externalPath, FileOpenMode.OpenForReadAndAllShare, false, "");
using (Transaction tr = extDb.TransactionManager.StartOpenCloseTransaction())
{
// 读取外部 DWG 的数据
}
}事务中的对象关系
ObjectId vs DBObject 生命周期
// ✅ ObjectId 在事务外仍然有效(轻量值类型)
ObjectId savedId;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Line line = new Line(start, end);
modelSpace.AppendEntity(line);
tr.AddNewlyCreatedDBObject(line, true);
savedId = line.ObjectId; // 保存 ID 到事务外
tr.Commit();
}
// savedId 在此仍有效
// 🚨 DBObject 在事务外无效!
// 事务结束后,通过 GetObject 获取的 DBObject 引用被释放
ObjectId id;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Line line = tr.GetObject(id, OpenMode.ForRead) as Line;
double len = line.Length; // ✅ 事务内可以访问
tr.Commit();
}
// double len = line.Length; // ❌ line 已被释放!AccessViolation!🚨 这是 AutoCAD 二次开发中最常见的崩溃原因。永远不要在事务外持有
DBObject引用。应该持有ObjectId,需要时通过新事务重新GetObject。
事务工具类封装
public static class TransactionHelper
{
/// <summary>
/// 在只读事务中执行操作
/// </summary>
public static void Read(Database db, Action<Transaction> action)
{
using (Transaction tr = db.TransactionManager.StartOpenCloseTransaction())
{
action(tr);
// 只读操作不 Commit
}
}
/// <summary>
/// 在读写事务中执行操作
/// </summary>
public static void Write(Database db, Action<Transaction> action)
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
action(tr);
tr.Commit();
}
}
/// <summary>
/// 获取模型空间
/// </summary>
public static BlockTableRecord GetModelSpace(Database db, Transaction tr)
{
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
return (BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
}
}
// 使用
TransactionHelper.Write(db, tr =>
{
Line line = new Line(new Point3d(0,0,0), new Point3d(100,0,0));
BlockTableRecord ms = TransactionHelper.GetModelSpace(db, tr);
ms.AppendEntity(line);
tr.AddNewlyCreatedDBObject(line, true);
});