Tối ưu hóa truy vấn trong Revit API

/Bài viết

    Khi làm việc với Revit API, một trong những tác vụ phổ biến và quan trọng nhất là truy xuất các phần tử (Elements) từ mô hình. Việc này thường được thực hiện thông qua lớp FilteredElementCollector. Tuy nhiên, nếu không được sử dụng đúng cách, việc truy vấn dữ liệu có thể trở nên chậm chạp, đặc biệt với các mô hình lớn, ảnh hưởng nghiêm trọng đến hiệu suất của Add-in. Bài viết này sẽ đi sâu vào cách tối ưu hóa các truy vấn bằng FilteredElementCollector để đảm bảo Add-in của bạn hoạt động nhanh chóng và hiệu quả.

    Tại sao FilteredElementCollector lại quan trọng?

    FilteredElementCollector là công cụ chính để tìm kiếm và lọc các Element trong một đối tượng Document của Revit. Nó cung cấp một loạt các phương thức lọc mạnh mẽ, cho phép bạn chỉ định chính xác loại Element hoặc các thuộc tính mà bạn muốn truy xuất.

    Tuy nhiên, việc không hiểu rõ cơ chế hoạt động của các bộ lọc có thể dẫn đến các truy vấn không hiệu quả, buộc Revit phải duyệt qua hàng ngàn Element không cần thiết, gây lãng phí tài nguyên và thời gian.

    Các loại bộ lọc (ElementFilter) và độ hiệu quả

    Revit API cung cấp nhiều loại ElementFilter khác nhau, mỗi loại có một cơ chế hoạt động riêng và mức độ hiệu quả khác nhau. Hiểu rõ sự khác biệt này là chìa khóa để tối ưu hóa.

    Các bộ lọc được chia thành hai nhóm chính: Quick Filters (Bộ lọc nhanh) và Slow Filters (Bộ lọc chậm).

    Quick Filters (Bộ lọc nhanh)

    Đây là những bộ lọc hoạt động hiệu quả nhất vì chúng sử dụng dữ liệu đã được lập chỉ mục (indexed data) trong cơ sở dữ liệu của Revit. Khi bạn sử dụng Quick Filter, Revit không cần phải duyệt qua từng Element một để kiểm tra điều kiện.

    Các Quick Filters phổ biến:

    • ElementCategoryFilter: Lọc các Element theo BuiltInCategory hoặc Category. Đây là bộ lọc được sử dụng thường xuyên nhất và cực kỳ hiệu quả.

      // Ví dụ: Lấy tất cả các bức tường
      FilteredElementCollector collector = new FilteredElementCollector(doc);
      ICollection<Element> walls = collector.OfCategory(BuiltInCategory.OST_Walls).ToElements();
      
    • ElementClassFilter: Lọc các Element theo kiểu C# của chúng (ví dụ: Wall, Door, FamilyInstance, Level).

      // Ví dụ: Lấy tất cả các đối tượng tường (Revit.DB.Wall)
      FilteredElementCollector collector = new FilteredElementCollector(doc);
      ICollection<Element> walls = collector.OfClass(typeof(Wall)).ToElements();
      
      // Hoặc kết hợp cả Class và Category (rất mạnh)
      collector = new FilteredElementCollector(doc);
      ICollection<Element> doors = collector.OfClass(typeof(FamilyInstance))
                                        .OfCategory(BuiltInCategory.OST_Doors)
                                        .ToElements();
      
    • ElementIsElementTypeFilter: Lọc để chỉ lấy các loại đối tượng (Type Elements - ví dụ: WallType, DoorType).

      // Ví dụ: Lấy tất cả các loại tường
      FilteredElementCollector collector = new FilteredElementCollector(doc);
      ICollection<Element> wallTypes = collector.WhereElementIsElementType().ToElements();
      
    • ElementIsViewIndependentFilter: Lọc các Element độc lập với View (như Levels, Grids).

    • BoundingBoxIntersectsFilter, BoundingBoxContainsFilter, BoundingBoxIsInsideFilter: Các bộ lọc dựa trên hình học vùng giới hạn (Bounding Box) để kiểm tra sự giao cắt, chứa đựng hoặc nằm trong một vùng nhất định. Các bộ lọc này thường được kết hợp với một Outline hoặc BoundingBoxXYZ.

    • LogicalAndFilter / LogicalOrFilter: Kết hợp nhiều bộ lọc. Khi kết hợp Quick Filters, kết quả vẫn là một Quick Filter hiệu quả.

    Nguyên tắc vàng: Luôn cố gắng sử dụng Quick Filters trước để giảm thiểu số lượng Element mà Revit phải kiểm tra.

    Slow Filters (Bộ lọc chậm)

    Các bộ lọc này đòi hỏi Revit phải kiểm tra từng Element một để xác định xem nó có đáp ứng điều kiện lọc hay không. Điều này làm cho chúng kém hiệu quả hơn đáng kể so với Quick Filters, đặc biệt trong các mô hình lớn.

    Các Slow Filters phổ biến:

    • ParameterFilter: Lọc các Element dựa trên giá trị của các tham số (Parameters). Đây là một bộ lọc rất mạnh nhưng thường là Slow Filter nếu không được tối ưu. Tuy nhiên, một số loại ParameterFilter có thể được tối ưu hóa nếu sử dụng đúng StorageTypeBuiltInParameter.

      • Mẹo tối ưu: Nếu bạn cần lọc theo giá trị tham số, hãy cố gắng kết hợp nó với một hoặc nhiều Quick Filters trước.
      // Ví dụ kém hiệu quả (nếu không có Quick Filter trước):
      // Lấy tất cả các Element có tham số "Comments" chứa "Important"
      ParameterValueProvider provider = new ParameterValueProvider(new ElementId(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS));
      FilterStringRuleEvaluator evaluator = new FilterStringContains();
      FilterRule rule = new FilterStringRule(provider, evaluator, "Important", false); // false = case-insensitive
      ElementFilter filter = new ElementFilter(rule);
      
      FilteredElementCollector collector = new FilteredElementCollector(doc);
      ICollection<Element> importantElements = collector.WherePasses(filter).ToElements(); // Có thể chậm
      
      • Ví dụ hiệu quả hơn (kết hợp Quick Filter):
      // Lấy tất cả các cửa có tham số "Comments" chứa "Important"
      FilteredElementCollector collector = new FilteredElementCollector(doc);
      ICollection<Element> importantDoors = collector.OfCategory(BuiltInCategory.OST_Doors) // Quick Filter trước
                                                   .WherePasses(filter) // Sau đó mới đến Slow Filter
                                                   .ToElements();
      
    • ElementLevelFilter: Lọc các Element nằm trên một Level cụ thể. Thường được sử dụng cùng với các Quick Filter khác.

    • FamilyInstanceFilter: Lọc các FamilyInstance của một FamilySymbol cụ thể.

    • StructuralInstanceUsageFilter, StructuralWallUsageFilter, v.v.

    Chiến lược tối ưu hóa truy vấn với FilteredElementCollector

    Để đảm bảo các truy vấn của bạn nhanh và hiệu quả, hãy áp dụng các chiến lược sau:

    Ưu tiên Quick Filters

    Luôn bắt đầu truy vấn của bạn với một hoặc nhiều Quick Filters để giảm thiểu đáng kể số lượng Element mà các bộ lọc tiếp theo phải xử lý. Revit sẽ đánh giá các bộ lọc theo thứ tự bạn thêm vào FilteredElementCollector.

    // BAD (có thể duyệt qua tất cả các phần tử rồi mới lọc theo loại)
    // FilteredElementCollector collector = new FilteredElementCollector(doc)
    //                                     .WhereElementIsNotElementType() // Slow Filter
    //                                     .OfClass(typeof(Wall)); // Quick Filter
    
    // GOOD (lọc theo Class trước, hiệu quả hơn)
    FilteredElementCollector collector = new FilteredElementCollector(doc)
                                         .OfClass(typeof(Wall)) // Quick Filter trước
                                         .WhereElementIsNotElementType(); // Sau đó mới là Slow Filter (nếu cần)
    

    Kết hợp các bộ lọc bằng LogicalAndFilter hoặc chuỗi phương thức

    Khi cần áp dụng nhiều điều kiện, bạn có thể xâu chuỗi các phương thức lọc (OfCategory().OfClass().WherePasses()) hoặc sử dụng LogicalAndFilter. Revit thường tự động tối ưu hóa khi bạn xâu chuỗi các Quick Filters.

    // Lấy tất cả các thể hiện của cửa (FamilyInstance)
    FilteredElementCollector collector = new FilteredElementCollector(doc);
    ICollection<Element> doorInstances = collector.OfClass(typeof(FamilyInstance)) // Quick Filter (theo Class)
                                                .OfCategory(BuiltInCategory.OST_Doors) // Quick Filter (theo Category)
                                                .ToElements();
    

    Tránh sử dụng .ToElements() quá sớm

    .ToElements() sẽ thực thi truy vấn và tải tất cả các Element phù hợp vào bộ nhớ. Nếu bạn có nhiều bộ lọc hoặc thao tác tiếp theo, hãy cố gắng áp dụng tất cả các bộ lọc trước khi gọi .ToElements().

    Chỉ lấy những gì bạn cần

    • Sử dụng WhereElementIsNotElementType() nếu bạn chỉ muốn các thể hiện (instance) và WhereElementIsElementType() nếu bạn chỉ muốn các loại (type).

    • Nếu bạn chỉ cần kiểm tra sự tồn tại của một Element chứ không cần toàn bộ danh sách, hãy sử dụng Any(), FirstElement(), hoặc FirstOrDefault() thay vì ToElements().

      // Chỉ kiểm tra xem có bất kỳ bức tường nào không
      bool hasWalls = new FilteredElementCollector(doc).OfClass(typeof(Wall)).Any();
      
      // Lấy bức tường đầu tiên tìm thấy
      Wall firstWall = new FilteredElementCollector(doc).OfClass(typeof(Wall)).Cast<Wall>().FirstOrDefault();
      

    Cache các kết quả truy vấn nếu có thể

    Nếu bạn cần truy xuất cùng một tập hợp Element nhiều lần trong cùng một phiên chạy Add-in, hãy cân nhắc lưu trữ kết quả vào một biến List<Element> hoặc Dictionary<ElementId, Element> thay vì lặp lại truy vấn mỗi lần. Điều này sẽ tránh được việc Revit phải thực hiện lại quá trình lọc tốn kém.

    Bạn đã từng gặp phải vấn đề về hiệu suất khi truy vấn dữ liệu trong Revit API chưa? Hãy chia sẻ kinh nghiệm của bạn ở phần bình luận dưới đây nhé!