事务

事务是类似于上下文的对象,可封装对Revit模型所做的任何更改。只有在存在打开的活动事务处理时,才能对文档进行任何更改。尝试在事务之外更改文档将引发异常。在提交活动事务之前,更改不会成为模型的一部分。因此,事务中的所有更改都可以显式或隐式回滚(通过析构函数)。在任何给定时间,每个文档只能打开一个事务。一个事务可以由一个或多个操作组成。

Revit API中有三个与事务相关的主要类:

  • Transaction 事务
  • SubTransaction 子事务
  • TransactionGroup 事务组

本节将更深入地讨论这些类中的每一个类。对文档进行更改时只需要Transaction类。其他类可用于更好地组织更改。

***注意:***如果事务是从外部线程或外部非模态对话框启动的,则会引发异常。事务只能从支持的API工作流启动,例如外部命令、事件、更新程序或回调的一部分。

本节中的页面

  • Transaction Classes 事务类
  • Transactions in Events 事件中的事务
  • Failure Handling Options 故障处理选项
  • Getting Element Geometry and AnalyticalModel
    获取元素几何图形和分析模型
  • Temporary transactions 临时事务

Transaction Classes 事务类

三个事务对象共享通用方法:

表51:通用事务对象方法

Method 方法 Description 描述
Start 将启动上下文
Commit 结束上下文并将所有更改提交到文档
Rollback 结束上下文并放弃对文档的所有更改
GetStatus 返回事务对象的当前状态

除了返回当前状态的GetStatus()方法外,Start、Commit和RollBack方法还返回TransactionStatus,指示该方法是否成功。可用的TransactionStatus值包括:

表52:TransactionStatus值

Status 状态 Description 描述
Uninitialized 对象实例化后的初始值;上下文尚未启动
Started 事务对象已成功启动(调用了Start)
RolledBack 事务对象已成功回滚(调用了Rollback)
Committed 已成功提交事务对象(调用了Commit)
Pending 试图提交或回滚事务对象,但由于失败,该过程尚未完成,正在等待最终用户的响应(在非模态对话框中)。一旦故障处理完成,状态将自动更新(为Committed或RolledBack状态)。

Transaction 事务

事务是对Revit模型进行任何更改所需的上下文。一次只能打开一个事务;不允许嵌套。每个事务都必须有一个名称,一旦事务成功提交,该名称将列在Revit的“撤消”菜单中。

代码区域23-1:使用事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void CreatingSketch(UIApplication uiApplication)
{
Autodesk.Revit.DB.Document document = uiApplication.ActiveUIDocument.Document;
Autodesk.Revit.ApplicationServices.Application application = uiApplication.Application;

// Create a few geometry lines. These lines are transaction (not in the model),
// therefore they do not need to be created inside a document transaction.
XYZ Point1 = XYZ.Zero;
XYZ Point2 = new XYZ(10, 0, 0);
XYZ Point3 = new XYZ(10, 10, 0);
XYZ Point4 = new XYZ(0, 10, 0);

Line geomLine1 = Line.CreateBound(Point1, Point2);
Line geomLine2 = Line.CreateBound(Point4, Point3);
Line geomLine3 = Line.CreateBound(Point1, Point4);

// This geometry plane is also transaction and does not need a transaction
XYZ origin = XYZ.Zero;
XYZ normal = new XYZ(0, 0, 1);
Plane geomPlane = Plane.CreateByNormalAndOrigin(normal, origin);

// In order to a sketch plane with model curves in it, we need
// to start a transaction because such operations modify the model.

// All and any transaction should be enclosed in a 'using'
// block or guarded within a try-catch-finally blocks
// to guarantee that a transaction does not out-live its scope.
using (Transaction transaction = new Transaction(document))
{
if (transaction.Start("Create model curves") == TransactionStatus.Started)
{
// Create a sketch plane in current document
SketchPlane sketch = SketchPlane.Create(document,geomPlane);

// Create a ModelLine elements using the geometry lines and sketch plane
ModelLine line1 = document.Create.NewModelCurve(geomLine1, sketch) as ModelLine;
ModelLine line2 = document.Create.NewModelCurve(geomLine2, sketch) as ModelLine;
ModelLine line3 = document.Create.NewModelCurve(geomLine3, sketch) as ModelLine;

// Ask the end user whether the changes are to be committed or not
TaskDialog taskDialog = new TaskDialog("Revit");
taskDialog.MainContent = "Click either [OK] to Commit, or [Cancel] to Roll back the transaction.";
TaskDialogCommonButtons buttons = TaskDialogCommonButtons.Ok | TaskDialogCommonButtons.Cancel;
taskDialog.CommonButtons = buttons;

if (TaskDialogResult.Ok == taskDialog.Show())
{
// For many various reasons, a transaction may not be committed
// if the changes made during the transaction do not result a valid model.
// If committing a transaction fails or is canceled by the end user,
// the resulting status would be RolledBack instead of Committed.
if (TransactionStatus.Committed != transaction.Commit())
{
TaskDialog.Show("Failure", "Transaction could not be committed");
}
}
else
{
transaction.RollBack();
}
}
}
}

SubTransaction 子事务

SubTransaction可以用来封装一组模型修改操作。子事务是可选的。修改模型时不需要它们。它们是一种方便的工具,允许将较大的任务逻辑拆分为较小的任务。子事务只能在已经打开的事务中创建,并且必须在事务关闭(提交或回滚)之前关闭(提交或回滚)。与事务不同,子事务可以嵌套,但任何嵌套的子事务都必须在封闭子事务之前关闭。子事务没有名称,因为它们不显示在Revit的“撤消”菜单上。

TransactionGroup事务组

TransactionGroup允许将几个独立的事务分组在一起,这使组的所有者有机会一次处理多个事务。当一个事务组要被关闭时,它可以被回滚,这意味着属于该组的所有以前提交的事务都将被回滚。如果不回滚,则可以提交或同化组。在前一种情况下,所有提交的事务(在组内)将保持原样。在后一种情况下,组内的事务将被合并到一个单独的事务中,该事务将使用组的名称。

事务组只能在没有打开的事务时启动,并且必须在关闭所有封闭的事务(回滚或提交)后关闭。事务组可以嵌套,但必须在封闭组关闭之前关闭任何嵌套组。事务组是可选的。修改模型时不需要这些参数。

下面的示例演示如何使用Assimilate()方法使用TransactionGroup来合并两个单独的Transactions。下面的代码将导致单个Undo项添加到Undo菜单,名称为“Level and Grid”。

代码区域23-2:将多个事务合并到一个TransactionGroup中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public void CompoundOperation(Autodesk.Revit.DB.Document document)
{
// All and any transaction group should be enclosed in a 'using' block or guarded within
// a try-catch-finally blocks to guarantee that the group does not out-live its scope.
using (TransactionGroup transGroup = new TransactionGroup(document, "Level and Grid"))
{
if (transGroup.Start() == TransactionStatus.Started)
{
// We are going to call two methods, each having its own local transaction.
// For our compound operation to be considered successful, both the individual
// transactions must succeed. If either one fails, we will roll our group back,
// regardless of what transactions might have already been committed.

if (CreateLevel(document, 25.0) && CreateGrid(document, new XYZ(0,0,0), new XYZ(10,0,0)))
{
// The process of assimilating will merge the two (or any number of) committed
// transaction together and will assign the grid's name to the one resulting transaction,
// which will become the only item from this compound operation appearing in the undo menu.
transGroup.Assimilate();
}
else
{
// Since we could not successfully finish at least one of the individual
// operation, we are going to roll the entire group back, which will
// undo any transaction already committed while this group was open.
transGroup.RollBack();
}
}
}
}

public bool CreateLevel(Autodesk.Revit.DB.Document document, double elevation)
{
// All and any transaction should be enclosed in a 'using'
// block or guarded within a try-catch-finally blocks
// to guarantee that a transaction does not out-live its scope.
using (Transaction transaction = new Transaction(document, "Creating Level"))
{
// Must start a transaction to be able to modify a document

if( TransactionStatus.Started == transaction.Start())
{
if (null != Level.Create(document, elevation))
{
// For many various reasons, a transaction may not be committed
// if the changes made during the transaction do not result a valid model.
// If committing a transaction fails or is canceled by the end user,
// the resulting status would be RolledBack instead of Committed.
return (TransactionStatus.Committed == transaction.Commit());
}

// For we were unable to create the level, we will roll the transaction back
// (although on this simplified case we know there weren't any other changes)

transaction.RollBack();
}
}
return false;
}

public bool CreateGrid(Autodesk.Revit.DB.Document document, XYZ p1, XYZ p2)
{
// All and any transaction should be enclosed in a 'using'
// block or guarded within a try-catch-finally blocks
// to guarantee that a transaction does not out-live its scope.
using (Transaction transaction = new Transaction(document, "Creating Grid"))
{
// Must start a transaction to be able to modify a document
if (TransactionStatus.Started == transaction.Start())
{
// We create a line and use it as an argument to create a grid
Line gridLine = Line.CreateBound(p1, p2);

if ((null != gridLine) && (null != Grid.Create(document, gridLine)))
{
if (TransactionStatus.Committed == transaction.Commit())
{
return true;
}
}

// For we were unable to create the grid, we will roll the transaction back
// (although on this simplified case we know there weren't any other changes)

transaction.RollBack();
}
}
return false;
}

事件中的事务

在事件期间修改文档

事件不会自动打开事务。因此,文档不会在事件期间被修改,除非事件的某个处理程序通过在事务内部进行更改来修改文档。如果事件处理程序打开一个事务,则要求它也关闭它(提交它或回滚它),否则所有更改都将被丢弃。

请注意,在某些事件(例如DocumentClosing事件)期间,不允许修改活动文档。如果事件处理程序试图在此类事件期间进行修改,则将引发异常。事件文档指示事件是否为只读。

DocumentChanged事件

DocumentChanged事件在每个事务提交、撤消或重做后引发。这是一个只读事件,旨在使外部数据与Revit数据库的状态保持同步。若要更新Revit数据库以响应图元中的更改,请使用动态模型更新框架。

失败处理选项

失败处理选项是在事务结束时如何处理失败(如果有)的选项。在使用Transaction.SetFailureHandlingOptions()方法调用Transaction.Commit()或Transaction.RollBack()之前,可以随时设置失败处理选项。但是,在提交或回滚事务后,这些选项将返回到各自的默认设置。

SetFailureHandlingOptions()方法接受FailureHandlingOptions对象作为参数。无法创建此对象,必须使用GetFailureHandlingOptions()方法从事务中获取它。通过调用相应的Set方法(如SetClearAfterRollback())来设置选项。以下各节将更详细地讨论故障处理选项。

ClearAfterRollback

此选项控制是否应在回滚事务后清除所有警告。默认值为False。

DelayedMiniWarnings DelayedMinimax

此选项控制是否在当前正在结束的事务结束时显示小警告(如果有),或者是否应将其推迟到下一个事务结束时显示。当不希望在每个步骤结束时显示中间警告,而希望等到整个链完成时,通常在事务链中使用此方法。

多个事务可能会延迟验证。第一个未将此选项设置为True的事务将显示其自己的所有警告(如果有)以及可能从以前的事务累积的所有警告。默认值为False。

注意:此选项在模态模式下被忽略(请参阅下面的ForcedModalHandling)。

ForcedModalHandling

此选项控制最终的故障是以模态方式还是以非模态方式处理。预设值为True。请注意,如果设置了非模态故障处理,则处理事务可能会异步完成,这意味着从Commit或RollBack调用返回时,事务将尚未完成(状态将为“Pending”)。

SetFailuresPreprocessor

如果提供此接口,则在事务结束时发现失败时调用此接口。预处理器可以检查当前的故障,甚至尝试解决它们。有关详细信息,请参阅故障发布和处理。

SetTransactionFinalizer

终结器是一个接口,如果提供了该接口,则可用于在事务结束时执行自定义操作。请注意,它不会在调用Commit()或RollBack()方法时调用,而是仅在提交或回滚过程完成后调用。事务终结器必须实现ITransactionFinalizer接口,该接口需要定义两个函数:

  • OnCommitted - called at the end of committing a transaction
    OnCommitted -在提交事务结束时调用
  • OnRolledBack - called at the end of rolling back a transaction
    OnRolledBack -在回滚事务结束时调用

注意:由于终结器是在事务完成后调用的,因此除非启动新事务,否则无法从终结器修改文档。

获取元素几何图形和分析模型

创建新图元或修改图元后,需要重新生成和自动连接图元以将更改传播到整个模型中。如果不进行再生(以及相关的自动连接),则无法获得“几何”属性和“元素的分析模型”(在创建新元素的情况下),或者它们可能无效。在访问图元的“几何”或“分析模型”之前,了解再生发生的方式和时间非常重要。

尽管再生和自动连接对于传播模型中所做的更改是必要的,但这可能很耗时。最好是这些事件只在必要时发生。

当修改模型的事务成功提交时,或者调用Document.Regenerate()或Document.AutoJoinElements()方法时,自动进行重新生成和自动联接。Regenerate()和AutoJoinElements()只能在打开的事务中调用。应该注意的是,Regeneration()方法可能会失败,在这种情况下,RegenerationFailedException将被抛出。如果发生这种情况,则需要通过回滚当前事务或子事务来回滚对文档的更改。

有关更多信息,请参见分析模型和几何形状。

下面的示例程序演示了事务如何填充这些属性:

代码区域23-3:填充几何和分析模型属性的事务处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void TransactionDuringElementCreation(UIApplication uiApplication, Level level)
{
Autodesk.Revit.DB.Document document = uiApplication.ActiveUIDocument.Document;

// Build a location line for the wall creation
XYZ start = new XYZ(0, 0, 0);
XYZ end = new XYZ(10, 10, 0);
Autodesk.Revit.DB.Line geomLine = Line.CreateBound(start, end);

// All and any transaction should be enclosed in a 'using'
// block or guarded within a try-catch-finally blocks
// to guarantee that a transaction does not out-live its scope.
using (Transaction wallTransaction = new Transaction(document, "Creating wall"))
{
// To create a wall, a transaction must be first started
if (wallTransaction.Start() == TransactionStatus.Started)
{
// Create a wall using the location line
Wall wall = Wall.Create(document, geomLine, level.Id, true);

// the transaction must be committed before you can
// get the value of Geometry and AnalyticalModel.

if (wallTransaction.Commit() == TransactionStatus.Committed)
{
Autodesk.Revit.DB.Options options = uiApplication.Application.Create.NewGeometryOptions();
Autodesk.Revit.DB.GeometryElement geoelem = wall.get_Geometry(options);
Autodesk.Revit.DB.Structure.AnalyticalModel analyticalmodel = wall.GetAnalyticalModel();
}
}
}
}

此示例的事务时间轴如下所示:

临时事务

并不总是需要提交事务。事务框架还允许回滚事务。这在事务处理过程中出现错误时很有用,但也可以直接用作创建临时事务的技术。

使用临时事务对于某些类型的分析可能很有用。例如,如果应用程序希望在墙或其他对象被洞口剪切之前从其提取几何属性,则应将临时事务与Document.Delete()结合使用。当应用程序删除剪切目标元素的元素时,剪切元素的几何图形将恢复到其原始状态(在重新生成文档之后)。

要使用临时事务处理,请执行以下操作:

  1. 使用Transaction构造函数实例化Transaction,并为其分配一个名称。
  2. 调用Transaction.Start()
  3. 对文档进行临时更改(元素修改、删除或创建)
  4. 重新生成文档
  5. 提取所需的几何图形和属性
  6. 调用Transaction.RollBack()将文档还原到以前的状态。

这种技术也适用于子事务。

注:翻译自Revit API Developers Guide