‫راه‌هایی برای دینامیک کردن entityهای NHibernate

فرض کنید تعدادی کلاس سی‌شارپ را با استفاده از Reflection.Emit در زمان اجرا (Runtime) ساخته‌اید. این یعنی کلاس‌ها دینامیک بوده و هیچ سورس کدی وجود ندارد، بلکه همه چیز فقط در حافظه وجود دارد یا نهایتاً فایل dll اسمبلی آن را بتوان در دیسک ذخیره کرد. حال می‌خواهیم برای این کلاس‌های دینامیک HBM یا همان فایل mapping مربوط به NHibernate را هم داشته باشیم. هدف نهایی این است که این کلاس‌ها از طریق HBM به یک سری جداول دیتابیسی map شده و بتوان با NHibernate با آن‌ها کار کرد. دقت کنید که نمی‌توان HBMها را از قبل داشت چون خود entityها از قبل وجود ندارند. ما هیچ اطلاعاتی راجع به نام آنها، پراپرتی‌ها، نوع آن‌ها یا دیگر مشخصات آن‌ها نداریم.

من برای انجام این امر یعنی داشتن mapping دینامیک برای entityهای دینامیک زمان Runtime چندین راه را امتحان کردم. با اینکه بیشتر آن‌ها به جواب نرسیدند اما هر کدام نکات جالبی داشتند:

۱- استفاده از فیلدهای XML: اگر تعداد جداول خیلی زیاد شوند، هم نگهداری آن‌ها سخت می‌شود و هم نوشتن queryهای آن خیلی خیلی سخت می‌شود.

۲- تولید کد کلاس سی‌شارپ و HBM با ابزارهایی مثل T4 یا CodeDOM و build آن‌ها و خوراندن آن‌ها به AppDomain جاری: سرعت خیلی پایینی خواهد داشت، احتمالاً دسترسی‌های خاص هم نیاز خواهد داشت. هر تغییری در ساختار entityهای دینامیک موجب restart شدن application خواهد شد. تولید کد کار چندان ساده‌ای نیست.

۳- استفاده از آبجکت دینامیک دات‌نت که از نسخه ۴ اضافه شده است: قابلیت توسعه و دینامیک بودن را در حد fieldها و propertyها می‌دهد. نمی‌توان در زمان اجرا mappingهای جدید اضافه کرد.

۴- استفاده از فیلدهای IDictionary: همان مشکلات راه حل آبجکت دینامیک دات‌نت را دارد.

۵- استفاده از قابلیت جدید mapping by code در NHibernate یا ConfORM یا Fluent NHibernate: به هدف خیلی نزدیک است، چون همه چیز بر اساس type است و هیچ HBMی وجود ندارد. اما تولید delegateها و lambda expressionهای مورد نیاز به صورت runtime اگر کار غیر ممکنی باشد حتماً کار سخت و پیچیده‌ای خواهد بود. در مورد Automapping موجود در FNH کار را خوب راه می‌اندازد اما انعطاف پذیری آن با توجه به محدودیت‌های این کار، آن را غیر قابل استفاده می‌سازد.

۶- استفاده از SetResultTransformer موجود در NHibernate برای query زدن روی جداول دیتابیس بدون نیاز به داشتن Entityها. تا اندازه زیادی جواب می‌دهد! ولی زیادی ساده و بی‌ساختار است. نیاز به نوشتن مقادیر زیادی SQL هم دارد.

۷- کلاس‌های تولیدی توسط Reflection.Emit از همان ابتدا به صورتی باشد که روی تعریف کلاس‌ها و memberها از Attributeهای خاص Castle ActiveRecord استفاده شده باشد. این راه چندان بد به نظر نمی‌رسد اما دو تا مشکل دارد: ۱- استفاده اجباری الگوی Active Record ۲- وابستگی به پروژه Castle ActiveRecord.

۸- ساختن کلاس Mapping از Namespace خود NHibernate: این اولین راهی بود که امتحان کردم. مبنای اولیه کار نمونه کد Ayende Rahien بود. اما متأسفانه به هیچ وجه نتوانستم آن کد را اجرا کنیم. با وجود این گزینه هنوز گزینه خوبی به نظر می‌رسد.

۹- ایده گرفتن از کدهای FNH و ConfORM و Castle ActiveRecord و حتی خود NHibernate: این پروژه در داخل خود کدهایی برای کار با Mappingها دارند. اما حجیم بودن کد آن‌ها مانع از استفاده راحت از آن‌ها می‌شود.

۱۰- استفاده از کتابخانه‌های 3rd party تولید HBM از جداول دیتابیسی: این راه را فقط به عنوان رزرو اینجا آورده‌ام و اصلاً نمی‌دانم که آیا چنین کتابخانه‌هایی وجود دارند یا نه.

‫استفاده از Fluent NHibernate

از همان اولین باری که اسم Fluent NHibernate را شنیدم فکر کردم چیز جالبی نیست. نمی‌دانم چرا، شاید به خاطر آن که فکر می‌کردم انعطاف‌پذیری را فدای راحتی کرده یا شاید هم به خاطر غرور low level.

طی مدت اخیر که به دنبال راهی برای dynamic کردن entityهای NHibernate بودم خود به خود مجبور شدم مروری به همه راه‌حل‌های mapping از جمله Fluent NHibernate داشته باشم.

طی این مرور بودم که فهمیدم FNH آنقدر هم که فکر می‌کردم بد نیست، اولاً به خاطر این که Auto Mapping دارد. دوم به خاطر این که Fluent mapping آن سادگی و خوانایی نسبتاً قابل قبولی نسبت به HBM دارد.

FNH سه راه برای mapping دارد: Auto Mapping، Fluent Mapping و Traditional Mapping.‏ Auto mapping کار mapping را در صورتی که نیازمندی‌هایتان با پیش‌فرض‌های FNH خیلی تفاوت نداشته شد به خوبی راه می‌اندازد. در این روش شما فقط یک class ساده سی‌شارپی را به FNH معرفی می‌کنید بدون آن که از attribute یا lambda expression برای بیان تناظر بین کلاس و جدول استفاده کرده باشید. FNH خودش یک mapping پیش فرض در نظر خواهد گرفت. برای فهم سادگی موضوع حتماً نگاهی به نمونه‌های اینجا بیندازید.