- Published on
Làm việc với các giao dịch (Transactions)
Khi bạn phát triển các ứng dụng Revit API, việc tương tác và thay đổi dữ liệu trong mô hình Revit là một tác vụ cốt lõi. Tuy nhiên, không phải mọi thay đổi đều có thể thực hiện trực tiếp. Revit thực thi một nguyên tắc nghiêm ngặt về tính toàn vẹn dữ liệu: mọi thay đổi đối với mô hình phải được bao bọc trong một Transaction (giao dịch). Hiểu và sử dụng Transaction
đúng cách là điều bắt buộc để tạo ra các Add-in ổn định và đáng tin cậy.
- Transaction trong Revit API: Nguyên tắc vàng để thay đổi mô hình BIM
- Cách sử dụng Transaction cơ bản
- Các loại Transaction khác
- Các lưu ý quan trọng khi sử dụng Transaction
Transaction trong Revit API: Nguyên tắc vàng để thay đổi mô hình BIM
Trong ngữ cảnh của Revit API, một Transaction
có thể được hình dung như một "khối" các hoạt động thay đổi dữ liệu trong mô hình. Tất cả các thay đổi bên trong một Transaction
đều được xem là một đơn vị logic duy nhất.
Tại sao Transaction
lại quan trọng?
- Đảm bảo tính toàn vẹn dữ liệu (Data Integrity): Revit là một cơ sở dữ liệu mạnh mẽ. Nếu một phần mềm bên ngoài (Add-in của bạn) thay đổi dữ liệu một cách lộn xộn, nó có thể làm hỏng mô hình hoặc tạo ra trạng thái không nhất quán.
Transaction
giúp Revit kiểm soát chặt chẽ các thay đổi này. - Hỗ trợ Undo/Redo: Mọi
Transaction
thành công đều được ghi lại trong lịch sửUndo
của Revit. Điều này cho phép người dùng hoàn tác (Undo) toàn bộ các thay đổi được thực hiện trongTransaction
đó chỉ với một thao tác, đảm bảo trải nghiệm người dùng liền mạch. - Tính nguyên tử (Atomicity): Một
Transaction
hoặc là hoàn thành tất cả các thay đổi của nó thành công (commit), hoặc là không có thay đổi nào được áp dụng (rollback) nếu có lỗi xảy ra. Điều này ngăn chặn mô hình ở trạng thái nửa chừng, lỗi thời. - Quản lý bộ nhớ và hiệu suất:
Transaction
giúp Revit quản lý các thay đổi một cách hiệu quả hơn, đặc biệt khi có nhiều thao tác sửa đổi diễn ra.
Transaction
cơ bản
Cách sử dụng Đối tượng Transaction
thuộc namespace Autodesk.Revit.DB
. Cách phổ biến và được khuyến nghị để sử dụng Transaction
là dùng khối using
trong C#, đảm bảo rằng Transaction
được xử lý đúng cách ngay cả khi có lỗi xảy ra.
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIDocument uidoc = commandData.Application.ActiveUIDocument;
Document doc = uidoc.Document;
// BẮT ĐẦU MỘT TRANSACTION
// Tham số đầu tiên là Document mà Transaction sẽ hoạt động trên đó.
// Tham số thứ hai là tên của Transaction, sẽ hiển thị trong lịch sử Undo của Revit.
using (Transaction tr = new Transaction(doc, "Thay đổi chiều cao tường mẫu"))
{
// 1. Bắt đầu Transaction
tr.Start();
try
{
// 2. Thực hiện các thay đổi đối với mô hình tại đây
// Ví dụ: Tìm một bức tường và thay đổi chiều cao của nó
FilteredElementCollector collector = new FilteredElementCollector(doc);
Wall wall = collector.OfClass(typeof(Wall)).Cast<Wall>().FirstOrDefault();
if (wall != null)
{
Parameter heightParam = wall.LookupParameter("Unconnected Height");
if (heightParam != null && heightParam.IsReadWrite)
{
// Đặt chiều cao mới (ví dụ: 5 mét)
// Lưu ý: Các tham số thường yêu cầu giá trị nội bộ của Revit (Internal Units)
// Ở đây, tui giả định đơn vị mặc định là feet, 5 mét ~ 16.404 feet
heightParam.Set(5.0 * 3.28084); // 5 mét đổi ra feet
TaskDialog.Show("Thành công", $"Chiều cao của tường '{wall.Name}' đã được thay đổi.");
}
}
else
{
TaskDialog.Show("Cảnh báo", "Không tìm thấy tường nào trong mô hình.");
}
// 3. Commit Transaction nếu mọi thứ thành công
tr.Commit();
}
catch (Exception ex)
{
// 4. Rollback Transaction nếu có lỗi xảy ra
tr.RollBack();
message = "Có lỗi xảy ra khi thực hiện giao dịch: " + ex.Message;
return Result.Failed;
}
} // Khối 'using' sẽ tự động gọi Dispose() và đảm bảo Transaction được kết thúc.
return Result.Succeeded;
}
Transaction
khác
Các loại Ngoài Transaction
cơ bản, Revit API còn cung cấp các loại giao dịch chuyên biệt khác:
SubTransaction
SubTransaction
cho phép bạn thực hiện các giao dịch nhỏ hơn bên trong một Transaction
lớn hơn. Các SubTransaction
có thể được RollBack
độc lập mà không ảnh hưởng đến toàn bộ Transaction
chính. Điều này hữu ích khi bạn có nhiều bước thay đổi và muốn khả năng hoàn tác một phần.
Tuy nhiên, cần lưu ý:
- Bạn không thể
Commit
mộtSubTransaction
độc lập. Nó chỉ có thể đượcRollBack
hoặc đượcCommit
(theo nghĩa là được chấp nhận) khiTransaction
chính đượcCommit
. - Nếu
Transaction
chính bịRollBack
, tất cả cácSubTransaction
bên trong nó cũng sẽ bịRollBack
.
using (Transaction mainTr = new Transaction(doc, "Giao dịch chính phức tạp"))
{
mainTr.Start();
// Thay đổi 1: Tạo Level mới
using (SubTransaction subTr1 = new SubTransaction(doc))
{
subTr1.Start();
Level newLevel = Level.Create(doc, 10.0); // Tạo level ở độ cao 10 feet
subTr1.Commit(); // SubTransaction được chấp nhận (chưa thực sự lưu)
}
// Thay đổi 2: Tạo một bức tường và sau đó Rollback nó nếu không tìm thấy cửa
using (SubTransaction subTr2 = new SubTransaction(doc))
{
subTr2.Start();
// ... Code tạo tường ...
TaskDialog.Show("Info", "Đã tạo tường tạm thời.");
// Giả sử có một điều kiện kiểm tra
bool foundDoor = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors).Any();
if (!foundDoor)
{
subTr2.RollBack(); // Hoàn tác riêng SubTransaction này
TaskDialog.Show("Cảnh báo", "Không tìm thấy cửa, tường tạm thời đã bị hủy.");
}
else
{
subTr2.Commit(); // Chấp nhận SubTransaction này
TaskDialog.Show("Info", "Đã tìm thấy cửa, tường tạm thời được giữ lại.");
}
}
// Cuối cùng, Commit giao dịch chính
mainTr.Commit();
}
TransactionGroup
TransactionGroup
cho phép bạn nhóm nhiều Transaction
độc lập thành một đơn vị Undo
duy nhất. Điều này cực kỳ hữu ích khi Add-in của bạn thực hiện nhiều bước mà mỗi bước cần một Transaction
riêng (ví dụ: do yêu cầu của một số API) nhưng bạn muốn người dùng chỉ cần Undo
một lần để hoàn tác tất cả.
using (TransactionGroup tg = new TransactionGroup(doc, "Nhóm các giao dịch thay đổi"))
{
tg.Start(); // Bắt đầu nhóm giao dịch
// Giao dịch 1: Tạo tường
using (Transaction tr1 = new Transaction(doc, "Tạo tường"))
{
tr1.Start();
// Code tạo tường
tr1.Commit();
}
// Giao dịch 2: Tạo cửa
using (Transaction tr2 = new Transaction(doc, "Tạo cửa"))
{
tr2.Start();
// Code tạo cửa
tr2.Commit();
}
// Hoàn tất nhóm giao dịch. Bây giờ, nếu người dùng Undo, cả tạo tường và tạo cửa sẽ bị hoàn tác.
tg.Assimilate(); // "Hấp thụ" tất cả các Transaction con thành một Undo duy nhất
// Hoặc tg.RollBack(); để hủy bỏ toàn bộ nhóm Transaction nếu có lỗi xảy ra.
}
Transaction
Các lưu ý quan trọng khi sử dụng - Không bao giờ thay đổi mô hình ngoài
Transaction
: Đây là quy tắc quan trọng nhất. Nếu bạn cố gắng thay đổi mộtElement
bên ngoài mộtTransaction
đang hoạt động, Revit sẽ ném ra lỗi. - Chỉ một
Transaction
có thể hoạt động tại một thời điểm: Bạn không thể có haiTransaction
cùngStart()
trên cùng mộtDocument
đồng thời. - Luôn sử dụng khối
using
: Điều này đảm bảoTransaction
đượcDispose
đúng cách và tránh rò rỉ tài nguyên, cũng như tự động xử lý các trường hợpCommit()
hoặcRollBack()
. - Đặt tên
Transaction
rõ ràng: Tên này xuất hiện trong danh sáchUndo
của người dùng, vì vậy hãy đặt tên có ý nghĩa (ví dụ: "Tạo hàng loạt cột", "Thay đổi vật liệu tường"). - Xử lý lỗi: Luôn bao bọc các thao tác thay đổi mô hình trong khối
try-catch
và gọiRollBack()
trong khốicatch
để đảm bảo mô hình không bị hỏng nếu có lỗi. - Khi nào thì không cần
Transaction
? Khi bạn chỉ đọc dữ liệu từ mô hình mà không thay đổi gì, bạn không cầnTransaction
.
Bạn đã từng gặp phải những lỗi liên quan đến Transaction
trong quá trình phát triển Add-in Revit chưa? Hãy chia sẻ kinh nghiệm của bạn ở phần bình luận nhé.