۱۳۸۹/۱۱/۰۹

ثبت استثناهاي مديريت شده توسط ELMAH


در مورد ELMAH پيشتر مطالبي در اين سايت منتشر شده است:

از آن مي‌توان جهت ثبت استثناهاي مديريت نشده در انواع و اقسام برنامه‌هاي وب دات نتي استفاده كرد. از برنامه‌هاي ASP.NET Webforms ، ASP.NET MVC تا سرويس‌هاي WCF ، WCF RIA و غيره. سپس اين Http module ويژه امكان مرور خطاهاي ثبت شده را از راه دور ميسر مي‌سازد، به همراه فيد RSS از خطاها، امكان مشاهده‌ي مقادير فيلدها در لحظه‌ي بروز خطا، ارسال ايميل خطاها و امكان ذخيره سازي آن‌ها در فايل‌هاي XML تا انواع بانك‌هاي اطلاعاتي.
براي نمونه، سايت Stackoverflow از يك نمونه‌ي سفارشي شده‌ي آن استفاده مي‌كند (+).

همانطور كه در اين معرفي آمده است : ثبت استثناهاي "مديريت نشده". اما آيا امكان ثبت استثناهاي مديريت شده هم توسط آن وجود دارد؟
پاسخ: بله. به صورت زير:
...
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
...
فقط كافي است ارجاعي به اسمبلي ELMAH اضافه شود و از سطر فوق استفاده گردد. به اين صورت خروجي شبيه به همان خطاهاي مديريت نشده در ليست خطاهاي ثبت شده توسط ELMAH ظاهر خواهد شد.

۱۳۸۹/۱۱/۰۷

BloggerToCHM 1.5


آخرين نگارش برنامه‌ي BloggerToCHM را از آدرس زير مي‌توانيد دريافت كنيد:


تغييرات:
  • كامنت‌هاي داراي لينك به صفحات داخلي تشخيص داده خواهند شد
  • دكمه Locate و همچنين auto locate اضافه شد
  • بهبود نمايش تركيب متون فارسي و انگليسي در عنوان‌هاي TOC فايل CHM
  • به روز شدن نام وبلاگ در صورت تغيير آن در سايت اصلي
  • نمايش ليست آرشيو وبلاگ بر اساس آخرين نظر‌هاي كاربران

۱۳۸۹/۱۱/۰۶

يك دست سازي ي و ك دريافتي در صفحات وب


با استفاده از jQuery ، تحت نظر قرار دادن ورودي‌هاي كاربران در تمام فيلدهاي ورودي صفحه كار ساده‌اي است؛ اما جايگزيني مثلا ى فارسي با ي عربي و برعكس درست در لحظه‌ي تايپ آن‌ها كار ساده‌اي نيست و هر مرورگر روش خاص خودش را دارد و بعضي‌ها هم اصلا اجازه‌ي تغيير رخدادهاي رسيده را نمي‌دهند.
اسكريپت زير كار يك دست سازي ي و ك دريافتي در صفحات وب را انجام مي‌دهد (براي مثال اگر كاربر ي تايپ كند به صورت خودكار به ى تبديل مي‌شود):
// <![CDATA[
function substituteCharInFireFox(charCode, e) {
var keyEvt = document.createEvent("KeyboardEvent");
keyEvt.initKeyEvent("keypress", true, true, null, false, false, false, false, 0, charCode);
e.target.dispatchEvent(keyEvt);
e.preventDefault();
}

function substituteCharInChrome(charCode, e) {
//it does not work yet! /*$.browser.webkit*/
//https://bugs.webkit.org/show_bug.cgi?id=16735
var keyEvt = document.createEvent("KeyboardEvent");
keyEvt.initKeyboardEvent("keypress", true, true, null, false, false, false, false, 0, charCode);
e.target.dispatchEvent(keyEvt);
e.preventDefault();
}

function insertAtCaret(myValue, e) {
var obj = e.target;
var startPos = obj.selectionStart;
var endPos = obj.selectionEnd;
var scrollTop = obj.scrollTop;
obj.value = obj.value.substring(0, startPos) + myValue + obj.value.substring(endPos, obj.value.length);
obj.focus();
obj.selectionStart = startPos + myValue.length;
obj.selectionEnd = startPos + myValue.length;
obj.scrollTop = scrollTop;
e.preventDefault();
}

$(document).ready(function () {
$(document).keypress(function (e) {

var keyCode = e.keyCode ? e.keyCode : e.which;
var arabicYeCharCode = 1610;
var persianYeCharCode = 1740;
var arabicKeCharCode = 1603;
var persianKeCharCode = 1705;

if ($.browser.msie) {
switch (keyCode) {
case arabicYeCharCode:
event.keyCode = persianYeCharCode;
break;
case arabicKeCharCode:
event.keyCode = persianKeCharCode;
break;
}
}
else if ($.browser.mozilla) {
switch (keyCode) {
case arabicYeCharCode:
substituteCharInFireFox(persianYeCharCode, e);
break;
case arabicKeCharCode:
substituteCharInFireFox(persianKeCharCode, e);
break;
}
}
else {
switch (keyCode) {
case arabicYeCharCode:
insertAtCaret(String.fromCharCode(persianYeCharCode), e);
break;
case arabicKeCharCode:
insertAtCaret(String.fromCharCode(persianKeCharCode), e);
break;
}
}
});
});
// ]]>
تابع substituteCharInChrome قرار است در نگارش‌هاي آتي گوگل كروم كار كند! كروم فعلا هر نوع شبيه سازي فشرده شدن كليدهاي صفحه كليد را به صفر ترجمه مي‌كند. به همين جهت از روش insertAtCaret در مورد آن استفاده شد. هر دو تابع substituteChar ذكر شده در مورد فايرفاكس و كروم و يا روش ساده IE (با توجه به اينكه keyCode در IE فقط خواندني نيست)، با اپرا كار نمي‌كنند!

  • دريافت اين اسكريپت: (+)
  • نسخه‌ي فشرده شده آن: (+)
  • يك پروژه‌ي ساده ASP.NET نمونه در مورد استفاده از آن: (+)

اين اسكريپت با IE، فايرفاكس، اپرا ، كروم گوگل و Safari شركت اپل سازگار است و تفاوتي هم نمي‌كند كه در يك html ساده استفاده شود يا در صفحات ASP ، PHP ، ASP.NET ، JSP يا هر چي!


مطالب مشابه:

۱۳۸۹/۱۱/۰۵

OneNote و مصرف بالاي RAM و CPU


از برنامه OneNote زياد استفاده مي‌كنم. عموما براي يادداشت برداري از سايت‌ها؛ فقط كافي است يك صفحه از مرورگر خودتون را با Ctrl+A انتخاب و با Ctrl+C در حافظه كپي كنيد. سپس با Ctrl+V در OneNote ، كل صفحه با همان فرمت اصلي و تمام تصاوير، جداول و غيره ذخيره خواهد شد. همچنين در OneNote 2010، دريافت تصاوير از سايت‌ها به صورت asynchronous است (برخلاف نگارش 2007 آن) و حين دريافت تصاوير برنامه متوقف نمي‌شود.

اما اگر سرويس Windows search كه كار indexing را انجام مي‌دهد خاموش باشد:
  • احتمالا OneNote شما بالاي 400 مگ رم مصرف خواهد كرد.
  • مرتبا هنگ مي‌كند.
  • عموما CPU Usage ايي بالاي 50 درصد به صورت مداوم خواهيد داشت.
  • جستجوي آن ديگر درست كار نمي‌كند و بسيار بسيار كند خواهد بود.
و اگر سرويس ياد شده روشن باشد،‌ همه چيز عادي است، از مصرف رم تا CPU و غيره.

۱۳۸۹/۱۱/۰۳

سطح دوم cache در NHibernate


عموما دو الگوي اصلي caching در برنامه‌ها وجود دارند: cache aside و cache through .
در الگوي cache through ، سيستم caching داخل DAL (كه در اينجا همان NHibernate است)، تعبيه مي‌شود؛ مانند سطح اول caching كه پيشتر در مورد آن صحبت شد. در اين حالت cache از ديد ساير قسمت‌هاي برنامه مخفي است و DAL به صورت خودكار آن‌را مديريت مي‌كند.
در الگوي cache aside ، كار مديريت سيستم caching دستي است و خارج از NHibernate قرار مي‌گيرد و DAL هيچگونه اطلاعي از وجود آن ندارد. در اين حالت لايه caching موظف است تا هنگام به روز شدن بانك اطلاعاتي، اطلاعات خود را نيز به روز نمايد. اين لايه عموما توسط ساير شركت‌ها يا گروه‌ها برنامه نويسي تهيه مي‌شود.
NHibernate جهت سهولت كار با اين نوع cache providers خارجي، نقاط تزريق ويژه‌اي را تدارك ديده است كه به second level cache معروف است. هدف از second level cache فراهم آوردن ديدي كش شده از بانك اطلاعاتي است تا فراخواني‌هاي كوئري‌ها به سرعت و بدون تماس با بانك اطلاعاتي صورت گيرد.
در حال حاضر (زمان نگارش اين مطلب)، entity framework اين لايه‌ي دوم caching يا به عبارتي ديگر، امكان تزريق ساده‌تر cache providers خارجي را به صورت توكار ارائه نمي‌دهد.
در NHibernate طول عمر second level cache در سطح session factory (يا به عبارتي طول عمر تمام برنامه) تعريف مي‌شود و برخلاف سطح اوليه caching محدود به يك سشن نيست. در اين حالت هر زمانيكه يك موجوديت به همراه ID منحصربفرد آن تحت نظر NHibernate قرارگيرد و همچنين سطح دوم caching نيز فعال باشد، اين موجوديت در تمام سشن‌هاي برنامه بدون نياز به مراجعه به بانك اطلاعاتي در دسترس خواهد بود (بنابراين بايد دقت داشت كه هدف از اين سيستم، كار سريعتر با اطلاعاتي است كه سطح دسترسي عمومي دارند).

در ادامه ليستي از cache providers خارجي مهيا جهت استفاده در سطح دوم caching را ملاحظه مي‌نمائيد:

  • AppFabric Caching Services : بر اساس Microsoft's AppFabric Caching Services كه يك پلتفرم caching محسوب مي‌شود (+). (اين پروژه پيشتر به نام Velocity معروف شده بود و قرار بود تنها براي ASP.NET ارائه شود كه سياست آن به گونه‌اي جامع‌تر تغيير كرده است)
  • MemCache : بر اساس سيستم معروف MemCached تهيه شده است (+).
  • NCache : (+)
  • ScaleOut : (+)
  • Prevalence : (+)
  • SysCache : بر اساس همان روش آشناي متداول در برنامه‌هاي ASP.NET به كمك System.Web.Caching.Cache كار مي‌كند؛ يا به قولي همان IIS caching
  • SysCache2 : همانند SysCache است با اين تفاوت كه SQL dependencies ويژه SQL Server را نيز پشتيباني مي‌كند.
  • SharedCache : يك سيستم distributed caching نوشته شده براي دات نت است (+).
اين موارد و پروايدرها جزو پروژه‌ي nhcontrib در سايت سورس فورج هستند (+).


مطالب تكميلي:



۱۳۸۹/۱۱/۰۱

NHibernate و سطح اول cache آن


اين روزها هيچكدام از فناوري‌هاي دسترسي به داده بدون امكان يكپارچگي آن‌ها با سيستم‌ها و روش‌هاي متفاوت caching ، مطلوب شمرده نمي‌شوند. ايده اصلي caching هم به زبان ساده به اين صورت است :‌ فراهم آوردن روش‌هايي جهت ميسر ساختن دسترسي سريعتر به داده‌هايي كه به صورت متناوب در برنامه مورد استفاده قرار مي‌گيرند، بجاي مراجعه مستقيم به بانك اطلاعاتي و خواندن اطلاعات از ديسك سخت.
يكي از تفاوت‌هاي مهم NHibernate با اكثر ORM هاي موجود داشتن دو سطح متفاوت cache است : first level cache & second level cache .
براي نمونه Entity framework (در زمان نگارش اين مطلب) تنها first level caching را پشتيباني مي‌كند و پروايدر توكار و يكپارچه‌اي را جهت second level caching ارائه نمي‌دهد.
در اين قسمت قصد داريم First Level Cache را بررسي كنيم.

سطح اول caching در NHibernate چيست؟

سطح اول caching در تمام ORM هايي كه آن‌را پشتيباني مي‌كنند مانند NHibernate ، در طول عمر يك تراكنش تعريف مي‌گردد. در اين حالت در طي يك تراكنش و طول عمر يك سشن، دريافت اطلاعات هر ركورد از بانك اطلاعاتي، تنها يكبار انجام خواهد شد؛ صرفنظر از اينكه كوئري دريافت اطلاعات آن چندبار فراخواني مي‌‌گردد. يكي از دلايل اين روش هم آن است كه هيچ دو شيء متفاوتي كه هم اكنون در حافظه قرار دارند نبايد بيانگر يك ركورد واحد از بانك اطلاعاتي باشند.
در NHibernate به صورت پيش فرض هر زمانيكه از شيء استاندارد session استفاده مي‌كنيد، سطح اول caching نيز فعال است. درست در زمانيكه سشن خاتمه مي‌يابد، اين سطح از caching نيز به صورت خودكار تخليه خواهد گرديد.
به first level caching اصطلاحا thought-out cache system يا Cache Through pattern و يا identity map هم گفته مي‌شود.

مثال:

روش متداول و استاندارد كار با NHibernate عموما به صورت زير است:

الف) دريافت شيء Session از Session Factory
ب) شروع يك تراكنش با فراخواني متد BeginTransaction شيء Session
ج) براي مثال دريافت اطلاعات ركوردي با ID مساوي يك به كمك متد Get مرتبط با شيء Session : اين اطلاعات مستقيما از بانك اطلاعاتي دريافت خواهد شد.
د) سپس مجددا سعي در دريافت ركوردي با ID مساوي يك. اينبار اطلاعات اين شيء مستقيما از cache خوانده مي‌شود و رفت و برگشتي به بانك اطلاعاتي نخواهيم داشت. به همين جهت به اين روش identity map هم گفته مي‌شود، زيرا NHibernate بر اساس ID منحصربفرد اين اشياء ، identity map خود را تشكيل مي‌دهد.
ه) خاتمه‌ي سشن با فراخواني متد Close آن
بلافاصله
الف) دريافت شيء Session از Session Factory
ب) شروع يك تراكنش با فراخواني متد BeginTransaction شيء Session
ج) براي مثال دريافت اطلاعات ركوردي با ID مساوي يك به كمك متد Get مرتبط با شيء Session : اين اطلاعات مستقيما از بانك اطلاعاتي دريافت خواهد شد (زيرا در يك سشن جديد قرار داريم و همچنين سشن قبلي بسته شده و كش آن تخليه گشته است).
د) خاتمه‌ي سشن با فراخواني متد Close آن


سؤال: آيا استفاده از يك سشن سراسري در برنامه صحيح است؟
پاسخ: خير!
توضيحات: زمانيكه از يك سشن سراسري استفاده مي‌كنيد، كش NHibernate را در اختيار تمام كاربران همزمان سيستم قرار داده‌ايد. در طي يك سشن، همانطور كه عنوان شد، بر اساس IDهاي اشياء، يك identity map تشكيل مي‌شود و در اين حالت به ازاي هر ركورد بانك اطلاعاتي فقط و فقط يك شيء در حافظه وجود خواهد داشت كه اين روش در محيط‌هاي چندكاربره مانند برنامه‌هاي وب به زودي تبديل به نشت اطلاعات و يا تخريب اطلاعات مي‌گردد. به همين جهت در اين نوع برنامه‌ها روش session-per-request بهترين حالت كاري است.

سؤال: حين به روز رساني اشياء جديد، به خطا بر مي‌خورم. مشكل در كجاست؟
فرض كنيد شيء مفروض Customer را توسط متد session.Get از بانك اطلاعاتي دريافت و تعدادي از خواص آن‌را جهت ساخت شيء جديدي از كلاس Customer استفاده كرده‌ايم. اكنون اگر بخواهيم اين شيء جديد را در بانك اطلاعاتي ذخيره يا به روز رساني كنيم، NHibernate اين اجازه را نمي‌دهد! چرا؟
پاسخ:
خطاي متداول اين حالت عموما به صورت زير است:
a different object with the same identifier value was already associated with the session
اگر شخصي با مكانيزم سطح اول caching در NHibernate آشنايي نداشته باشد، شايد ساعاتي را در انجمن‌هاي مرتبط، جهت يافتن روش حل خطاي فوق سپري كند.
همانطور كه عنوان شد، در طول يك سشن، نمي‌توان دو شيء با يك ID را به عنوان يك ركورد بانك اطلاعاتي مورد استفاده قرار داد. اولين فراخواني Get ، سبب كش شدن آن شيء در identity map سطح اول caching مي‌گردد.
راه حل:
الف) از چندين و چند شيء استفاده نكنيد. هر ركورد بايد تنها با يك وهله از شيء‌ايي متناظر باشد.
ب) مي‌توان پيش از update‌، كش سطح اول را به صورت دستي خالي كرد. براي اين منظور از متد Clear شيء سشن استفاده كنيد.
ج) بجاي استفاده از متد saveOrUpdate شيء سشن، از متد Merge آن استفاده كنيد. به اين صورت شيء جديد ايجاد شده با شيء موجود در كش يكي خواهد شد.
د) مي‌توان بجاي تخليه كل كش (حالت ب)، كش مرتبط با شيء Customer را به صورت دستي خالي كرد. براي اين منظور از متد Evict شيء سشن استفاده نمائيد.

و لازم به ذكر است كه متد Flush سبب تخليه كش نمي‌گردد. كار اين متد اعمال كليه تغييرات اعمالي موجود در كش به بانك اطلاعاتي است و بيشتر جهت هماهنگ سازي اين دو مورد استفاده قرار مي‌گيرد.

سؤال: آيا مي‌توان سطح اول caching را غيرفعال كرد؟
پاسخ:بله.
توضيحات:
عموما كليه ORMs جهت Batching يا Bulk data operations (براي مثال ثبت تعداد زيادي ركورد يا به روز رساني تعداد بالايي از آن‌ها، يا نمايش فقط خواندني تعداد زيادي ركورد و گزارشگيري از آن‌ها) كارآيي مطلوبي ندارند. نمونه‌اي از آن‌را در مبحث جاري ملاحظه كرده‌ايد. هر شيءايي كه به نحوي به سشن جاري وارد مي‌شود تحت نظر قرار مي‌گيرد و اين مورد در تعداد بالاي ثبت يا به روز رساني ركوردها، يعني كاهش سرعت و كارآيي، به علاوه مصرف بالاي حافظه. به همين جهت بايد به خاطر داشت كه ORMs جهت سناريوهاي OLTP مناسب هستند و كساني كه سرعت و كارآيي ORMs را با Batch processing اندازه گيري مي‌كنند، كلا دركي از فلسفه‌ي وجودي ORMs و ساختار دروني آن‌ها ندارند!
خوشبختانه NHibernate با معرفي Stateless Sessions بر اين مشكل فائق آمده است. در اينجا بجاي ISession تنها كافي است از IStatelessSession استفاده گردد:
using (IStatelessSession statelessSession = sessionFactory.OpenStatelessSession())
using (ITransaction transaction = statelessSession.BeginTransaction())
{
//now insert 1,000,000 records!
}
در اين حالت سيستم دو مزيت عمده را تجربه خواهد كرد: سرعت بالاي ثبت اطلاعات با تعداد زياد ركورد و همچنين مصرف پايين حافظه از آنجائيكه يك IStatelessSession ارجاعي را به اشيايي كه بارگذاري مي‌كند، در خود نگهداري نخواهد كرد.
تنها بايد به خاطر داشت كه در اين حالت lazy loading پشتيباني نمي‌شود و همچنين رخدادهاي دروني NHibernate نيز لغو خواهند شد.

۱۳۸۹/۱۰/۲۸

سرويس جمع و مفرد سازي اسامي


اگر به Entity data model wizard در VS.Net 2010 دقت كرده باشيد، گزينه‌ي "Pluralize or singularize generated object names" نيز به آن اضافه شده است:



اين مورد از اين جهت حائز اهميت است كه عموما نام جداول در بانك اطلاعاتي، جمع است و نام كلاس متناظر ايجاد شده براي آن در كدهاي برنامه بهتر است مفرد باشد. براي مثال نام جدول، Customers است و نام كلاس آن بهتر است Customer تعريف گردد. به اين صورت كار كردن با آن توسط يك ORM با معناتر خواهد بود؛ زيرا زمانيكه يك وهله از شيء Customer ايجاد مي‌شود، فقط يك ركورد از بانك اطلاعاتي مد نظر است؛ در حاليكه يك جدول مجموعه‌اي است از ركوردها.
زبان انگليسي هم پر است از اسامي جمع و مفرد باقاعده و بي‌قاعده و كل عمليات با اضافه و حذف كردن يك s و يا es پايان نمي‌يابد؛ براي مثال phenomenon و phenomena را در نظر بگيرد تا Money و Moneys.
اين امكان مهيا شده توسط Entity Framework 4.0 يا همان EF v2 با برنامه نويسي هم قابل دسترسي است و در اسمبلي System.Data.Entity.Design.dll و فضاي نام System.Data.Entity.Design.PluralizationServices قرار گرفته است.
اين اسمبلي جزيي از دات نت 4 است و اگر آن‌را توسط گزينه‌ي Add references در VS.NET مشاهده نمي‌كنيد، علت آن است كه در تنظيمات پروژه جاري، گزينه‌ي Target framework بر روي Client profile قرار گرفته است كه بايد به دات نت 4 كامل تغيير يابد.
استفاده از آن هم به صورت زير است:

using System;
using System.Data.Entity.Design.PluralizationServices;
using System.Globalization;

namespace PluralizationServicesTest
{
class Program
{
static void Main(string[] args)
{
var service = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
Console.WriteLine(service.Pluralize("mouse"));
Console.WriteLine(service.IsPlural("phenomena"));
}
}
}

ملاحظات:
اين روش فعلا به زبان انگليسي محدود است و اگر Culture را به مورد ديگري تنظيم كنيد با خطاي "We don't support locales other than English yet" متوقف خواهيد شد.


روش ديگر:
كتابخانه‌ي سورس باز Castle ActiveRecord نيز داراي كلاسي است به نام Inflector كه براي همين منظور طراحي شده است:


كاربرد آن در Fluent NHibernate
در Fluent NHibernate كار نگاشت كلاس‌ها به جداول به صورت خودكار صورت مي‌گيرد و همچنين توليد ساختار بانك اطلاعاتي نيز به همين نحو مي‌باشد. اما مي‌توان توليد نام جداول را سفارشي نيز نمود. براي مثال از كلاس Book به صورت خودكار ساختار جدولي به نام Books را توليد كند:
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using NHibernate.Helper.Toolkit;

namespace NHibernate.Helper.MappingConventions
{
public class TableNameConvention : IClassConvention
{
public void Apply(IClassInstance instance)
{
instance.Table(Inflector.Pluralize(instance.EntityType.Name));
}
}
}
و براي تزريق آن خواهيم داشت:

... = new AutoPersistenceModel()
.Where(...)
.Conventions.Setup(c =>c.Add<TableNameConvention>())
.AddEntityAssembly(...)
...

۱۳۸۹/۱۰/۲۵

استفاده از IIS Express 7.5 در VS.NET


استفاده از IIS در VS.NET و پروژه‌هاي ASP.NET داستان خودش را دارد. در نگارش‌هاي 2002 و 2003 آن، تنها وب سرور قابل استفاده جهت كار با VS.NET همان IIS اصلي بود. مهم‌ترين مشكل اين روش، نياز به داشتن دسترسي مديريتي بر روي سيستم بود (كه در بعضي از شركت‌ها، اين مورد براي عموم كاربران ممنوع است) به همراه نصب جداگانه‌ و تنظيمات مخصوص IIS ، صرفا جهت آزمايش يك برنامه‌ي ساده؛ همچنين با توجه به اينكه IIS جزو كامپوننت‌ها ويندوز بوده و هر نگارشي، IIS خاص خودش را دار است، اين مورد هم مشكلات ويژه‌اي را به همراه دارد (براي مثال IIS5 ويندوز XP را با IIS7 ويندوز سرور 2008 در نظر بگيريد؛ يكي براي توسعه يكي جهت محيط كاري). اين روش در VS.Net 2005 كنار گذاشته شد و از وب سرور توكاري به نام Cassini يا ASP.NET Development Server استفاده گرديد. به اين صورت ديگر نيازي به نصب مجزاي IIS كامل جهت آزمايش‌ برنامه‌هاي ASP.NET نبود و همچنين نياز به داشتن دسترسي مديريتي الزامي نيز منتفي گرديد. اين روش هنوز هم تا نگارش 2010 ويژوال استوديو مرسوم است؛ اما ... اما كساني كه با Cassini كار كرده باشند مي‌دانند كه يك سري از رفتار‌هاي آن با IIS واقعي تطابق ندارد و اگر برنامه‌ي ASP.NET شما با Cassini خوب نمايش داده مي‌شود الزامي ندارد كه با IIS واقعي هم به همان نحو رفتار كند، براي نمونه رفتار مسيريابي آدرس‌هاي نسبي در IIS واقعي و Cassini يكي نيست. علاوه بر آن IIS هاي 7 و 7.5 هم امكانات و ويژگي‌هاي خاص خود را دارند كه Cassini آن‌ها را پوشش نمي‌دهد؛ به علاوه اين دو فقط در ويندوزهاي جديد مانند ويندوز سرور 2008 يا ويندوز 7 قابل دسترسي هستند. به همين جهت اخيرا يك نسخه‌ي سبك و express از IIS 7.5 به صورت جداگانه براي برنامه نويس‌ها فقط جهت آزمودن برنامه‌هاي خود تهيه شده‌ است و البته هدفگيري اصلي آن پروژه‌ي WebMatrix است؛ به همراه ويژگي‌هاي جديد IIS7 مانند امكان آزمودن تنظيمات ويژه IIS7 در وب كانفيگ برنامه، پشتيباني كامل از SSL ، Url Rewrite و ساير ماژول‌هاي IIS7، عدم نياز به دسترسي مديريتي براي اجراي آن، امكان اجراي آن بر روي پورت‌هاي مختلف بدون تداخل با وب سرور(هاي) موجود بر روي سيستم و همچنين برخلاف IIS7 اصلي، بر روي ويندوز XP نيز قابل اجرا است. حجم نگارش IIS Express 7.5 تنها 3.9 مگابايت است:


سرويس پك يك ويژوال استوديوي 2010 (كه در زمان نگارش اين مطلب نسخه‌ي بتاي آن ارائه شده)، يك گزينه‌ي جديد را به منوي كليك راست بر روي نام پروژه در VS.NET به نام Use IIS Express ، اضافه كرده است تا به سادگي بتوان از اين امكان جديد استفاده كرد (يا به عبارتي با IIS Express يكپارچه است و نياز به تنظيم خاصي ندارد).
در ساير حالات (و نسخه‌هايي كه اين يكپارچگي وجود ندارد و نخواهد داشت) به صورت زير مي‌توان عمل كرد:
روش اول:
دستور زير را در خط فرمان وارد نمائيد:
"C:\Program Files\IIS Express\iisexpress.exe" /path:D:\Prog\1389\MySite\ /port:4326 /clr:v4.0
به اين صورت وب سروري جهت ارائه‌ي سايتي با مسير ذكر شده بر روي پورت 4326 (http://localhost:4326/) بر اساس دات نت 4 تشكيل خواهد شد (براي نمونه جهت دات نت سه و نيم مقدار v3.5 را وارد نمائيد).

روش دوم (كه در حقيقت همان روش اول با ارائه‌ي پشت صحنه‌ي موقت آن است):
الف) ابتدا به مسير My Documents\IISExpress\config مراجعه كرده و فايل applicationhost.config را باز كنيد. سپس گره مربوط به site را يافته (حدود سطر 153) و گزينه‌ي serverAutoStart را حذف كنيد:
<site name="WebSite1" id="1">
<application path="/">
<virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite1" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":8080:localhost" />
</bindings>
</site>
ب) سپس تنظيمات سايت مورد نظر خود را به صورت دستي به اين فايل اضافه كنيد. براي مثال:
<site name="WebSite2" id="2">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="D:\Prog\1389\MyTestSite\" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":1389:localhost" />
</bindings>
</site>
توضيحات:
Name در اينجا نامي دلخواه است كه وارد خواهيد نمود.
Id شماره سايتي است كه ثبت خواهد شد.
applicationPool در اينجا بسيار مهم است. اگر سايت شما مبتني بر دات نت 4 است، Clr4IntegratedAppPool را وارد نمائيد و اگر غير از اين است، Clr2IntegratedAppPool بايد تنظيم شود.
physicalPath همان مسير پروژه شما است.
در قسمت bindingInformation هم مي‌توان شماره پورت مورد نظر را وارد كرد.

اكنون فايل applicationhost.config را ذخيره كرده و ببنديد.
سپس دستور زير را در خط فرمان ويندوز وارد نمائيد:
"C:\Program Files\IIS Express\iisexpress.exe" /site:WebSite2
كه در اينجا WebSite2 همان مدخل جديدي است كه به فايل applicationhost.config اضافه شده است. به اين صورت آدرس http://localhost:1389/ جهت دسترسي به سايت شما آماده استفاده خواهد بود.

تنظيمات ديباگر VS.NET :
تا اينجا تنها موفق شده‌ايم كه اين وب سرور آزمايشي را راه اندازي كنيم. اما نكته‌ي مهم امكان ديباگ كردن برنامه توسط آن‌را از دست داده‌ايم. براي اين منظور در VS.NET به خواص پروژه، برگه‌ي Web آن مراجعه كنيد. در قسمت Servers گزينه‌ي use custom web server را انتخاب كرده و آدرسي را كه در يكي از دو روش فوق ساخته‌ايد وارد نماييد. براي مثال http://localhost:4326/
همچنين بايد دقت داشت كه در همين قسمت هيچكدام از debuggers ذيل گزينه‌ي use custom web server نبايد تيك خورده باشند (چون VS.NET دقيقا نمي‌داند كه بايد به كدام پروسه در ويندوز attach شود).
اكنون برنامه را در حالت ديباگ در VS.NET آغاز كنيد (بديهي است فرض بر اين است كه iisexpress.exe با تنظيمات ذكر شده بايد در حال اجرا باشد).
و ... حداقل مزيت آن بسيار سريع‌تر بودن اين روش نسبت به Cassini يا ASP.NET Development Server است.
تا اينجا فقط VS.NET به صورت خودكار مرورگر را باز كرده و سايت نمايش داده مي‌شود؛ اما اگر در قسمتي از كدهاي خود breakpoint قرار دهيم كار نمي‌كند. براي اين منظور بايد در حين اجراي برنامه، از منوي debug ، گزينه‌ي attach to process را انتخاب كرده و به iisexpress متصل شويد.

۱۳۸۹/۱۰/۲۲

بالا بردن سرعت بارگذاري اوليه NHibernate


در زمان اولين بارگذاري NHibernate ، ساخت تمام نگاشت‌ها صورت گرفته و همچنين session factory ايجاد مي‌گردد. به همين جهت به كمك الگوي thread safe singleton نسبت به كش كردن آن در طول عمر يك برنامه استفاده مي‌گردد.
در برنامه‌اي كه در يك محيط كاري مورد استفاده قرار مي‌گيرد اين زمان اصلا مهم نيست، زيرا تنها يكبار بايد انجام شود. اما به عنوان يك برنامه نويس شايد در طول روز صدها بار نياز به باز و بسته كردن برنامه جهت آزمودن آن داشته باشيم و اين مورد پس از مدتي تبديل به عذاب مي‌شود! خوشبختانه امكان serialize نمودن تنظيمات توليدي session factory به فايل و سپس خواندن از آن نيز وجود دارد كه اين امر در حين توسعه‌ي برنامه بسيار ارزشمند است.
جهت مطالعه بيشتر مي‌توان به مطالب زير مراجعه كرد:

و حاصل تمام اين مقالات در پروژه‌ي Effectus، فايل Effectus\Infrastructure\BootStrapper.cs آن گردآوري شده است.

۱۳۸۹/۱۰/۲۰

يافتن نام رشته‌اي كامل يك كلاس در دات نت


دو تنظيم زير را در نظر بگيريد:
<add key="nhibernate-logger" value="NHibernate.Helper.Logging.LoggerFactory, NHibernate.Helper" />
و يا
<add name="StaticContentCacheModule" type="StaticContentCacheModule.StaticCache, StaticContentCacheModule"/>
اين نوع موارد را در فايل‌هاي app.config و يا web.config زياد مي‌توان يافت.
الان فرض كنيد كلاس StaticCache مربوط به StaticContentCacheModule فرضي فوق را به صورت دستي به برنامه‌ي خود اضافه كرده‌ايد. همچنين سطر فوق را نيز بدون هيچ تغييري در قسمت http modules مربوط به web.config برنامه معرفي نموده‌ايد. برنامه را اجرا مي‌كنيد، اما ماژول ذكر شده كار نمي‌كند! چرا؟
چون نام رشته‌اي متناظر با كلاس StaticCache ايي كه اكنون به پروژه‌ي خود اضافه كرده‌ايد، با توجه به فضاهاي نام پروژه‌ي جديد، كاملا دگرگون شده است. بنابراين، سؤال مهم اينجا است كه اين نام را بر اساس تنظيمات پروژه‌ي جاري چگونه مي‌توان يافت؟
خوشبختانه دات نت فريم ورك، ابزاري توكار را براي توليد اين نام رشته‌اي، به همراه دارد:
class Test
{
static void Main()
{
string name = typeof(System.Data.DataView).AssemblyQualifiedName;
Console.WriteLine(name);
}
}
خاصيت AssemblyQualifiedName ذكر شده در مثال فوق، دقيق‌ترين نامي است كه مي‌توانيد در پروژه‌ي خود استفاده نمائيد.
خروجي اين مثال جهت نمايش نام رشته‌اي معادل كلاس System.Data.DataView به صورت زير است:
System.Data.DataView, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

سؤال: از كجا متوجه شوم كه رشته‌ي فوق واقعا كار مي‌كند؟
مقدار متغير name مثال فوق بايد پس از بكارگيري در متد Type.GetType ، حاصلي غير null را بازگشت دهد.
var name = typeof(System.Data.DataView).AssemblyQualifiedName;
var type = Type.GetType(name);
اگر حاصل نال بود، يعني همان مشكلي كه در ابتداي مطلب ذكر شد: ماژول مشخص شده در web.config برنامه توسط رشته‌ي مورد نظر كار نخواهد كرد.

نكته: اگر قصد معرفي اسمبلي ديگري را به برنامه داريد و اين اسمبلي امضاي ديجيتال دارد (strong name signature)، بايد تمام اطلاعات حاصل را ذكر كنيد (مانند مثال فوق كه شامل Version ، Public key token و غيره است). در غير اينصورت (عدم وجود امضاي ديجيتال) ذكر دو قسمت اول خروجي خاصيت AssemblyQualifiedName كافي خواهند بود.

۱۳۸۹/۱۰/۱۹

نمايش حجم ViewState برنامه‌هاي ASP.NET WebForms


امكان اندازه گيري دقيق حجم ViewState در برنامه‌هاي ASP.NET WebForms وجود دارد (+) ، اما خوب، اين روش يك ايراد مهم هم دارد. چند نفر حاضرند تمام صفحات خود را ويرايش كرده و ارث بري ذكر شده را پياده سازي كنند؟
يك روش ديگر اعمال آن به تمام صفحات، استفاده از پوشه‌ي استاندارد App_Browsers و سپس ايجاد فايلي مانند ViewStateManager.browser مي‌باشد:
<!--Applies to all pages-->
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page" adapterType="ViewStateManagerCore.SomeClass" />
</controlAdapters>
</browser>
</browsers>
به اين صورت ارث بري مورد نظر به صورت خودكار بر روي تمام صفحات اعمال خواهد شد.
علاوه بر اين دو روش (ارث بري دستي و ارث بري خودكار)، افزونه‌اي هم براي فايرفاكس جهت نمايش حجم ViewState صفحات طراحي شده است كه از آدرس زير قابل دريافت مي‌باشد :




اين موضوع چه اهميتي دارد؟
ممكن است كاربران سايت شما گاهي از اوقات در بعضي از صفحات با خطاي "Validation of viewstate MAC failed" مواجه و متوقف شوند. عموما حجم بالاي ViewState اين مشكل را درست مي‌كند. حجم ViewState بالا است (چند صد كيلوبايت ...)، ‌صفحه دير رندر مي‌شود يا هنوز كامل نشده، شخص صفحه را متوقف مي‌كند. ASP.NET در اين حالت اجازه‌ي ارسال اطلاعات از اين صفحه‌ي ناقص را به دلايل امنيتي نمي‌دهد كه خوب است (شايد جعل شده باشد؟).


پ.ن.
راه حل پيشنهادي مايكروسافت جهت حل اين مشكل (شروع شده از سال 2007)، ارتقاء برنامه‌هاي شما به ASP.NET MVC مي‌باشد :)

۱۳۸۹/۱۰/۱۷

مديريت Join در NHibernate 3.0


مباحث eager fetching/loading (واكشي حريصانه) و lazy loading/fetching (واكشي در صورت نياز، با تاخير، تنبل) جزو نكات كليدي كار با ORM هاي پيشرفته بوده و در صورت عدم اطلاع از آن‌ها و يا استفاده‌ي ناصحيح از هر كدام، بايد منتظر از كار افتادن زود هنگام سيستم در زير بار چند كاربر همزمان بود. به همين جهت تصور اينكه "با استفاده از ORMs ديگر از فراگيري SQL راحت شديم!" يا اينكه "به من چه كه پشت صحنه چه اتفاقي مي‌افته!" بسي مهلك و نادرست است!
در ادامه به تفصيل به اين موضوع پرداخته خواهد شد.

ابزار مورد نياز

در اين مطلب از برنامه‌ي NHProf استفاده خواهد شد.
اگر مطالب NHibernate اين سايت را دنبال كرده باشيد، در مورد لاگ كردن SQL توليدي به اندازه‌ي كافي توضيح داده شده يا حتي يك ماژول جمع و جور هم براي مصارف دم دستي نوشته شده است. اين موارد شايد اين ايده را به همراه داشته باشند كه چقدر خوب مي‌شد يك برنامه‌ي جامع‌تر براي اين نوع بررسي‌ها تهيه مي‌شد. حداقل SQL نهايي فرمت مي‌شد (يعني برنامه بايد مجهز به يك SQL Parser تمام عيار باشد كه كار چند ماهي هست ...؛ با توجه به اينكه مثلا NHibernate از افزونه‌هاي SQL ويژه بانك‌هاي اطلاعاتي مختلف هم پشتيباني مي‌كند، مثلا T-SQL مايكروسافت با يك سري ريزه كاري‌هاي منحصر به MySQL متفاوت است)، يا پس از فرمت شدن، syntax highlighting به آن اضافه مي‌شد، در ادامه مشخص مي‌كرد كدام كوئري‌ها سنگين‌تر هستند، كداميك نشانه‌ي عدم استفاده‌ي صحيح از ORM مورد استفاده است، چه مشكلي دارد و از اين موارد.
خوشبختانه اين ايده‌ها يا آرزوها با برنامه‌ي NHProf محقق شده است. اين برنامه براي استفاده‌ي يك ماه اول آن رايگان است (آدرس ايميل خود را وارد كنيد تا يك فايل مجوز رايگان يك ماهه براي شما ارسال گردد) و پس از يك ماه، بايد حداقل 300 دلار هزينه كنيد.


واكشي حريصانه و غيرحريصانه چيست؟

رفتار يك ORM جهت تعيين اينكه آيا نياز است براي دريافت اطلاعات بين جداول Join صورت گيرد يا خير، واكشي حريصانه و غيرحريصانه را مشخص مي‌سازد.
در حالت واكشي حريصانه به ORM خواهيم گفت كه لطفا جهت دريافت اطلاعات فيلدهاي جداول مختلف، از همان ابتداي كار در پشت صحنه، Join هاي لازم را تدارك ببين. در حالت واكشي غيرحريصانه به ORM خواهيم گفت به هيچ عنوان حق نداري Join ايي را تشكيل دهي. هر زماني كه نياز به اطلاعات فيلدي از جدولي ديگر بود بايد به صورت مستقيم به آن مراجعه كرده و آن مقدار را دريافت كني.
به صورت خلاصه برنامه نويس در حين كار با ORM هاي پيشرفته نيازي نيست Join بنويسد. تنها بايد ORM را طوري تنظيم كند كه آيا اينكار را حتما خودش در پشت صحنه انجام دهد (واكشي حريصانه)، يا اينكه خير، به هيچ عنوان SQL هاي توليدي در پشت صحنه نبايد حاوي Join باشند (lazy loading).


چگونه واكشي حريصانه و غيرحريصانه را در NHibernate 3.0 تنظيم كنيم؟

در NHibernate اگر تنظيم خاصي را تدارك نديده و خواص جداول خود را به صورت virtual معرفي كرده باشيد، تنظيم پيش فرض دريافت اطلاعات همان lazy loading است. به مثالي در اين زمينه توجه بفرمائيد:

مدل برنامه:
مدل برنامه همان مثال كلاسيك مشتري و سفارشات او مي‌باشد. هر مشتري چندين سفارش مي‌تواند داشته باشد. هر سفارش به يك مشتري وابسته است. هر سفارش نيز از چندين قلم جنس تشكيل شده است. در اين خريد، هر جنس نيز به يك سفارش وابسته است.


using System.Collections.Generic;
namespace CustomerOrdersSample.Domain
{
public class Customer
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Order> Orders { get; set; }
}
}

using System;
using System.Collections.Generic;
namespace CustomerOrdersSample.Domain
{
public class Order
{
public virtual int Id { get; set; }
public virtual DateTime OrderDate { set; get; }
public virtual Customer Customer { get; set; }
public virtual IList<OrderItem> OrderItems { set; get; }
}
}

namespace CustomerOrdersSample.Domain
{
public class OrderItem
{
public virtual int Id { get; set; }
public virtual Product Product { get; set; }
public virtual int Quntity { get; set; }
public virtual Order Order { set; get; }
}
}

namespace CustomerOrdersSample.Domain
{
public class Product
{
public virtual int Id { set; get; }
public virtual string Name { get; set; }
public virtual decimal UnitPrice { get; set; }
}
}

كه جداول متناظر با آن به صورت زير خواهند بود:
    create table Customers (
CustomerId INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (CustomerId)
)

create table Orders (
OrderId INT IDENTITY NOT NULL,
OrderDate DATETIME null,
CustomerId INT null,
primary key (OrderId)
)

create table OrderItems (
OrderItemId INT IDENTITY NOT NULL,
Quntity INT null,
ProductId INT null,
OrderId INT null,
primary key (OrderItemId)
)

create table Products (
ProductId INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
UnitPrice NUMERIC(19,5) null,
primary key (ProductId)
)

alter table Orders
add constraint fk_Customer_Order
foreign key (CustomerId)
references Customers

alter table OrderItems
add constraint fk_Product_OrderItem
foreign key (ProductId)
references Products

alter table OrderItems
add constraint fk_Order_OrderItem
foreign key (OrderId)
references Orders

همچنين يك سري اطلاعات آزمايشي زير را هم در نظر بگيريد: (بانك اطلاعاتي انتخاب شده SQL CE است)

SET IDENTITY_INSERT [Customers] ON;
GO
INSERT INTO [Customers] ([CustomerId],[Name]) VALUES (1,N'Customer1');
GO
SET IDENTITY_INSERT [Customers] OFF;
GO
SET IDENTITY_INSERT [Products] ON;
GO
INSERT INTO [Products] ([ProductId],[Name],[UnitPrice]) VALUES (1,N'Product1',1000.00000);
GO
INSERT INTO [Products] ([ProductId],[Name],[UnitPrice]) VALUES (2,N'Product2',2000.00000);
GO
INSERT INTO [Products] ([ProductId],[Name],[UnitPrice]) VALUES (3,N'Product3',3000.00000);
GO
SET IDENTITY_INSERT [Products] OFF;
GO
SET IDENTITY_INSERT [Orders] ON;
GO
INSERT INTO [Orders] ([OrderId],[OrderDate],[CustomerId]) VALUES (1,{ts '2011-01-07 11:25:20.000'},1);
GO
SET IDENTITY_INSERT [Orders] OFF;
GO
SET IDENTITY_INSERT [OrderItems] ON;
GO
INSERT INTO [OrderItems] ([OrderItemId],[Quntity],[ProductId],[OrderId]) VALUES (1,10,1,1);
GO
INSERT INTO [OrderItems] ([OrderItemId],[Quntity],[ProductId],[OrderId]) VALUES (2,5,2,1);
GO
INSERT INTO [OrderItems] ([OrderItemId],[Quntity],[ProductId],[OrderId]) VALUES (3,20,3,1);
GO
SET IDENTITY_INSERT [OrderItems] OFF;
GO

دريافت اطلاعات :
مي‌خواهيم نام كليه محصولات خريداري شده توسط مشتري‌ها را به همراه نام مشتري و زمان خريد مربوطه، نمايش دهيم (دريافت اطلاعات از 4 جدول بدون join نويسي):

var list = session.QueryOver<Customer>().List();

foreach (var customer in list)
{
foreach (var order in customer.Orders)
{
foreach (var orderItem in order.OrderItems)
{
Console.WriteLine("{0}:{1}:{2}", customer.Name, order.OrderDate, orderItem.Product.Name);
}
}
}

خروجي به صورت زير خواهد بود:
Customer1:2011/01/07 11:25:20 :Product1
Customer1:2011/01/07 11:25:20 :Product2
Customer1:2011/01/07 11:25:20 :Product3
اما بهتر است نگاهي هم به پشت صحنه عمليات داشته باشيم:



همانطور كه مشاهده مي‌كنيد در اينجا اطلاعات از 4 جدول مختلف دريافت مي‌شوند اما ما Join ايي را ننوشته‌ايم. ORM هرجايي كه به اطلاعات فيلدهاي جداول ديگر نياز داشته، به صورت مستقيم به آن جدول مراجعه كرده و يك كوئري، حاصل اين عمليات خواهد بود (مطابق تصوير جمعا 6 كوئري در پشت صحنه براي نمايش سه سطر خروجي فوق اجرا شده است).
اين حالت فقط و فقط با تعداد ركورد كم بهينه است (و به همين دليل هم تدارك ديده شده است). بنابراين اگر براي مثال قصد نمايش اطلاعات حاصل از 4 جدول فوق را در يك گريد داشته باشيم، بسته به تعداد ركوردها و تعداد كاربران همزمان برنامه (خصوصا در برنامه‌هاي تحت وب)، بانك اطلاعاتي بايد بتواند هزاران هزار كوئري رسيده حاصل از lazy loading را پردازش كند و اين يعني مصرف بيش از حد منابع (IO بالا، مصرف حافظه بالا) به همراه بالا رفتن CPU usage و از كار افتادن زود هنگام سيستم.
كساني كه پيش از اين با SQL نويسي خو گرفته‌اند احتمالا الان منابع موجود را در مورد نحوه‌ي نوشتن Join در NHibernate زير و رو خواهند كرد؛ زيرا پيش از اين آموخته‌اند كه براي دريافت اطلاعات از دو يا چند جدول مرتبط بايد Join نوشت. اما همانطور كه پيشتر نيز عنوان شد، اگر با جزئيات كار با NHibernate آشنا شويم، نيازي به Join نويسي نخواهيم داشت. اينكار را خود ORM در پشت صحنه بايد و مي‌تواند مديريت كند. اما چگونه؟
در NHibernate 3.0 با معرفي QueryOver كه جايگزيني از نوع strongly typed همان ICriteria API قديمي است، يا با معرفي Query كه همان LINQ to NHibernate مي‌باشد، متدي به نام Fetch نيز تدارك ديده شده است كه استراتژي‌هاي lazy loading و eager loading را به سادگي توسط آن مي‌توان مشخص نمود.

مثال: دريافت اطلاعات با استفاده از QueryOver

var list = session
.QueryOver<Customer>()
.Fetch(c => c.Orders).Eager
.Fetch(c => c.Orders.First().OrderItems).Eager
.Fetch(c => c.Orders.First().OrderItems.First().Product).Eager
.List();

foreach (var customer in list)
{
foreach (var order in customer.Orders)
{
foreach (var orderItem in order.OrderItems)
{
Console.WriteLine("{0}:{1}:{2}", customer.Name, order.OrderDate, orderItem.Product.Name);
}
}
}

پشت صحنه:



اينبار فقط يك كوئري حاصل عمليات بوده و join ها به صورت خودكار با توجه به متدهاي Fetch ذكر شده كه حالت eager loading آن‌ها صريحا مشخص شده است، تشكيل شده‌اند (6 بار رفت و برگشت به بانك اطلاعاتي به يكبار تقليل يافت).

نكته 1: نتايج تكراري
اگر حاصل join آخر را نمايش دهيم، نتايجي تكراري خواهيم داشت كه مربوط است به مقدار دهي customer با سه وهله از شيء مربوطه تا بتواند واكشي حريصانه‌ي مجموعه اشياء فرزند آن‌را نيز پوشش دهد. براي رفع اين مشكل يك سطر TransformUsing بايد اضافه شود:
...
.TransformUsing(NHibernate.Transform.Transformers.DistinctRootEntity)
.List();


دريافت اطلاعات با استفاده از LINQ to NHibernate3.0
براي اينكه بتوان متدهاي Fetch ذكر شده را به LINQ to NHibernate 3.0 اعمال نمود، ذكر فضاي نام NHibernate.Linq ضروري است. پس از آن خواهيم داشت:

var list = session
.Query<CUSTOMER>()
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.OrderItems)
.ThenFetch(p => p.Product)
.ToList();

اينبار از FetchMany، سپس ThenFetchMany (براي واكشي حريصانه مجموعه‌هاي فرزند) و در آخر از ThenFetch استفاده خواهد شد.

همانطور كه ملاحظه مي‌كنيد حاصل اين كوئري، با كوئري قبلي ذكر شده يكسان است. هر دو، اطلاعات مورد نياز از دو جدول مختلف را نمايش مي‌دهند. اما يكي در پشت صحنه شامل چندين و چند كوئري براي دريافت اطلاعات است، اما ديگري تنها از يك كوئري Join دار تشكيل شده است.


نكته 2: خطاهاي ممكن
ممكن است حين تعريف متدهاي Fetch در زمان اجرا به خطاهاي Antlr.Runtime.MismatchedTreeNodeException و يا Specified method is not supported و يا موارد مشابهي برخورد نمائيد. تنها كاري كه بايد انجام داد جابجا كردن مكان بكارگيري extension methods است. براي مثال متد Fetch بايد پس از Where در حالت استفاده از LINQ ذكر شود و نه قبل از آن.

۱۳۸۹/۱۰/۱۵

آيا ديتابيس مورد استفاده در NHibernate با نگاشت‌هاي تعريف شده همخواني دارد؟


زمانيكه خاصيتي به يكي از كلاس‌هاي نگاشت‌هاي تعريف شده اضافه مي‌شود يا حذف مي‌گردد، دقيقا بايد اين به روز رساني در سمت بانك اطلاعاتي هم انجام شود. امكان تهيه و همچنين اعمال اسكريپت نهايي توليد database schema مهيا است، اما ممكن است به هر علتي اين كار فراموش شود. اكنون سؤال اين است كه آيا مي‌توان سريع بررسي كرد كه ديتابيس مورد استفاده با نگاشت‌هاي برنامه همخواني و تطابق دارد؟
جهت پاسخ به اين سؤال بهترين راه ايجاد يك كوئري Select بر اساس تمام خواص تعريف شده در يك كلاس است. اگر يكي از خواص يا حتي خود جدول وجود نداشته باشد، انجام اين كوئري خودبخود با شكست مواجه شده و يك استثناء صادر خواهد شد. همين ايده را به سادگي مي‌توان با NHibernate هم پياده سازي كرد:
public class ConfirmDatabaseMatchesMappings
{
public static void ValidateDatabaseSchemaAgainstMappings()
{
//در اينجا بايد سشن فكتوري سراسري تعريف شده را دريافت و استفاده كرد
using (var session = sessionManager.OpenSession())
{
var allClassMetadata = session.SessionFactory.GetAllClassMetadata();

foreach (var entry in allClassMetadata)
{
session.CreateCriteria(entry.Value.GetMappedClass(EntityMode.Poco))
.SetMaxResults(0).List();
}
}
}
}
براي مثال اگر فيلدي در كلاس‌هاي برنامه موجود باشد اما در بانك اطلاعاتي خير، استثناي حاصل شبيه به عبارات ذيل خواهد بود:
NHibernate.Exceptions.GenericADOException was unhandled
Message=could not execute query
...
و اگر كمي ساير اطلاعات اين استثناء را بررسي كنيم، به همان عبارات آشناي فلان فيلد يافت نشد يا فلان جدول وجود ندارد، ‌مي‌رسيم.

۱۳۸۹/۱۰/۱۴

وضعيت فناوري‌هاي مرتبط با دات نت از ديدگاه مرگ و زندگي!


مطلبي كه در ذيل آورده شده صرفا يك برداشت شخصي است بر اساس نقل قول‌ها و بررسي وضعيت اعضاي تيم‌هاي مرتبط با فناوري‌هاي مختلف بكار گرفته شده در دات نت فريم ورك و ... نه رسمي!

ADO.NET ، DataSet ، DataTable و امثال آن: مرده! (مرده به معناي اينكه ديگر توسعه‌ي جدي نخواهد يافت)
ADO.NET‌ اولين فناوري دسترسي به داده‌ها در دات نت فريم ورك بود/است. مدل طراحي آن هم بر اساس امكانات زبان‌هاي آن زمان (زمان شروع به كار دات نت) بود (و تا دات نت 4 هم تغيير عمده‌اي نكرده). براي مثال در زمان ارائه اولين نگارش آن خبري از Generics نبود (در دات نت 2 اضافه شد)؛ يا LINQ وجود نداشت (در دات نت سه و سه و نيم اضافه و تكميل شد). به همين جهت طراحي آن در حال حاضر (با وجود دات نت 4) بوي ماندگي مي‌دهد (مانند استفاده از ديتاست و ديتاتيبل) و با ORM هاي مايكروسافت جهت استفاده از امكانات Generics و LINQ جايگزين شده‌ است.
البته اين مورد تنها مورد مرده‌اي است كه "بايد" ياد گرفت؛ مهم نيست كه ORMs ارائه شده‌اند. مهم اين است كه زير ساخت تمام ORM هاي نوشته شده براي دات نت همين ADO.NET خام است.


LINQ to SQL : مرده!
مايكروسافت با اين فناوري ORM هاي خودش را شروع كرد اما بعد از مدتي ديد كه بهتر است يك نسخه‌ي عمومي‌تر با پشتيباني از بانك‌هاي اطلاعاتي ديگر مانند اوراكل، MySQL و غيره را نيز ارائه دهد. همينجا بود كه آن‌را خيلي ساده با Entity framework جايگزين كرد و در roadmap ارائه شده صراحتا EF به عنوان راه حل توصيه شده دسترسي به داده‌هاي مايكروسافت اعلام شده است (+). حالا اين وسط ديگر مهم نيست شما پروژه نوشته بوديد يا هر چي. ديگر منتظر تغييرات خاصي در LINQ to SQL نباشيد. فقط يك سري رفع باگ و نگهداري پروژه را شاهد خواهيد بود. البته در همان زمان خيلي زود تكذيب كردند كه LINQ to SQL مرده اما براي نمونه آقاي Damien كه عضو اصلي تيم LINQ to SQL بودند، اكنون در تيم XBOX مشغول به كار هستند! (+) تو خود شرح مفصل بخوان از اين مجمل!

ضمنا اين رو هم در نظر داشته باشيد كه LINQ != LINQ to SQL ؛ به عبارتي LINQ به خودي خود فقط يك language feature است.


Windows Forms يا به اختصار WinForms : مرده!
به نظر مظلوم‌تر از اين يكي در دات نت يافت نمي‌شود! همين چند وقت پيش يكي از اعضاي مايكروسافت اين نظر سنجي رو برگزار كرده بود كه "ما چكار كنيم كه شما راحت‌تر از WinForms به WPF مهاجرت كنيد؟!" (+)
در قاموس WPF ، ويندوز فرمز يعني Canvas panel ؛ به عبارتي صلب‌ترين حالت طراحي رابط كاربري و اين انتقال و مهاجرت هرچند براي كساني كه عمري را روي آن گذاشته‌اند، دردناك خواهد بود اما با وجود توانايي‌هاي WPF چيزي را از دست نخواهند داد. سيستم Layout (طرح بندي) در WinForms و همچنين دلفي، بر اساس قراردهي اشياء در مختصات مطلقي در صفحه است. مثلا اين دكمه‌ي خاص در آن نقطه‌ي معلوم قرار مي‌گيرد و همين. اين روش طرح بندي يكي از چندين روش طرح بندي در WPF است كه اصطلاحا Canvas نام دارد. توصيه اكيد و مطلق در WPF آن است كه از Canvas فقط براي طراحي اشياء گرافيكي پيچيده استفاده كنيد و نه طراحي رابط كاربر. چرا؟ چون براي مثال در Silverlight كه برادر كوچكتر WPF محسوب مي‌شود، رابط كاربري آن بايد بتواند همانند HTML انعطاف پذير باشد و با اندازه‌هاي مختلف مرورگر يا اندازه‌ي قلم‌هاي بزرگتر هماهنگ شده و مقاومت كند، بدون اينكه از ريخت بيفتد و اين مورد را ساير سيستم‌هاي طرح بندي رابط كاربر (منهاي Canvas يا همان سيستم طرح بندي WinForms) ارائه مي‌دهند. مورد ديگري كه در WPF و Silverlight بسيار حائز اهميت است ، Content Controls مي‌باشد. چقدر خوب مي‌شد بتوان داخل يك كنترل، كلا يك سيستم طرح بندي را تعريف كرد و اشياء دلخواهي را داخل آن قرار داد. مثلا ToolTip پيش فرض وجود دارد. بسيار هم خوب. بنده علاقه دارم، متن عنوان آن ضخيم باشد. كنار آن يك تصوير كوچك و در سمت چپ آن متن قرار گيرد. براي انجام اينكار در WPF لازم نيست تا شما منتظر نگارش بعدي دات نت باشيد كه دست اندركاران مربوطه با افتخار در يك سند 50 صفحه‌اي توضيح دهند كه چگونه مي‌توان اينكار را انجام داد. يك سيستم طرح بندي را اضافه كنيد. موارد ذكر شده را در آن تعريف كنيد. بدون استفاده از هيچ نوع كامپوننتي يا بدون منتظر ماندن تا نگارش بعدي اين محصولات، به مقصود خود رسيده‌ايد.


ASP.NET Web forms : داره نفس‌هاي آخرش رو مي‌كشه!
از زمانيكه ASP.NET MVC آمد نسخه‌ي Web forms تقريبا فراموش شد. به وبلاگ اسكات گاتري يا Haacked و ساير اعضاي اصلي دات نت كه مراجعه كنيد در سه سال اخير در حد تعداد انگشتان يك دست هم در مورد Web forms مطلب ننوشته‌اند. تمام تمركز و نوآوري‌ها بر روي MVC متمركز شده و حتي در نسخه‌ي 4 دات نت هم فقط يك سري صافكاري مختصر را در Web forms شاهد بوديم مثلا نام كنترل‌ها را خودتان هم در زمان رندر نهايي مي‌توانيد تعيين كنيد! يا لطفا كردند و قسمتي از url routing موجود در ASP.NET MVC را به ASP.NET web forms 4 هم قرض دادند (اين مورد شايد مهم‌ترين تغيير قابل ذكر در ASP.Net web forms 4 است).
البته اين رو هم اضافه كنم كه ASP.NET MVC‌ واقعا قابل احترام است؛ هدف از آن جدا سازي لايه‌هاي برنامه با الگوهاي استاندارد صنعتي (و نه هر روش برنامه نويسي چند لايه من درآوردي)، ترويج كد نويسي بهتر، ترويج Unit testing ، رفع يك سري مشكلات ASP.NET Web forms (مثلا از ViewState هاي حجيم ديگر خبري نيست) و امثال آن است.
براي نمونه توجه شما را به مطلبي كه آقاي Dino Sposito در مورد ASP.NET Webforms نوشته جلب مي‌كنم: (+)
به صورت خلاصه ايشان عنوان كرده زمان طراحي ASP.NET Webforms در 10 سال قبل، هدف ما انتقال ساده‌تر برنامه نويس‌هاي VB به محيط وب بود. به همين جهت دست به اختراع postback ، viewState ، كنترل‌هاي سمت سرور و غيره زديم. (بنابراين تاكيد تمام اين‌ها اين است كه webforms فناوري دهه قبل "بود" و الان بنابر نيازهاي جديد دست به طراحي مجدد زده‌ايم)

در مورد "پايان پروژه ASP.NET Ajax Control Toolkit" هم قبلا مطلب نوشته بودم و اين يكي واقعا official است!


و در پايان بايد متذكر شد كه فلان فناوري مرده يا داره نفس‌هاي آخرش رو مي‌كشه اصلا مهم نيست. هنوز هم هستند سازمان‌هايي كه برنامه‌هاي نوشته شده با ASP كلاسيك (نگارش قبل از ASP.NET Web forms) خود را دارند و خيلي هم از آن راضي هستند!
اين موارد رو از اين جهت نوشتم كه اگر مي‌خواهيد تازه به اين جمع وارد شويد دقيقا بدانيد بايد روي چه مواردي بيشتر وقت بگذاريد و يادگيري كداميك صرفا اتلاف وقت خواهند بود (مثلا EF بر LINQ to SQL ارجح است و اگر امروز مي‌خواهيد شروع كنيد با EF شروع كنيد، يا ديگر كم كم با وجود WPF ، بازار كاري براي WinForms نخواهد بود).

۱۳۸۹/۱۰/۱۳

SQL توليدي در NHibernate از كدام متد صادر شده است؟


اگر مطلب "ذخيره سازي SQL توليدي در NH3" را دنبال كرده باشيد كه يك مثال عملي از "NHibernate 3.0 و عدم وابستگي مستقيم به Log4Net" بود، خروجي حاصل از آن به صورت زير است:
---+ 12/29/2010 05:35:59.75 +---
SQL ...

---+ 12/29/2010 05:35:59.75 +---
SQL ...
و پس از مدتي اين فايل هيچ حسي را منتقل نمي‌كند! يك سري SQL كه لاگ شده‌اند. مشخص نيست كدام متد در كدام كلاس و كدام فضاي نام، سبب صدور اين عبارت SQL ثبت شده‌، گرديده‌ است.
خوشبختانه در دات نت فريم ورك مي‌توان با بررسي Stack trace ، رد كاملي را از فراخوان‌هاي متدها يافت:
StackTrace stackTrace = new StackTrace();
StackFrame stackFrame = stackTrace.GetFrame(1);
MethodBase methodBase = stackFrame.GetMethod();
Type callingType=methodBase.DeclaringType;
با بررسي StackFrame ها امكان يافتن نام متدها، فضاهاي نام و غيره ميسر است. مثلا يكي از كاربردهاي مهم اين روش، ثبت فراخوان‌هاي متدي است كه استثنايي را ثبت كرده است.
بر اين اساس سورس مثال قبل را جهت درج اطلاعات فراخوان‌هاي متدها تكميل كرده‌ام، كه از اين آدرس قابل دريافت است.

اكنون اگر از اين ماژول جديد استفاده كنيم، خروجي نمونه‌‌ي آن به صورت زير خواهد بود:
---+ 01/02/2011 02:19:24.98 +---
-- Void ASP.feedback_aspx.ProcessRequest(System.Web.HttpContext) [File=App_Web_4nvdip40.5.cs, Line=0]
--- Void Prog.Web.UserCtrls.FeedbacksList.Page_Load(System.Object, System.EventArgs) [File=FeedbacksList.ascx.cs, Line=23]
---- Void Prog.Web.UserCtrls.FeedbacksList.BindTo() [File=FeedbacksList.ascx.cs, Line=43]
----- System.Collections.Generic.IList`1[Feedback] Prog.GetAllUserFeedbacks(Int32) [File=FeedbackWebRepository.cs, Line=66]

SELECT ...
@p0 = 3 [Type: Int32 (0)]
به اين معنا كه عبارت SQL ثبت شده، حاصل از پردازش صفحه‌ي feedback.aspx، سپس متد Page_Load آن كه از يوزر كنترل FeedbacksList.ascx استفاده مي‌كند، مي‌باشد. در اينجا فراخواني متد BindTo سبب فراخواني متد GetAllUserFeedbacks در فايل FeedbackWebRepository.cs واقع در سطر 66 آن گرديده است.
اينطوري حداقل مي‌توان دريافت كه SQL توليدي دقيقا به كجا بر مي‌گردد و چه متدي سبب صدور آن شده است.

ملاحظات:
اين ماژول تنها در صورت وجود فايل pdb معتبر كنار اسمبلي‌هاي شما اين خروجي مفصل را توليد خواهد كرد. در غير اينصورت از آن صرفنظر مي‌كند. (براي مثال نام فايل سورس فراخوان، شماره‌ي سطر فراخوان، حتي محل قرارگيري آن فايل بر روي كامپيوتر شما در فايل‌هاي pdb ثبت مي‌گردند؛ به همين جهت توصيه اكيد حين ارائه‌ي نهايي برنامه، حذف اين نوع فايل‌ها است)