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
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 trang trí thuộc tính, cho phép thiết kế phân cấp lớp xử lý dễ dàng mở rộng.
Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012
Thông tin trong bài đăng này dựa trên Dynamics AX 2012 R3. Thông tin có thể hoặc không thể áp dụng cho các phiên bản khác. (Cập nhật: Tôi có thể xác nhận rằng thông tin trong bài viết này cũng áp dụng cho 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 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ố kết hợp dữ liệu khác. Một thiết kế cổ điển là sau đó có một phương thức xây dựng trong siêu lớp, có một công tắc xác định lớp nào sẽ khởi tạo dựa trên đầu vào.
Về nguyên tắc, cách 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 enum hoặc có thể đầu vào là sự kết hợp của một số 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, đồng thời thiết kế luôn có nhược điểm là bạn sẽ cần phải sửa đổi phương thức xây dựng đã nói nếu bạn thêm một lớp con mới hoặc thực hiện thay đổi đối với lớp con nào sẽ đượ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 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 để hệ thống có thể tìm ra lớp con nào nên được sử dụng để xử lý cái gì. Bạn vẫn sẽ cần một phương thức xây dựng, nhưng nếu thực hiện đúng, 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 một ví dụ tưởng tượng và nói 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. Việc xử lý nào sẽ 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 bây giờ, bạn đang xem xét rất nhiều 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 kết hợp, nhưng bạn cũng biết rằng bạn sẽ được yêu cầu xử lý ngày càng nhiều các kết hợp khác theo thời gian.
Chúng ta hãy đơn giản hóa vấn đề và cho rằng 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, mọi kết hợp khác có thể bỏ qua, nhưng vì bạn biết mình sẽ phải xử lý chúng sau nên bạn sẽ muốn thiết kế mã theo cách dễ mở rộng.
Hệ thống phân cấp của bạn hiện tại có thể trông giống như thế này:
- Bộ xử lý của tôi
- Bộ xử lý của tôi_Bán hàng
- MyProcessor_Sales_ReservOrdered
- MyProcessor_Sales_ReservPhysical
- Bộ xử lý của tôi_Bán hàng
Bây giờ, bạn có thể dễ dàng triển khai một phương thức trong siêu lớp khởi tạo một lớp con dựa trên một enum ModuleInventPurchSales và StatusIssue. Nhưng sau đó bạn sẽ cần phải sửa đổi siêu lớp mỗi khi bạn thêm một lớp con, và đó không thực sự là ý tưởng về kế thừa trong lập trình hướng đối tượng. Sau cùng, bạn không cần phải sửa đổi RunBaseBatch hoặc SysOperationServiceBase mỗi khi bạn thêm một tác vụ hàng loạt mới.
Thay vào đó, bạn có thể sử dụng khung SysExtension. Điều đó sẽ yêu cầu bạn thêm một lớp khác, lớp này cần mở rộng SysAttribute. Lớp này sẽ được sử dụng làm thuộc tính mà bạn có thể trang trí cho 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 lớp hợp đồng dữ liệu cho triển khai SysOperation, ở chỗ nó sẽ có một số thành viên dữ liệu và phương thức parm để lấy và thiết lập các giá trị đó.
Trong trường hợp của chúng tôi, ClassDeclaration có thể trông giống như thế này:
{
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ể cung cấp một số hoặc tất cả các giá trị mặc định, nhưng tôi chưa làm điều đó.
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 chúng ở đây vì tôi chắc rằng bạn biết cách thực hiện điều đó - nếu không, hãy coi đó là một bài tập ;-)
Bây giờ 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ý của mình. Ví dụ, khai báo lớp có thể trông như thế này:
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 cho lớp theo bất kỳ cách nào bạn muốn, điều quan trọng ở đây là bạn phải trang trí 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 phân cấp lớp trong Dynamics AX và bạn nên tuân thủ theo các quy ước đó nếu có thể).
Bây giờ bạn đã trang trí các lớp của mình để xác định loại xử lý mà từng lớp thực hiện, bạn có thể tận dụng khung SysExtension để khởi tạo các đối tượng của các lớp con khi cần.
Trong lớp siêu của bạn (MyProcessor), bạn có thể thêm phương thức xây dựng như thế này:
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à đối tượng (xin lỗi vì 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 thực hiện việc chấp nhận tên của siêu lớp của một hệ thống phân cấp (và siêu lớp này không cần phải ở đầu hệ thống phân cấp; nó chỉ có nghĩa là chỉ các lớp mở rộng 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 của một lớp mở rộng siêu lớp đã 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 nhiều xác thực hoặc logic hơn nữa vào phương thức xây dựng tùy ý, nhưng điều quan trọng cần lưu ý ở đây là sau 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 phù hợp, phương thức xây dựng sẽ tìm thấy chúng ngay cả khi chúng không tồn tại khi nó được viết.
Còn về hiệu suất thì sao? Thành thật mà nói, tôi chưa thử đánh giá chuẩn, nhưng tôi linh cảm rằng hiệu suất này có lẽ kém hơn 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 suất trong Dynamics AX đều do truy cập cơ sở dữ liệu gây ra, tôi sẽ không quá lo lắng về điều đó.
Tất nhiên, nếu bạn đang triển khai thứ gì đó đòi hỏi phải tạo 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 những trường hợp kinh điển mà bạn chỉ khởi tạo một đối tượng duy nhất để thực hiện một số xử lý dài, tôi nghi ngờ điều đó sẽ quan trọng. Ngoài ra, khi xem xét 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 chạy, tôi nghi ngờ nó sẽ ảnh hưởng đáng kể đến hiệu suất. Khắc phục sự cố: Nếu phương thức xây dựng không tìm thấy các lớp con của bạn mặc dù bạn chắc chắn rằng chúng được trang trí chính xác, thì đó có thể là sự cố bộ nhớ đệm. Hãy thử xóa bộ nhớ đệm trên cả máy khách và máy chủ. Không cần thiết phải thực sự khởi động lại AOS, nhưng đó có thể là giải pháp cuối cùng.