Miklix

Sử dụng SysExtension Framework để tìm ra lớp con nào để khởi tạo trong Dynamics AX 2012

Đã xuất bản: lúc 00:26:10 UTC 16 tháng 2, 2025
Cập nhật lần cuối: lúc 08:43:27 UTC 12 tháng 1, 2026

Bài viết này mô tả cách sử dụng khung SysExtension ít được biết đến trong Dynamics AX 2012 và Dynamics 365 for Operations để tạo các lớp con dựa trên các thuộc tính trang trí, cho phép thiết kế hệ thống phân cấp lớp xử lý một cách dễ dàng mở rộng.


Trang này được dịch máy từ tiếng Anh để có thể tiếp cận được với nhiều người nhất có thể. Thật không may, dịch máy vẫn chưa phải là công nghệ hoàn thiện, do đó có thể xảy ra lỗi. Nếu bạn thích, bạn có thể xem phiên bản tiếng Anh gốc tại đây:

Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012

Thông tin trong bài viết này dựa trên Dynamics AX 2012 R3. Thông tin này có thể không đúng với các phiên bản khác. (Cập nhật: Tôi xác nhận rằng thông tin trong bài viết này cũng đúng với Dynamics 365 for Operations)

Khi triển khai các lớp xử lý trong Dynamics AX, bạn thường phải đối mặt với việc tạo ra một hệ thống phân cấp lớp, trong đó mỗi lớp con tương ứng với một giá trị enum hoặc có một số liên kết dữ liệu khác. Một thiết kế kinh điển là sử dụng phương thức khởi tạo trong lớp cha, phương thức này có một câu lệnh switch để xác định lớp nào sẽ được khởi tạo dựa trên dữ liệu đầu vào.

Về nguyên tắc, phương pháp này hoạt động tốt, nhưng nếu bạn có nhiều đầu vào khác nhau (nhiều phần tử trong một kiểu liệt kê hoặc đầu vào là sự kết hợp của nhiều giá trị khác nhau), việc bảo trì có thể trở nên tẻ nhạt và dễ xảy ra lỗi. Hơn nữa, thiết kế này luôn có nhược điểm là bạn sẽ cần phải sửa đổi phương thức khởi tạo nếu bạn thêm một lớp con mới hoặc thay đổi lớp con nào nên được sử dụng dựa trên đầu vào nào.

May mắn thay, có một cách thanh lịch hơn nhiều, nhưng không may là cũng ít được biết đến hơn, để thực hiện việc này, đó là sử dụng khung SysExtension.

Khung này tận dụng các thuộc tính mà bạn có thể sử dụng để trang trí các lớp con của mình, giúp hệ thống xác định lớp con nào nên được sử dụng để xử lý việc gì. Bạn vẫn cần một phương thức khởi tạo, nhưng nếu được thực hiện đúng cách, bạn sẽ không bao giờ phải sửa đổi nó khi thêm các lớp con mới.

Hãy xem xét một ví dụ giả định rằng bạn sẽ triển khai một hệ thống phân cấp thực hiện một số loại xử lý dựa trên bảng InventTrans. Quá trình xử lý nào cần thực hiện phụ thuộc vào StatusReceipt và StatusIssue của các bản ghi, cũng như việc các bản ghi đó có liên quan đến SalesLine, PurchLine hay không. Ngay từ bây giờ, bạn đã thấy rất nhiều sự kết hợp khác nhau.

Giả sử bạn biết rằng hiện tại bạn chỉ cần xử lý một số ít các tổ hợp, nhưng bạn cũng biết rằng theo thời gian, bạn sẽ được yêu cầu xử lý nhiều tổ hợp hơn nữa.

Để mọi thứ tương đối đơn giản, giả sử hiện tại bạn chỉ cần xử lý các bản ghi liên quan đến SalesLine với StatusIssue là ReservPhysical hoặc ReservOrdered, tất cả các kết hợp khác có thể bỏ qua tạm thời, nhưng vì bạn biết rằng bạn sẽ phải xử lý chúng sau này, bạn nên thiết kế mã của mình sao cho dễ dàng mở rộng.

Hiện tại, cấu trúc phân cấp của bạn có thể trông như thế này:

  • Bộ xử lý của tôiBộ xử lý_Bán hàngBộ xử lý_Bán hàng_Đặt trướcBộ xử lý_Bán hàng_Đặt trướcVật lý

Giờ đây, bạn có thể dễ dàng triển khai một phương thức trong lớp cha để tạo ra một lớp con dựa trên ModuleInventPurchSales và kiểu liệt kê StatusIssue. Nhưng khi đó bạn sẽ cần phải sửa đổi lớp cha mỗi khi thêm một lớp con, và đó không thực sự là ý tưởng của kế thừa trong lập trình hướng đối tượng. Xét cho cùng, bạn không cần phải sửa đổi RunBaseBatch hoặc SysOperationServiceBase mỗi khi thêm một công việc xử lý hàng loạt mới.

Thay vào đó, bạn có thể sử dụng framework SysExtension. Điều này sẽ yêu cầu bạn thêm một lớp khác, lớp này cần kế thừa từ SysAttribute. Lớp này sẽ được sử dụng như thuộc tính mà bạn có thể dùng để trang trí các lớp xử lý của mình.

Lớp này rất giống với cách bạn tạo một lớp hợp đồng dữ liệu cho việc triển khai SysOperation, ở chỗ nó sẽ có một số thành viên dữ liệu và các phương thức tham số để lấy và thiết lập các giá trị đó.

Trong trường hợp của chúng ta, ClassDeclaration có thể trông giống như thế này:

class MyProcessorSystemAttribute extends SysAttribute
{
    ModuleInventPurchSales  module;
    StatusIssue             statusIssue;
    StatusReceipt           statusReceipt
}

Bạn cần tạo một phương thức `new()` để khởi tạo tất cả các thành viên dữ liệu. Nếu muốn, bạn có thể gán giá trị mặc định cho một số hoặc tất cả chúng, nhưng tôi chưa từng làm điều đó.

public void new(ModuleInventPurchSales  _module,
                StatusIssue             _statusIssue,
                StatusReceipt           _statusReceipt)
{
    ;

    super();

    module          = _module;
    statusIssue     = _statusIssue;
    statusReceipt   = _statusReceipt;
}

Và bạn cũng nên triển khai phương thức `parm` cho mỗi thành viên dữ liệu, nhưng tôi đã bỏ qua phần đó ở đây vì tôi chắc chắn bạn biết cách làm điều đó - nếu không, hãy coi đó như một bài tập ;-)

Giờ đây, bạn có thể sử dụng lớp thuộc tính của mình để trang trí cho từng lớp xử lý. Ví dụ, phần khai báo lớp có thể trông như thế này:

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::None,
                            StatusReceipt::None)]
class MyProcessor_Sales extends MyProcessor
{
}

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::ReservOrdered,
                            StatusReceipt::None)]
class MyProcessor_Sales_ReservOrdered extends MyProcessor_Sales
{
}

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::ReservPhysical,
                            StatusReceipt::None)]
class MyProcessor_Sales_ReservPhysical extends MyProcessor_Sales
{
}

Tất nhiên bạn có thể đặt tên các lớp của mình theo bất kỳ cách nào bạn muốn, phần quan trọng ở đây là bạn cần trang trí các lớp bằng các thuộc tính tương ứng với loại xử lý mà chúng thực hiện. (Nhưng hãy nhớ rằng có các quy ước đặt tên cho hệ thống phân cấp lớp trong Dynamics AX và luôn nên tuân theo những quy ước đó, nếu có thể).

Giờ đây, khi bạn đã trang trí các lớp của mình để xác định loại xử lý mà mỗi lớp thực hiện, bạn có thể tận dụng khung SysExtension để tạo các đối tượng của các lớp con khi cần thiết.

Trong lớp cha (MyProcessor), bạn có thể thêm một phương thức khởi tạo như sau:

public static MyProcessor construct(ModuleInventPurchSales _module,
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
    MyProcessor                 ret;
    MyProcessorSystemAttribute  attribute;
    ;

    attribute = new MyProcessorSystemAttribute( _module,
                                                _statusIssue,
                                                _statusReceipt);

    ret = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(MyProcessor), attribute);

    if (!ret)
    {
        //  no class found
        //  here you could throw an error, instantiate a default
        //  processor instead, or just do nothing, up to you
    }

    return ret;
}

Phần thực sự thú vị - và thực sự là mục đích (xin lỗi vì cách chơi chữ) của toàn bộ bài đăng này - là phương thức getClassFromSysAttribute() trong lớp SysExtensionAppClassFactory. Phương thức này nhận vào đó tên của lớp cha trong một hệ thống phân cấp (và lớp cha này không nhất thiết phải nằm ở đầu hệ thống phân cấp; điều đó đơn giản có nghĩa là chỉ những lớp kế thừa lớp này mới đủ điều kiện) và một đối tượng thuộc tính.

Sau đó, nó trả về một đối tượng thuộc lớp kế thừa lớp cha được chỉ định và được trang trí bằng một thuộc tính tương ứng.

Rõ ràng là bạn có thể thêm bao nhiêu phần kiểm tra hoặc logic bổ sung vào phương thức khởi tạo tùy ý, nhưng điều quan trọng cần nhớ ở đây là một khi đã triển khai, bạn sẽ không bao giờ phải sửa đổi phương thức này nữa. Bạn có thể thêm các lớp con vào hệ thống phân cấp và miễn là bạn đảm bảo trang trí chúng một cách thích hợp, phương thức khởi tạo sẽ tìm thấy chúng ngay cả khi chúng không tồn tại khi phương thức được viết.

Còn về hiệu năng thì sao? Thành thật mà nói, tôi chưa thử đo hiệu năng, nhưng trực giác của tôi cho rằng cách này có lẽ hoạt động kém hơn so với thiết kế câu lệnh switch cổ điển. Tuy nhiên, xét đến việc hầu hết các vấn đề về hiệu năng trong Dynamics AX đều do truy cập cơ sở dữ liệu gây ra, tôi nghĩ bạn không cần quá lo lắng về điều đó.

Tất nhiên, nếu bạn đang triển khai một thứ gì đó yêu cầu tạo ra hàng nghìn đối tượng một cách nhanh chóng, bạn có thể muốn tìm hiểu thêm, nhưng trong các trường hợp điển hình mà bạn chỉ khởi tạo một đối tượng duy nhất để thực hiện một số quá trình xử lý dài dòng, tôi nghi ngờ điều đó sẽ gây ra vấn đề gì. Ngoài ra, xét đến mẹo khắc phục sự cố của tôi (đoạn tiếp theo), có vẻ như khung SysExtension dựa vào bộ nhớ đệm, vì vậy trong một hệ thống đang hoạt động, tôi nghi ngờ nó sẽ gây ra ảnh hưởng đáng kể đến hiệu suất.

Khắc phục sự cố: Nếu phương thức khởi tạo không tìm thấy các lớp con của bạn ngay cả khi bạn chắc chắn rằng chúng đã được trang trí đúng cách, đó có thể là vấn đề về bộ nhớ đệm. Hãy thử xóa bộ nhớ đệm trên cả máy khách và máy chủ. Việc khởi động lại AOS không nhất thiết phải thực hiện, nhưng đó có thể là biện pháp cuối cùng.

Đọc thêm

Nếu bạn thích bài viết này, bạn cũng có thể thích những gợi ý sau:


Chia sẻ trên BlueskyChia sẻ trên FacebookChia sẻ trên LinkedInChia sẻ trên TumblrChia sẻ trên XChia sẻ trên LinkedInGhim trên Pinterest

Mikkel Christensen

Về tác giả

Mikkel Christensen
Mikkel là người sáng lập và chủ sở hữu của miklix.com. Ông có hơn 20 năm kinh nghiệm làm lập trình viên máy tính/nhà phát triển phần mềm chuyên nghiệp và hiện đang làm việc toàn thời gian cho một tập đoàn CNTT lớn của Châu Âu. Khi không viết blog, ông dành thời gian rảnh rỗi cho nhiều sở thích, thú vui và hoạt động, có thể được phản ánh ở một mức độ nào đó trong nhiều chủ đề được đề cập trên trang web này.