Skip to content
C#
事务与再生

事务与再生

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();
        }
    }
}