استفاده از چارچوب SysExtension برای یافتن اینکه کدام زیر کلاس را در Dynamics AX 2012 نمونه سازی کنیم
منتشر شده: ۱۶ فوریهٔ ۲۰۲۵ ساعت ۰:۲۶:۱۱ (UTC)
آخرین به روز رسانی: ۱۲ ژانویهٔ ۲۰۲۶ ساعت ۸:۴۳:۲۸ (UTC)
این مقاله نحوه استفاده از چارچوب کمتر شناختهشده SysExtension در Dynamics AX 2012 و Dynamics 365 for Operations را برای نمونهسازی زیرکلاسها بر اساس تزئینات ویژگی شرح میدهد و امکان طراحی به راحتی قابل توسعه سلسله مراتب کلاس پردازش را فراهم میکند.
Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012
اطلاعات موجود در این پست بر اساس Dynamics AX 2012 R3 است. ممکن است برای نسخههای دیگر معتبر باشد یا نباشد. (بهروزرسانی: میتوانم تأیید کنم که اطلاعات موجود در این مقاله برای Dynamics 365 for Operations نیز معتبر است)
هنگام پیادهسازی کلاسهای پردازش در Dynamics AX، اغلب با ایجاد یک سلسله مراتب کلاس مواجه میشوید که در آن هر زیرکلاس با یک مقدار شمارشی مطابقت دارد یا دارای اتصال داده دیگری است. یک طراحی کلاسیک این است که سپس یک متد construct در کلاس اصلی داشته باشید که دارای یک سوئیچ است که بر اساس ورودی تعیین میکند کدام کلاس نمونهسازی شود.
این روش در اصل خوب کار میکند، اما اگر ورودیهای مختلف و زیادی داشته باشید (عناصر زیادی در یک enum یا شاید ورودی ترکیبی از چندین مقدار مختلف باشد)، نگهداری آن میتواند خستهکننده و مستعد خطا شود و طراحی همیشه این عیب را دارد که اگر بخواهید یک زیرکلاس جدید اضافه کنید یا تغییراتی در اینکه کدام زیرکلاس باید بر اساس کدام ورودی استفاده شود، ایجاد کنید، باید متد construct گفته شده را اصلاح کنید.
خوشبختانه، یک روش بسیار شیکتر، اما متأسفانه کمتر شناختهشده، برای انجام این کار وجود دارد، یعنی استفاده از چارچوب SysExtension.
این چارچوب از ویژگیهایی بهره میبرد که میتوانید برای تزئین زیرکلاسهای خود استفاده کنید تا سیستم بتواند تشخیص دهد کدام زیرکلاس باید برای مدیریت چه چیزی استفاده شود. شما هنوز به یک متد construct نیاز خواهید داشت، اما اگر به درستی انجام شود، هرگز هنگام اضافه کردن زیرکلاسهای جدید نیازی به تغییر آن نخواهید داشت.
بیایید به یک مثال فرضی نگاه کنیم و بگوییم که شما قصد دارید یک سلسله مراتب را پیادهسازی کنید که نوعی پردازش را بر اساس جدول InventTrans انجام دهد. اینکه کدام پردازش انجام شود به StatusReceipt و StatusIssue رکوردها و همچنین به اینکه آیا رکوردها به SalesLine، PurchLine یا هیچکدام مربوط هستند، بستگی دارد. در حال حاضر، شما به ترکیبهای مختلف زیادی نگاه میکنید.
فرض کنید میدانید که فعلاً فقط باید تعداد انگشتشماری از ترکیبها را مدیریت کنید، اما این را هم میدانید که از شما خواسته خواهد شد که به مرور زمان بتوانید ترکیبهای بیشتری را مدیریت کنید.
بیایید آن را نسبتاً ساده نگه داریم و بگوییم که در حال حاضر شما فقط باید رکوردهای مربوط به SalesLine را با StatusIssue از ReservPhysical یا ReservOrdered مدیریت کنید، فعلاً میتوان از سایر ترکیبات صرف نظر کرد، اما از آنجایی که میدانید بعداً باید آنها را مدیریت کنید، باید کد خود را به گونهای طراحی کنید که به راحتی قابل توسعه باشد.
سلسله مراتب شما ممکن است فعلاً چیزی شبیه به این باشد:
- پردازنده منفروش پردازنده منفروش پردازنده منرزرو سفارش داده شدهفروش پردازنده منرزرو فیزیکی
حالا، شما به راحتی میتوانید متدی را در کلاس اصلی پیادهسازی کنید که یک زیرکلاس را بر اساس ModuleInventPurchSales و یک شمارش StatusIssue نمونهسازی کند. اما در این صورت، هر بار که یک زیرکلاس اضافه میکنید، باید کلاس اصلی را تغییر دهید، و این واقعاً ایده وراثت در برنامهنویسی شیءگرا نیست. از این گذشته، لازم نیست هر بار که یک کار دستهای جدید اضافه میکنید، RunBaseBatch یا SysOperationServiceBase را تغییر دهید.
در عوض، میتوانید از چارچوب SysExtension استفاده کنید. این کار مستلزم اضافه کردن کلاس دیگری است که باید SysAttribute را ارثبری کند. این کلاس به عنوان صفتی استفاده میشود که میتوانید کلاسهای پردازش خود را با آن تزئین کنید.
این کلاس بسیار شبیه به نحوهی ایجاد یک کلاس قرارداد داده برای پیادهسازی SysOperation است، به این صورت که تعدادی عضو داده و متدهای parm برای دریافت و تنظیم آن مقادیر خواهد داشت.
در مورد ما، ClassDeclaration ممکن است چیزی شبیه به این باشد:
{
ModuleInventPurchSales module;
StatusIssue statusIssue;
StatusReceipt statusReceipt
}
شما باید یک متد new() برای نمونهسازی همه اعضای داده ایجاد کنید. در صورت تمایل میتوانید به برخی یا همه آنها مقادیر پیشفرض بدهید، اما من این کار را نکردهام.
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
;
super();
module = _module;
statusIssue = _statusIssue;
statusReceipt = _statusReceipt;
}
و همچنین باید برای هر عضو داده یک متد parm پیادهسازی کنید، اما من آنها را در اینجا حذف کردهام زیرا مطمئنم که میدانید چگونه این کار را انجام دهید - در غیر این صورت، بیایید آن را یک تمرین در نظر بگیریم ;-)
حالا میتوانید از کلاس ویژگی خود برای تزئین هر یک از کلاسهای پردازشی خود استفاده کنید. برای مثال، تعریف کلاس میتواند به این شکل باشد:
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
{
}
البته میتوانید کلاسهای خود را به هر شکلی که میخواهید نامگذاری کنید، بخش مهم این است که کلاسهای خود را با ویژگیهایی که با نوع پردازشی که انجام میدهند مطابقت دارند، تزئین کنید. (اما به خاطر داشته باشید که در Dynamics AX قراردادهای نامگذاری برای سلسله مراتب کلاسها وجود دارد و در صورت امکان، همیشه ایده خوبی است که از آنها پیروی کنید).
اکنون که کلاسهای خود را برای شناسایی نوع پردازش هر یک از آنها تزئین کردهاید، میتوانید از چارچوب SysExtension برای نمونهسازی اشیاء کلاسهای فرعی در صورت نیاز استفاده کنید.
در کلاس فوق العاده خود (MyProcessor)، میتوانید یک متد construct مانند این اضافه کنید:
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;
}
بخش واقعاً جالب - و در واقع شیء (ببخشید که از این کلمه استفاده میکنم) کل این پست - متد getClassFromSysAttribute() در کلاس SysExtensionAppClassFactory است. کاری که این متد انجام میدهد این است که نام کلاس اصلی یک سلسله مراتب را میپذیرد (و این کلاس اصلی نیازی نیست که در بالای سلسله مراتب باشد؛ این به این معنی است که فقط کلاسهایی که از این کلاس بسط میدهند واجد شرایط خواهند بود) و یک شیء ویژگی.
سپس یک شیء از کلاسی را برمیگرداند که کلاس اصلی مشخص شده را بسط میدهد و با یک ویژگی مربوطه تزئین شده است.
بدیهی است که میتوانید هر چقدر که میخواهید اعتبارسنجی یا منطق به متد construct اضافه کنید، اما نکته مهم این است که پس از پیادهسازی، دیگر هرگز نیازی به تغییر این متد نخواهید داشت. میتوانید کلاسهای فرعی را به سلسله مراتب اضافه کنید و تا زمانی که مطمئن شوید آنها را به طور مناسب تزئین میکنید، متد construct آنها را پیدا خواهد کرد، حتی اگر هنگام نوشتن وجود نداشته باشند.
در مورد عملکرد چطور؟ راستش را بخواهید، من سعی نکردهام آن را محک بزنم، اما احساس درونیام این است که این احتمالاً عملکرد بدتری نسبت به طراحی کلاسیک دستور سوئیچ دارد. با این حال، با توجه به اینکه تاکنون بیشترین مشکلات عملکرد در Dynamics AX ناشی از دسترسی به پایگاه داده است، زیاد نگران آن نیستم.
البته، اگر در حال پیادهسازی چیزی هستید که نیاز به ایجاد سریع هزاران شیء دارد، شاید بخواهید بیشتر بررسی کنید، اما در موارد کلاسیک که فقط یک شیء را برای انجام پردازش طولانی نمونهسازی میکنید، شک دارم که این موضوع مهم باشد. همچنین، با توجه به نکته عیبیابی من (پاراگراف بعدی)، به نظر میرسد که چارچوب SysExtension به ذخیرهسازی موقت (caching) متکی است، بنابراین در یک سیستم در حال اجرا، شک دارم که این موضوع تأثیر قابل توجهی بر عملکرد داشته باشد.
عیبیابی: اگر متد construct کلاسهای زیر مجموعه شما را پیدا نمیکند، حتی اگر مطمئن هستید که به درستی تزئین شدهاند، ممکن است مشکل از حافظه پنهان باشد. سعی کنید حافظه پنهان را هم در سمت کلاینت و هم در سمت سرور پاک کنید. لازم نیست AOS را مجدداً راهاندازی کنید، اما ممکن است آخرین راه حل باشد.
مطالعه بیشتر
اگر از این پست لذت بردید، ممکن است این پیشنهادات را نیز بپسندید:
- نحوه تکرار بر روی عناصر Enum از کد X++ در Dynamics AX 2012
- تفاوت بین داده() و buf2Buf() در Dynamics AX 2012
- استفاده از یک کوئری در کلاس قرارداد داده SysOperation در Dynamics AX 2012
