Транзакции и изменение элементов


Revit API предоставляет нам широкие возможности по изменению документов. Одновременно, он ставит нам и некоторые ограничения, чтобы мы случайно ничего не сломали.

Одним из таких ограничений являются транзакции. Обычно мы взаимодействуем с ними, когда раскрываем список последних команд у стрелочки “Отменить”:

Список последних транзакций в документе Список последних транзакций в документе

В чём же состоит ограничение? Мы не можем модифицировать документ вне транзакции. Смысл тут в том, что если мы что-то набедокурим нашим плагином, мы можем легко всё отменить. Давайте проверим: напишем код, который всем элементам на активном виде будет записывать значение Id в параметр “Комментарии”.

public class FirstRevitCommand : IExternalCommand
{
    static AddInId addinId = new AddInId(new Guid("0F296157-A2DC-4532-BB1B-6D6D3462F15A"));

    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
    {
        UIDocument uidoc = commandData.Application.ActiveUIDocument;
        Document doc = uidoc.Document;
        View activeView = doc.ActiveView;

        var collection = new FilteredElementCollector(doc, activeView.Id)
            .WhereElementIsNotElementType()
            .ToElements();

        foreach (var element in collection)
        {
            Parameter parameter = element.LookupParameter("Комментарии");

            if (parameter != null)
            {
                parameter.Set(element.Id.ToString());
            }
        }

        return Result.Succeeded;
    }
}

В данном коде практически всё рассматривалось в предыдущих статьях. Остановлюсь чуть подробнее на поиске параметра.

В данном случае я ищу параметр через метод LookupParameter(), которому передаю строку — точное имя параметра. Он возвращает либо параметр, если найдёт его, либо null, если не найдёт. Поэтому далее я проверяю, что мы получили параметр, а не null, и если так, то с помощью метода Set() записываю значение в виде строки.

Запустим плагин через Addin Manager, и что же мы увидим: Попытка изменить модель вне транзакции

Попытка изменить модель вне транзакции

Если запустить плагин из Ревита, то увидим примерно тоже самое: Попытка изменить модель вне транзакции

В общем, не надо быть знатоком английского уровня C1: Исключение “Попытка изменить модель вне транзакции” означает, что мы эту транзакцию забыли. Исправим код:

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
    UIDocument uidoc = commandData.Application.ActiveUIDocument;
    Document doc = uidoc.Document;
    View activeView = doc.ActiveView;

    var collection = new FilteredElementCollector(doc, activeView.Id)
        .WhereElementIsNotElementType()
        .ToElements();

    foreach (var element in collection)
    {
        using (Transaction t = new Transaction(doc, "Моя первая транзакция"))
        {
            t.Start();

            Parameter parameter = element.LookupParameter("Комментарии");

            if (parameter != null)
            {
                parameter.Set(element.Id.ToString());
            }

            t.Commit();
        }
    }

    return Result.Succeeded;
}

Что я сделал: добавил выражение

using (Transaction t = new Transaction(doc, "Моя первая транзакция"))

И обернул весь код по получению и изменению параметра в фигурные скобки.

Методы Start() и Commit() начинают и завершают транзакцию, соответственно.

Нам необязательно завершать транзакцию. Мы можем её откатить назад (RollBack()). Зачем? Например, так можно вычислить площадь проёмов в стене: взять площадь стены, удалить все проёмы, взять новую площадь и откатить транзакцию.

В данном случае я специально поставил транзакцию внутри цикла (правильнее было окружить весь цикл транзакцией), чтобы получить следующий результат: 35 транзакций за одно действие 35 транзакций за одно действие. Не очень удобно (особенно если вышла ошибка). Что же делать? Во-первых, нет никакой необходимости начинать транзакцию внутри каждой итерации цикла. Мы можем вынести её перед циклом, начать до старта цикла и завершить после. Транзакция будет одна и её легко можно будет отменить через Ctrl-Z.

Но иногда нам требуется завершить транзакцию и начать новую. Например, мы создали элемент и хотим что-то с ним сделать дальше. Это невозможно: пока мы не завершим транзакцию, Revit будет думать, что элемента нет в базе данных. Тогда мы можем воспользоваться группой транзакций:

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
    UIDocument uidoc = commandData.Application.ActiveUIDocument;
    Document doc = uidoc.Document;
    View activeView = doc.ActiveView;

    var collection = new FilteredElementCollector(doc, activeView.Id)
        .WhereElementIsNotElementType()
        .ToElements();

    using (TransactionGroup transactionGroup = new TransactionGroup(doc, "Моя первая транзакция"))
    {
        transactionGroup.Start();

        foreach (var element in collection)
        {
            using (Transaction t = new Transaction(doc, "Моя первая транзакция"))
            {
                t.Start();

                Parameter parameter = element.LookupParameter("Комментарии");

                if (parameter != null)
                {
                    parameter.Set(element.Id.ToString());
                }

                t.Commit();
            }
        }

        transactionGroup.Assimilate();
    }

    return Result.Succeeded;
}

Все транзакции соберутся в одну — результат достигнут.

А на этом всё! Приглашаю вас в свой телеграм-канал о Revit API и до новых встреч!