事务与再生
Revit 的事务(Transaction)机制比 AutoCAD 更严格、更多变。理解事务、子事务、事务组和再生是避免命令失败的关键。
为什么需要事务
Revit 是一个参数化的 BIM 数据库,任何修改(创建、修改、删除元素)都必须包裹在事务中。这保证了:
- 原子性:多步操作要么全部成功要么全部回滚
- 一致性:修改后的模型仍然满足所有约束
- 撤销支持:每个事务是撤销/恢复的单元
🚨 没有活动事务时调用任何修改 API →
Modification is forbidden...异常。
Transaction 基本用法
[Transaction(TransactionMode.Manual)]
public Result Execute(ExternalCommandData commandData, ...)
{
Document doc = commandData.Application.ActiveUIDocument.Document;
// 创建事务
using (Transaction tx = new Transaction(doc, "创建墙体"))
{
tx.Start(); // 开始事务
// ... 修改操作 ...
tx.Commit(); // 提交(或 tx.RollBack() 回滚)
} // Dispose 自动回滚未提交的事务
return Result.Succeeded;
}Transaction 状态
┌─────────────┐
│ 未启动 │ ← new Transaction(doc, "name")
└──────┬──────┘
│ tx.Start()
▼
┌─────────────┐
│ 活动状态 │ ← 可以执行修改操作
└──────┬──────┘
│
┌────┴────┐
▼ ▼
Commit() RollBack()
│ │
▼ ▼
┌─────┐ ┌─────┐
│已提交│ │已回滚│
└─────┘ └─────┘TransactionMode
// 手动事务 —— 推荐,完全控制
[Transaction(TransactionMode.Manual)]
// 需要在代码中手动 tx.Start() 和 tx.Commit()
// 自动事务 —— 简单场景
[Transaction(TransactionMode.Automatic)]
// Revit 自动创建外层事务,您仍可创建子事务
// 但不推荐:隐藏了事务边界,容易出问题
// 只读 —— 不修改数据库
[Transaction(TransactionMode.ReadOnly)]
// 无法修改任何内容,但性能最佳💡 只要能确定命令只读,就用
TransactionMode.ReadOnly:最快,且不会忘记提交。
子事务(SubTransaction)
用于精细化管理——子事务可以独立回滚而不影响父事务中其他子事务的修改:
using (Transaction tx = new Transaction(doc, "批量处理"))
{
tx.Start();
foreach (Wall wall in walls)
{
using (SubTransaction subTx = new SubTransaction(doc))
{
subTx.Start();
try
{
wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)
?.Set($"处理于 {DateTime.Now}");
subTx.Commit(); // 每面墙独立提交
}
catch
{
subTx.RollBack(); // 一个墙失败不影响其他墙
}
}
}
tx.Commit();
}
// 撤销时:Ctrl+Z → 撤销整个 tx(包含所有成功的子事务)
// 不能只撤销某几个子事务事务组(TransactionGroup)
// TransactionGroup 用于将多个 Transaction 绑定成一个"宏事务"
using (TransactionGroup tg = new TransactionGroup(doc, "生成楼层"))
{
tg.Start();
foreach (Level level in levels)
{
using (Transaction tx = new Transaction(doc, $"处理 {level.Name}"))
{
tx.Start();
CreateWallsAtLevel(doc, level);
tx.Commit();
}
}
tg.Assimilate(); // 合并:多个 Transaction 变成一个撤销单元
// 或 tg.Commit(); // 提交(不合并,每个 Transaction 独立可撤销)
}| 操作 | 效果 |
|---|---|
tg.Assimilate() |
所有内部事务合并为一个撤销步骤(一个 Ctrl+Z) |
tg.Commit() |
提交 TransactionGroup 但不合并撤销步骤 |
tg.RollBack() |
回滚内部所有已提交的 Transaction |
再生(Regeneration)
Revit 的参数化引擎在修改后会重新计算整个模型——这是一个昂贵的过程。
// Manual 再生 —— 推荐,完全控制
[Regeneration(RegenerationOption.Manual)]
// 命令结束时手动调用 doc.Regenerate()
// Automatic 再生 —— 默认
[Regeneration(RegenerationOption.Automatic)]
// Revit 在每个外部事务提交后自动再生
// 如果你有多个连续事务,每个都触发再生 → 极慢// 使用 Manual 再生模式时,在需要更新显示时手动调用
doc.Regenerate();
// 只刷新活动视图(比全文档再生轻量)
uidoc.RefreshActiveView();⚡ 批量修改(如导入 1000 个构件)时,使用
TransactionMode.Manual+RegenerationOption.Manual,在所有事务提交后手动doc.Regenerate()——耗时从几十秒可降至几秒。
失败处理与警告
Revit 在事务提交时可能抛出警告或错误(如约束冲突、几何无效):
// 设置失败处理策略
using (Transaction tx = new Transaction(doc, "处理"))
{
tx.Start();
// 有错误时继续(警告在提交时被抑制)
FailureHandlingOptions options = tx.GetFailureHandlingOptions();
options.SetFailuresPreprocessor(new SuppressAllWarnings());
tx.SetFailureHandlingOptions(options);
// ... 可能产生警告的操作 ...
tx.Commit();
}
// 自定义失败处理
public class SuppressAllWarnings : IFailuresPreprocessor
{
public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
{
// 获取所有失败消息
IList<FailureMessageAccessor> failures = failuresAccessor.GetFailureMessages();
foreach (FailureMessageAccessor failure in failures)
{
// 删除所有警告
failuresAccessor.DeleteWarning(failure);
}
return FailureProcessingResult.Continue;
}
}💡 更精细的做法是根据
FailureDefinitionId判断哪些警告可以忽略,哪些需要处理:
public class SelectiveWarningSuppressor : IFailuresPreprocessor
{
public FailureProcessingResult PreprocessFailures(FailuresAccessor fa)
{
foreach (FailureMessageAccessor fma in fa.GetFailureMessages())
{
// 忽略"房间标签在房间外"警告
if (fma.GetFailureDefinitionId() == BuiltInFailures.RoomFailures.RoomTagNotInRoom)
fa.DeleteWarning(fma);
// "元素太短无法创建" → 不能忽略,回滚
// else if (...) return FailureProcessingResult.ProceedWithRollBack;
}
return FailureProcessingResult.Continue;
}
}Transaction 注意事项
🚨 不能嵌套 Transaction
// ❌ 错误:Transaction 不能嵌套
using (Transaction outer = new Transaction(doc, "外层"))
{
outer.Start();
using (Transaction inner = new Transaction(doc, "内层"))
{
inner.Start(); // 💥 异常!已有活动事务
}
}
// ✅ 正确:内层用 SubTransaction
using (Transaction outer = new Transaction(doc, "外层"))
{
outer.Start();
using (SubTransaction inner = new SubTransaction(doc))
{
inner.Start(); // ✅
inner.Commit();
}
outer.Commit();
}🚨 在 Transaction 外修改
// ❌ 任何修改必须在事务内
wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS).Set("备注");
// 💥 "Modification is forbidden" 异常🚨 Commit 后不能继续操作
// ❌ 提交后继续操作
tx.Start();
Wall wall = Wall.Create(doc, curve, wallType.Id, level.Id, height, 0, false, false);
tx.Commit();
// wall 仍然有效,但修改它需要新事务
wall.get_Parameter(...).Set("value"); // 💥 没有活动事务🚨 事件处理器中的事务
// DocumentChanged 等事件处理器中,Revit 仍在处理自己的事务
// 不能在此创建新事务
doc.Application.DocumentChanged += (s, e) =>
{
using (Transaction tx = new Transaction(doc, "反应")) // 💥 异常!
{
tx.Start();
// ...
}
};
// ✅ 正确:使用 IExternalEventHandler 延迟处理
public class MyEventHandler : IExternalEventHandler
{
public void Execute(UIApplication app)
{
Document doc = app.ActiveUIDocument.Document;
using (Transaction tx = new Transaction(doc, "反应"))
{
tx.Start();
// 在这里修改是安全的
tx.Commit();
}
}
public string GetName() => "MyEventHandler";
}事务工具类封装
public static class TransactionHelper
{
/// <summary>
/// 在单个事务中执行操作
/// </summary>
public static void Execute(Document doc, string name, Action action)
{
using (Transaction tx = new Transaction(doc, name))
{
tx.Start();
action();
tx.Commit();
}
}
/// <summary>
/// 对每个元素执行子事务,单元素失败不影响其他
/// </summary>
public static void ExecuteWithSubTransactions(
Document doc, string name,
IEnumerable<Element> elements, Action<Element> action)
{
using (Transaction tx = new Transaction(doc, name))
{
tx.Start();
foreach (Element element in elements)
{
using (SubTransaction subTx = new SubTransaction(doc))
{
subTx.Start();
try
{
action(element);
subTx.Commit();
}
catch
{
subTx.RollBack();
}
}
}
tx.Commit();
}
}
}