نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت اول
سلام برادر.
Quartz.NET رو دست کم نگیرید. بسیار بسیار قدرتمنده. این کتابخانه برای ردیابی وظایف از مفهومی با عنوان JobStore استفاده می‌کنه. به طور پیش فرض داده‌های مربوط به ردیابی وضعیت اجرای وظیفه‌ها در حافظه‌ی RAM قرار می‌گیرند که Quartz.NET اون رو به عنوان RAMJobStore میشناسه. اما برای سناریوهایی همانند آنچه که شما گفتید، پشتیبانی از پیش تعبیه شده برای ذخیره‌ی داده‌های ردیابی رو در پایگاه داده هم فراهم می‌کنه و این قابلیت رو با عنوان AdoJobStore میشناسه.
این کتابخانه در حال حاضر از پایگاه‌های داده‌ی SqlServer، Oracle، MySql، SQLite و Firebird پشتیبانی می‌کنه . فایل اسکریپت برای تولید جداول مورد نیاز در بسته‌ی دانلودی اون هست.
در ضمن، Quartz.NET با خودش سرویسی به همراه داره که میتونه به عنوان سرویس‌های سیستم عامل معرفی بشه تا هرگاه سیستم بالا اومد، اون سرویس به طور خودکار وظیفه‌ها رو از یک فایل با فرمت XML میخونه و اجرا می‌کنه.

در مورد 4 سال پیش که فرمودید، بله بنده هم پیاده سازی‌های مختلفی دیدم. حتی یکی از هموطنان، با استفاده از Cache، برای برای برنامه‌های ASP.NET، قابلیت زمانبندی (هر چند بسیار محدود) ارائه داده بود. استفاده از Timer هم روتین‌ترین چیزی هست که به ذهن همه میرسه اما در قسمت پرسش و پاسخ سایت Quartz.NET، دلایل مطلوب نبودن استفاده‌ی صِرف از Timer در قسمت "?Why not just use System.Timers.Timer" گفته شده. 
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت پنجم
در قسمت قبل پیاده سازی change-tracking در سمت کلاینت توسط Web API را بررسی کردیم. در این قسمت نگاهی به حذف موجودیت‌های منفصل یا disconnected خواهیم داشت.


حذف موجودیت‌های منفصل

فرض کنید موجودیتی را از یک سرویس WCF دریافت کرده اید و می‌خواهید آن را برای حذف علامت گذاری کنید. مدل زیر را در نظر بگیرید.

همانطور که می‌بینید مدل ما صورت حساب‌ها و پرداخت‌های متناظر را ارائه می‌کند. در اپلیکیشن جاری یک سرویس WCF پیاده سازی کرده ایم که عملیات دیتابیسی کلاینت‌ها را مدیریت می‌کند. می‌خواهیم توسط این سرویس آبجکتی را (در اینجا یک موجودیت پرداخت) حذف کنیم. برای ساده نگاه داشتن مثال جاری، مدل‌ها را در خود سرویس تعریف می‌کنیم. برای ایجاد سرویس مذکور مراحل زیر را دنبال کنید.

  • در ویژوال استودیو پروژه جدیدی از نوع WCF Service Library بسازید و نام آن را به Recipe5 تغییر دهید.
  • روی پروژه کلیک راست کنید و گزینه Add New Item را انتخاب کنید. سپس گزینه‌های Data -> ADO.NET Entity Data Model را برگزینید.
  • از ویزارد ویژوال استودیو برای اضافه کردن یک مدل با جداول Invoice و Payment استفاده کنید. برای ساده نگه داشتن مثال جاری، فیلد پیمایشی Payments را از موجودیت Invoice حذف کرده ایم (برای این کار روی خاصیت پیمایشی Payments کلیک راست کنید و گزینه Delete From Model را انتخاب کنید.) روی خاصیت TimeStamp موجودیت Payment کلیک راست کنید و گزینه Properties را انتخاب کنید. سپس مقدار Concurrency Mode آن را به Fixed تغییر دهید. این کار باعث می‌شود که مقدار این فیلد برای کنترل همزمانی بررسی شود. بنابراین مقدار TimeStamp در عبارت WHERE تمام دستورات بروز رسانی و حذف درج خواهد شد.
  • فایل IService1.cs را باز کنید و تعریف سرویس را مانند لیست زیر تغییر دهید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    Payment InsertPayment();
    [OperationContract]
    void DeletePayment(Payment payment);
}
  • فایل Service1.cs را باز کنید و پیاده سازی سرویس را مانند لیست زیر تغییر دهید.
public class Service1 : IService1
{
    public Payment InsertPayment()
    {
        using (var context = new EFRecipesEntities())
        {
            // delete the previous test data
            context.Database.ExecuteSqlCommand("delete from [payments]");
            context.Database.ExecuteSqlCommand("delete from [invoices]");
            var payment = new Payment { Amount = 99.95M, Invoice =
                new Invoice { Description = "Auto Repair" } };
            context.Payments.Add(payment);
            context.SaveChanges();
            return payment;
        }
    }

    public void DeletePayment(Payment payment)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(payment).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }
}
  • برای تست این سرویس به یک کلاینت نیاز داریم. یک پروژه جدید از نوع Console Application به راه حل جاری اضافه کنید و کد آن را مطابق لیست زیر تغییر دهید. فراموش نکنید که ارجاعی به سرویس هم اضافه کنید. روی پروژه کلاینت کلیک راست کرده و Add Service Reference را انتخاب نمایید. ممکن است پیش از آنکه بتوانید سرویس را ارجاع کنید، نیاز باشد پروژه سرویس را ابتدا اجرا کنید (کلیک راست روی پروژه سرویس و انتخاب گزینه Debug -> Start Instance).
class Program
{
    static void Main()
    {
        var client = new Service1Client();
        var payment = client.InsertPayment();
        client.DeletePayment(payment);
    }
}
اگر روی خط اول متد ()Main یک breakpoint قرار دهید می‌توانید مراحل ایجاد و حذف یک موجودیت Payment را دنبال کنید.


شرح مثال جاری

در مثال جاری برای بروز رسانی و حذف موجودیت‌های منفصل از الگویی رایج استفاده کرده ایم که در سرویس‌های WCF و Web API استفاده می‌شود.

در کلاینت با فراخوانی متد InsertPayment یک پرداخت جدید در دیتابیس ذخیره می‌کنیم. این متد، موجودیت Payment ایجاد شده را باز می‌گرداند. موجودیتی که به کلاینت باز می‌گردد از DbContext منفصل (disconnected) است، در واقع در چنین وضعیتی آبجکت context ممکن است در فضای پروسس دیگری قرار داشته باشد، یا حتی روی کامپیوتر دیگری باشد.

برای حذف موجودیت Payment از متد DeletePayment استفاده می‌کنیم. این متد به نوبه خود با فراخوانی متد Entry روی آبجکت context و پاس دادن موجودیت پرداخت بعنوان آرگومان، موجودیت را پیدا می‌کند. سپس وضعیت موجودیت را به EntityState.Deleted تغییر می‌دهیم که این کار آبجکت را برای حذف علامت گذاری می‌کند. فراخوانی‌های بعدی متد ()SaveChanges موجودیت را از دیتابیس حذف خواهد کرد.

آبجکت پرداختی که برای حذف به context الحاق کرده ایم تمام خاصیت هایش مقدار دهی شده اند، درست مانند هنگامی که این موجودیت به دیتابیس اضافه شده بود. اما از آنجا که از foreign key association استفاده می‌کنیم، تنها فیلدهای کلید موجودیت، خاصیت همزمانی (concurrency) و TimeStamp برای تولید عبارت where مناسب لازم هستند که نهایتا منجر به حذف موجودیت خواهد شد. تنها استثنا درباره این قاعده هنگامی است که موجودیت شما یک یا چند خاصیت از نوع پیچیده یا Complex Type داشته باشد. از آنجا که خاصیت‌های پیچیده، اجزای ساختاری یک موجودیت محسوب می‌شوند نمی‌توانند مقادیر null بپذیرند. یک راه حل ساده این است که هنگامی که EF مشغول ساختن عبارت SQL Delete لازم برای حذف موجودیت بر اساس کلید و خاصیت همزمانی آن است، وهله جدیدی از نوع داده پیچیده خود بسازید. اگر فیلدهای complex type را با مقادیر null رها کنید، فراخوانی متد ()SaveChanges با خطا مواجه خواهد شد.

اگر از یک independent association استفاده می‌کنید که در آن کثرت (multiplicity) موجودیت مربوطه یک، یا صفر به یک است، EF انتظار دارد که کلید‌های موجودیت‌ها بدرستی مقدار دهی شوند تا بتواند عبارت where مناسب را برای دستورات بروز رسانی و حذف تولید کند. اگر در مثال جاری از یک رابطه independent association بین موجودیت‌های Invoice و Payment استفاده می‌کردیم، لازم بود تا خاصیت پیمایشی Invoice را با وهله ای از صورت حساب مقدار دهی کنیم که خاصیت InvoiceId آن نیز بدرستی مقدار دهی شده باشد. در این صورت عبارت where نهایی شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.

نکته: هنگام پیاده سازی معماری‌های n-Tier با Entity Framework، استفاده از رویکرد Foreign Key Association برای موجودیت‌های مرتبط باید با ملاحظات جدی انجام شود. پیاده سازی رویکرد Independent Association مشکل است و می‌تواند کد شما را بسیار پیچیده کند. برای مطالعه بیشتر درباره این رویکردها و مزایا و معایب آنها به این لینک مراجعه کنید که توسط یکی از برنامه نویسان تیم EF نوشته شده است.

اگر موجودیت شما تعداد متعددی Independent Association دارد، مقدار دهی تمام آنها می‌تواند خسته کننده شود. رویکردی ساده‌تر این است که وهله مورد نظر را از دیتابیس دریافت کنید و آن را برای حذف علامت گذاری نمایید. این روش کد شما را ساده‌تر می‌کند، اما هنگامی که آبجکت را از دیتابیس دریافت می‌کنید EF کوئری جاری را بازنویسی می‌کند تا تمام روابط یک، یا صفر به یک بارگذاری شوند. مگر آنکه از گزینه NoTracking روی context خود استفاده کنید. اگر در مثال جاری رویکرد Independent Association را پیاده سازی کرده بودیم، هنگامی که موجودیت Payment را از دیتابیس دریافت می‌کنیم (قبل از علامت گذاری برای حذف) EF یک Object state entry برای موجودیت پرداخت و یک Relationship entry برای رابطه بین Payment و Invoice می‌ساخت. سپس وقتی که موجودیت پرداخت را برای حذف علامت گذاری می‌کنیم، EF رابطه بین پرداخت و صورت حساب را هم برای حذف علامت گذاری می‌کند. در اینجا عبارت where تولید شده مانند قبل، شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.

یک گزینه دیگر برای حذف موجودیت‌ها در Independent Associations این است که تمام موجودیت‌های مرتبط را مشخصا بارگذاری کنیم (eager loading) و کل Object graph را برای حذف به سرویس WCF یا Web API بفرستیم. در مثال جاری می‌توانستیم موجودیت صورتحساب مرتبط با موجودیت پرداخت را مشخصا بارگذاری کنیم. اگر می‌خواستیم موجودیت Payment را حذف کنیم، می‌توانستیم کل گراف را که شامل هر دو موجودیت می‌شود به سرویس ارسال کنیم. اما هنگام استفاده از چنین روشی باید بسیار دقت کنید، چرا که این رویکرد پهنای باند بیشتری مصرف می‌کند و زمان پردازش بیشتری هم برای مرتب سازی (serialization) صرف می‌کند. بنابراین هزینه این رویکرد نسبت به سادگی کدی که بدست می‌آید به مراتب بیشتر است.

مطالب
SignalR - قسمت دوم
در قسمت اول بحث‌های مقدماتی درباره وب زمان واقعی (real time web) و معرفی کتابخونه SignalR به همراه یک مثال ساده رو با هم دیدیم. در ادامه به جزئیات ریزی از کتابخونه SignalR که توسط آقای David Fowler توسعه داده میشه میپردازم.

همونطور که قبلا هم اشاره شد قلب این کتابخونه در سمت سرور دو کلاس پایه PersistentConnection و Hub هستن که اولی سطحی پایینتر داره یعنی به تنظیمات و کدنویسی (بسیار) بیشتری برای پیاده‌سازی نیاز داره اما در عوض امکانات سطح پایینتری هم در اختیار برنامه نویس قرار میده که در برخی موارد موردنیاز هستن. در مورد بخشهای مختلف این دو کلاس و نحوه پیاده‌سازی هر دو کلاس فوق، تو آدرسی که قبلا اشاره کردم (آدرس پروژه متن باز این کتابخونه در github) راهنمایی‌های نسبتا مفصلی ارائه شده و نیازی به تکرار این مطالب در اینجا نیست. پیشنهاد میکنم که این مطالب رو با دقت مطالعه کنین.

SignalR به صورت توکار از 4 روشی که در قسمت قبل به اون اشاره شد برای برقراری ارتباط استفاده میکنه. WebSocket که به بسترهای جدیدی نیاز داره. Server-sent Events که تنها در مرورگرهایی که پشتیبانی کاملی از html5 دارند قابل استفاده است (بنابراین ie9 نمیتونه از این روش استفاده کنه). forever frame داده‌ها رو به‌صورت chunked (بخشی از استاندارد http 1.1) دریافت میکنه و روش آخر که Long-polling نام داره از هموش روش قدیمی اِیجکس (Ajax) بهره میبره و با استفاده از آبجکت معروف XmlHttpRequest کار ارتباط و تبادل داده‌ها رو انجام میده.

در اینجا برای بررسی این ارتباطات ابتدا برنامه چت ساده قسمت قبل رو در اینترنت اکسپلورر اجرا کنین و با استفاده developer tool (کلید F12) به درخواستهای مختلف ارسال شده به سرور نگاه کنین. پس از اجرای برنامه و قبل ارسال هرگونه داده به سرور در تب Network این ابزار چیزی شبیه به شکل زیر مشاهده خواهید کرد (البته باید قبل از ورود به صفحه برنامه چت روی دکمه Start capturing کلیک کنین):

با توجه به تصویر بالا SignalR در شرایط موجود بهترین روش برای برقراری ارتباط با سرور رو forever frame تشخیص داده و مشاهده میشه که این ارتباط دائمیه و فعلا نتیجه‌ای از سمت سرور دریافت نکرده و ارتباط کاملا زنده است. البته اگر در این ابزار درباره درخواستهای ارسالی به سرور بیشتر جستجو بکنین اطلاعات بیشتری نصیبتون میشه که آوردنش اینجا بحث رو طولانی میکنه.

حالا برنامه رو در یه مرورگر دیگه که از html5 پشتیبانی میکنه اجرا کنین. مثلا نتیجه در گوگل کروم و ابزار توسعه اون به شکل زیره:

همونطور که میبینین در اینجا روش استفاده شده Server Sent Events هست.

در فایرفاکس هم با استفاده از ابزار محبوب firebug نتیجه مشابه کروم بدست میاد:

البته اگر علاقه زیادی به کندوکاو در جزئیات این درخواستها دارین (مثل خود من) چیزی بهتر از fiddler2 پیدا نمیشه. میتونین پس از ارسال یک متن دوباره این درخواستها رو مورد بررسی قرار بدین و ببینین که چیجوری کانالهای ارتباط پس از ارسال و دریافت دیتا قطع و برقرار میشه.

این نکته رو هم باید یادآور بشم که هرچند که این کتابخونه بهترین روش رو میتونه انتخاب کنه اما به برنامه نویس امکان تعیین صریح روش ارتباط رو هم میده. اگر به راهنماهای این کتابخونه سر بزنین میبینین که امکانات زیادی بهش اضافه شده و امکانات زیادی هم در آینده به اون اضافه میشه. امکاناتی از قبیل ارسال داده‌ها به یک کلاینت خاص و یا به گروهی خاص از کلاینتها، خصوصی‌سازی آدرس سرور و همچنین پشتیبانی از Cross Domain در آخرین نسخه، امکان استفاده از Reactive Extension (بلاگ)، بحث Self Hosting که امکان خیلی جالبیه و میتونه خیلی جاها یه عنوان یک راه‌حل سبک و سریع به کار بیاد، قابلیت فوق العاده در بایندینگ داده‌ها در سمت سرور و مخصوصا کلاینت، امکان تشخیص برقراری یا قطع ارتباط کلاینتها در سمت سرور، استفاده از امکانات این کتابخونه برای برقراری ارتباط با کلاینتها در خارج از فضای کلاسهای مشتق شده از دو کلاس پایه (Hub و PersistentConnection) و چند مورد دیگه تا نسخه جاری اضافه شدند.

درحال حاضر دارم روی یه برنامه چت با امکانات بیشتر کار میکنم که پس از آماده شدن ارائه میدمش. یکی از پروژه‌های متن بازی که با استفاده از این کتابخونه توسعه داده شده jabbr.net است. یه اتاق گفتگوی کامل با امکانات جالبه که میتونین به اون هم یه سری بزنین.

در آخر هم یه لینک جالب برای مطالعه معرفی میکنم: Highest voted Signalr Questions - stackoverflow

مطالب
ذخیره سازی فایل‌ها در دیتابیس یا استفاده از فایل سیستم متداول؟

اگر به ساز و کار شیرپوینت مایکروسافت دقت کنید، همه چیز را داخل دیتابیس ذخیره می‌کند (از اطلاعات رکوردها گرفته تا فایل‌ها و غیره). حال شاید این سؤال مطرح شود که برای ذخیره سازی فایل‌هایی با تعداد بیش از یک میلیون عدد، استفاده از دیتابیس مناسب است یا فایل سیستم متداول. برای پاسخ به این سؤال باید به نکات ذیل توجه داشت:

- هر نوع عملیاتی که بر روی فایل‌ها صورت گیرد، بستن، بازکردن و غیره، نیازمند اعمالی در سطح سیستم عامل است (برای مثال بررسی سطح دسترسی لازم برای انجام این‌کارها).
- هر گونه عملیاتی بر روی فایل‌ها نیازمند یک حداقل قفل گذاری بر روی آن‌ها است که این نیز مصرف CPU قابل توجهی را سبب خواهد شد.
- تمامی اعمال ذکر شده کل سرور و تمامی سرویس‌های در حال اجرا را تحت تاثیر قرار داده و بازدهی آن‌ها‌را کاهش می‌دهند.
- حتی سیستم عامل‌ها نیز از یک file system database جهت مدیریت اعمال خود استفاده می‌کنند اما این روش برای مدیریت میلیون‌ها و میلیاردها فایل بهینه سازی نشده است.
- ذخیره سازی میلیون‌ها و میلیاردها فایل به تدریج سبب ایجاد fragmentation قابل توجهی شده و این مورد نیز بر روی کارآیی تاثیر منفی خواهد گذاشت (همچنین این مورد بر روی طول عمر تجهیزات ذخیره سازی داده‌ها تاثیر منفی دارند).
- تهیه پشتیبان و بازگرداندن میلیون‌ها فایل بسیار زمانگیر است (برای مثال جابجایی یک فایل یک مگابایتی بسیار سریعتر است از جابجایی 100 فایل 10 کیلوبایتی).
- مدیریت تغییرات و همچنین بررسی اینکه چه شخصی چه فایلی را قرار داده، حذف کرده یا تغییر داده است در حالت استفاده از file system مشکل است.
- به صورت پیش فرض عموما مباحث replication و امثال آن‌ توسط روش استفاده از file system خصوصا با تعداد بالای فایل، پشتیبانی نمی‌شود.
- در حالت استفاده از file system ، برنامه‌های وب باید دسترسی write بر روی یک سری پوشه داشته باشند که این مورد همیشه از دیدگاه امنیتی مساله ساز بوده و مشکل آفرین.
- کرش file system مساوی است با کرش سیستم عامل و بازگشت این‌ها زمان‌بر خواهد بود.

با توجه به این نکات استفاده از دیتابیس برای ذخیره سازی تعداد زیادی فایل، مزایای زیر را به همراه خواهد داشت:

- اکثر سیستم‌های دیتابیسی امروزی برای کار با حجم عظیمی از داده‌ها به حد بلوغ خود رسیده‌اند.
- هنگام استفاده از دیتابیس برای ذخیره سازی فایل‌ها دیگر سر و کار ما با میلیون‌ها فایل نخواهد بود و حداکثر چند فایل دیتابیس و ملحقات آن مانند لاگ فایل، کل سیستم را تشکیل می‌دهند.
- فایل‌های دیتابیس برای مثال SQL Server ، همیشه توسط SQL Server در حالت باز قرار داشته و مباحث قفل‌گذاری بر روی فایل‌های دیتابیس و بررسی سطح دسترسی و غیره توسط سیستم عامل در این‌جا به حداقل خود می‌رسد.
- در این حالت بار سیستم عامل شما تنها سیستمی است که مشغول سرویس دهی اطلاعات دیتابیس‌های شما است.
- جستجوی فایل‌ها، حتی جستجو در محتوای این فایل‌های ذخیره شده در یک دیتابیس بسیار سریعتر از روش file system می‌باشد. امکان استفاده از کوئری‌های SQL انعطاف پذیری خاصی را به این سیستم‌ها خواهند داد (برای مثال قابلیت full text search مربوط به SQL server امکان جستجو بر روی رکوردهایی با محتوای pdf را نیز پس از انجام اندکی تنظیمات، دارا می‌باشد).
- هنگام کار با دیتابیس مباحث تراکنشی نقش بسیار حائز اهمیتی را بازی می‌کنند اما عموما سیستم عامل‌ها در این زمینه نیازمند کار و برنامه نویسی قابل توجهی هستند (این قابلیت به ویندوز ویستا اضافه شده است).
- کرش یک دیتابیس عموما سبب کرش سیستم عامل یا حتی کرش سایر دیتابیس‌های موجود نخواهد شد.
- امکان تهیه پشتیبان از دیتابیس‌ها و بازیابی آن‌ها ساده است. (حداقل از بازیابی میلیون‌ها فایل ساده‌تر است)
- امکانات replication به صورت پیش فرض در اکثر سیستم‌های دیتابیسی امروزی مهیا است.
- امکان ثبت وقایع و مدیریت اطلاعات افزوده شده به دیتابیس، از طریق نرم افزارهایی که برای این کار نوشته خواهند شد (یا حتی امکانات توکار این برنامه‌ها) از هر لحاظ نسبت به روش file system برتری دارد.
- امکانات سوئیچ کردن به دیتابیسی دیگر در شبکه در صورت کرش یک نود، مهیا است و پیش بینی شده است.
- برای استفاده از یک دیتابیس توسط یک برنامه وب، نیازی به داشتن دسترسی write بر روی هیچ فولدری وجود ندارد که این خود یک مزیت امنیتی مهم است و همچنین امکان محدود کردن سطوح دسترسی به فایل‌های ذخیره شده در دیتابیس با برنامه‌های نوشته شده نیز ساده‌تر است. (البته در این‌جا مسلما منظور از دیتابیس، دیتابیس Access نیست و SQL Server یا MySQL مد نظر هستند)


نظرات مطالب
استفاده از خواص راهبری در EF Code first جهت ساده سازی کوئر‌ی‌ها
 IEnumerable فقط خواندنی است.
ICollection یک IEnumerable است که قابلیت Add و Remove به آن اضافه شده.
IList یک ICollection است که به اعضای آن از طریق ایندکس‌ها می‌توان دسترسی اتفاقی داشت.
در تمام مثال‌های EF Code first این سایت از
ICollection برای معرفی خواص راهبری استفاده شده چون EF برای انجام اعمال داخلی خودش به مجموعه‌هایی که قابلیت افزودن یا حذف عناصر را داشته باشند، نیاز دارد. به علاوه اگر به سورس EF هم مراجعه کنید برای تشخیص روابط بین کلاس‌ها به دنبال ICollection می‌گردد.
همچنین رسم است حین انتخاب اینترفیس‌هایی از این دست که از هم مشتق می‌شوند، روال انتخاب «the least specific abstraction» رعایت شود. یعنی انتخاب کوچکترین پیاده سازی با حداقل نیازهایی که کاربرد مورد نظر را برآورده می‌کند.

نظرات اشتراک‌ها
زبان برنامه نویسی Erlang
NodeJS یک فریم ورک سمت سرور بر پایه زبان جاوا اسکرپیت می‌باشد. قبلا جاوا اسکریپت فقط توسط مفسرهای مرورگرهای وب تفسیر می‌شد (یعنی فقط می‌تونستیم باهاش کدهای سمت کاربر بنویسیم)، اما حالا با NodeJs می‌تونید کدنویسی سمت سرور کنید. از طرفی چون با جاوا اسکریپت کدنویسی می‌کنید قابلیت استقلال از پلتفرم رو براتون به ارمغان میاره. ارتباط بین سرور و کلاینت موقعی که از NodeJs در طرف سرور استفاده می‌کنید دو طرفه هست، بدین معنی که علاوه بر اینکه کلاینت می‌تونه به سرور درخواست بده بعد سرور به درخواست اون پاسخ بده، سرور هم می‌تونه بدون داده شدن یک درخواست توسط کلاینت داده ای رو به طرف کلاینت ارسال کنه.

زبان Erlang در سال 1986 توسط شرکت Ericson سوئد به منظور استفاده در سرور‌های switching تلفن ساخته شد. این زبان توسط تیمی به سرپرستی  Joe Armstrong معرفی شد تا بتواند از برنامه‌های توزیع‌شده، مقاوم در برابر خطا، بلادرنگ و بی‌وقفه پشتیبانی کند. بعدها این زبان به شکل متن‌باز در اختیار عموم قرار گرفت. یکی از روش‌های برنامه نویسی که توسط این برنامه میشه ازش استفاده کرد، روش تابعی (Functional Programming) هست. این روش قبلا وجود داشت و مدتی هم از مد افتاد، ولی با اومدن پردازنده‌های چند هسته ای استفاده از زبان‌های برنامه نویسی که میشه با اونها تابعی نوشت از سر گرفته شد و حتی مایکروسافت در سال 2010 زبان برنامه نویسی #F رو معرفی کرد. یکی از قابلیتهای زبانهای تابعی سرعت اجرا شدن کدهای اونها هست که اونها رو از زبانهای امری مثل #C و Java جدا می‌کنه.

Scala هم یک زبان برنامه نویسی همه منظوره هست که ویژگی هایی رو از زبان‌های برنامه نویسی شیء گرا داره و همچنین توسط اون میشه برنامه نویسی تابعی انجام داد. از اون به عنوان جانشینی برای جاوا یاد می‌کنند چون قابلتهای اضافه بر جاوا رو داره.
مطالب دوره‌ها
استفاده از XQuery - قسمت اول
XQuery زبانی است که در ترکیب با T-SQL، جهت کار با نوع داده‌ای XML در SQL Server مورد استفاده قرار می‌گیرد. XQuery یک زبان declarative است. عموما زبان‌های برنامه نویسی یا declarative هست و یا imperative. در زبان‌های imperative مانند سی‌شارپ، در هر بار، یک سطر به پردازشگر برای توضیح اعمالی که باید انجام شوند، معرفی خواهد شد. در زبان‌های declarative، توسط زبانی سطح بالا، به پردازشگر عنوان می‌کنیم که قرار است جواب چه چیزی باشد. در این حالت پردازشگر سعی می‌کند تا بهینه‌ترین روش را برای یافتن پاسخ بیابد. SQL و XQuery، هر دو جزو زبان‌های declarative هستند.
XQuery پیاده سازی شده در SQL Server با استانداردهای XQuery 1.0 و XPath 2.0 سازگار است. XQuery برای کار با نودهای مختلف یک سند XML، از XPath استفاده می‌کند. همچنین باید دقت داشت که این زبان به بزرگی و کوچکی حروف حساس است. در آن تمام واژه‌های کلیدی lowercase هستند و تمام متغیرها با علامت $ شروع می‌شوند.


ورودی و خروجی در XQuery

استاندارد XQuery از یک سری توابع ورودی مانند doc برای کار با یک سند و collection برای پردازش چندین سند کمک می‌گیرد. SQL Server از هیچکدام از این توابع پشتیبانی نمی‌کند. در اینجا از XQuery، به کمک متدهای نوع داده‌ای XML استفاده خواهد شد. این متدها شامل موارد ذیل هستند:
- query : یک xml را به عنوان ورودی گرفته و نهایتا یک خروجی XML دیگر را بر می‌گرداند.
- exist : خروجی bit دارد؛ true یا false.
- value : یک خروجی SQL Type را ارائه می‌دهد.
- nodes : خروجی جدولی دارد.
- modify : برای تغییر اطلاعات بکار می‌رود.

این موارد را در طی مثال‌هایی بررسی خواهیم کرد. بنابراین در ادامه نیاز است یک سند XML را که در طی مثال‌های این قسمت مورد استفاده قرار خواهد گرفت، به شرح ذیل مدنظر داشته باشیم:
DECLARE @data XML 

SET @data = 
'<people>
 <person>
  <name>
<givenName>name1</givenName>
<familyName>lname1</familyName>
  </name>
  <age>33</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name2</givenName>
<familyName>lname2</familyName>
  </name>
  <age>40</age>
  <height>short</height>
 </person>
 <person>
  <name>
<givenName>name3</givenName>
<familyName>lname3</familyName>
  </name>
  <age>30</age>
  <height>medium</height>
 </person>
</people>'
در اینجا people در ریشه سند قرار گرفته و سپس سه شخص به مجموعه نودهای آن اضافه شده‌اند.
همانطور که در قسمت قبل نیز ذکر شد، اگر اطلاعات شما در یک فایل XML قرار دارند، نحوه‌ی خواندن آن به شکل یک فیلد XML با کمک openrowset مطابق دستورات زیر خواهد بود:
 declare @data xml
set @data = (select * from openrowset(bulk 'c:\path\data.xml', single_blob) as x)


بررسی متد query

متد query یک XQuery متنی را دریافت کرده، آن‌را بر روی XML ورودی اجرا نموده و سپس یک خروجی XML دیگر را ارائه خواهد داد.
اگر به کتاب‌های استاندارد XQuery مراجعه کنید، به یک چنین کوئری‌هایی خواهید رسید:
  for $p in doc("data.xml")/people/person
 where $p/age > 30
 return $p/name/givenName/text()
همانطور که عنوان شد، متد doc در SQL Server پیاده سازی نشده‌است. بجای آن حداقل از دو روشی که برای مقدار دهی متغیر data عنوان شد، می‌توان استفاده کرد. پس از آن معادل کوئری فوق در SQL Server به نحو ذیل توسط متد query نوشته می‌شود:
 SELECT @data.query('
 for $p in /people/person
 where $p/age > 30
 return $p/name/givenName/text()
 ')
این کوئری givenName تمام اشخاص بالای 30 سال را از سند XML مطرح شده در ابتدای بحث، استخراج می‌کند. خروجی آن نیز یک XML  است و اگر آن‌را در SQL Server managment studio اجرا کنید، یک خط آبی زیر نتیجه‌ی آن کشیده می‌شود که بیانگر لینکی است، به محتوای XML حاصل.



بررسی متد value

در ادامه متد value را بررسی خواهیم کرد. در اینجا قصد داریم مقدار سن اولین شخص را نمایش دهیم:
 SELECT @data.value('/people/person/age', 'int')
پارامتر اول متد value یک XQuery است و پارامتر دوم آن، نوع داده‌ای که قرار است بازگشت داده شود. در اینجا اگر اطلاعاتی یافت نشود، نال بازگشت داده خواهد شد.
اگر کوئری فوق را اجرا کنیم با خطای ذیل مواجه خواهیم شد:
 XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
در اینجا چون از XML Schema استفاده نشده، به untyped Atomic اشاره شده‌است و * پس از آن به zero to many اشاره دارد که برخلاف خروجی zero to one متد value است. این متد، صفر یا حداکثر یک مقدار را باید بازگشت دهد.
برای رفع این مشکل و اشاره به اولین شخص، می‌توان از روش ذیل استفاده کرد:
 SELECT @data.value('(/people/person/age)[1]', 'int')



تولید schema برای سند XML بحث جاری

با استفاده از برنامه Infer.exe مایکروسافت به سادگی می‌توان برای یک سند XML، فایل Schema ایجاد کرد. این برنامه را از اینجا می‌توانید دریافت کنید. پس از آن، اگر فرض کنیم اطلاعات سند XML مثال فوق در فایلی به نام people.xml ذخیره شده‌است، می‌توان schema آن‌را توسط دستور ذیل تولید کرد:
 Infer.exe people.xml -o schema.xsd
people.xml و people.xsd

که نهایتا چنین شکلی را خواهد داشت:
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="people">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="person">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="name">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="givenName" type="xs:string" />
                    <xs:element name="familyName" type="xs:string" />
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
              <xs:element name="age" type="xs:unsignedByte" />
              <xs:element name="height" type="xs:string" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
البته این فایل تولید شده به صورت خودکار، نوع age را unsignedByte تشخیص داده است که در صورت نیاز می‌توان آن‌را به int تبدیل کرد. ولی در کل خروجی آن بسیار با کیفیت و نزدیک به واقعیت است.
این خروجی را که اکنون به صورت یک فایل xsd، در کنار فایل xml معرفی شده به آن می‌توان یافت، با استفاده از openrowset قابل بارگذاری است:
 declare @schema xml
set @schema = (select * from openrowset(bulk 'c:\path\schema_1.xsd', single_blob) as x)
و یا حتی می‌توان یک متغیر از نوع XML را تعریف و سپس محتوای آن را به صورت رشته‌ای در همانجا مقدار دهی کرد.
سپس از این متغیر برای تعریف یک اسکیما کالکشن جدید استفاده خواهیم کرد:
 CREATE XML SCHEMA COLLECTION poeple_xsd AS @schema
در ادامه می‌توان متغیر data را که جهت مقدار دهی سند XML در ابتدای بحث تعریف کردیم، به صورت strongly typed تعریف کنیم:
 DECLARE @data XML(poeple_xsd)
SET @data = 'مانند قبل با همان محتوایی که در ابتدای بحث عنوان شد'
اینبار اگر کوئری ذیل را برای یافتن سن اولین شخص اجرا کنیم:
 SELECT @data.value('/people/person[1]/age', 'int')
خطای واضح‌تری را دریافت خواهیم کرد:
 XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xs:unsignedByte *'
در اینجا xs:unsignedByte بجای xdt:untypedAtomic پیشین گزارش شده‌است.
مشکل کوئری نوشته در اینجا این است که زمانیکه نوع XML تعریف می‌شود، پیش فرض آن content است. یعنی در این حالت چندین root elemnt مجاز هستند. بنابراین person 1 درخواستی، می‌تواند چندین خروجی داشته باشد که در متد value مجاز نیست. این متد، پیش از اجرای کوئری، توسط parser تعیین اعتبار می‌شود و الزاما نیازی نیست تا حتما اجرا شده و سپس مشخص شود که چندین خروجی حاصل آن است.
 اینبار تنها کاری که باید برای رفع مشکل گزارش شده انجام شود، تغییر content پیش فرض به document است:
 DECLARE @data XML(document poeple_xsd)
تغییر دیگری نیاز نیست. حتی نیاز نیست از پرانتزها برای مشخص کردن اولین age استفاده کنیم. چون به کمک schema دقیقا مشخص شده‌است که این سند، چه ساختاری دارد و همانند مثال ابتدای بحث، دیگر یک untyped xml نیست.



sequences در XQuery

Sequences بسیار شبیه به آرایه‌ای از آیتم‌ها هستند و منظور مجموعه‌ای از نودها یا مقادیر آن‌ها است. برای مثال به ورودی کوئری‌های XQuery به شکل توالی از یک سند و به خروجی آن‌ها همانند توالی صفر تا چند نود نگاه کنید.
 DECLARE @x XML
SET @x=''
SELECT @x.query(
'
1,2
(: 1,2 :)
')
در مثال فوق یک توالی اصطلاحا دو atomic value را ایجاد کرده‌ایم. این آیتم‌ها با کاما از یکدیگر جدا می‌شوند. همچنین x، پیش از بکارگیری مقدار دهی شده‌است تا null نباشد. عبارتی که بین (: :) قرار می‌گیرد، یک کامنت تفسیر خواهد شد.

همچنین باید دقت داشت که این توالی خطی تفسیر می‌شود.
 DECLARE @x XML
SET @x=''
SELECT @x.query(
'
for $x in (1,2,3)
for $y in (4,5)
return ($x,$y)
')
در اینجا یک جوین کارتزین نوشته شده است، که در آن یک x با یک y جوین خواهد شد. شاید تصور کنید که خروجی آن مجموعه‌ای است با سه عضو که هر عضو آن با دو عضو دیگر جوین می‌شود. اما اگر کوئری فوق را اجرا کنید، یک خروجی خطی را مشاهده خواهید کرد.

به علاوه در SQL Server امکان تعریف Heterogeneous sequences وجود ندارد؛ به عبارتی توالی بین مقادیر و نودها مجاز نیست. برای مثال اگر کوئری زیر را اجرا کنید:
 DECLARE @x XML
SET @x=''
SELECT @x.query(
'
1, <node/>
')
با خطای ذیل مواجه خواهید شد:
 XQuery [query()]: Heterogeneous sequences are not allowed: found 'xs:integer' and 'element(node,xdt:untyped)'
 
مطالب
روش‌هایی برای بهبود سرعت برنامه‌های مبتنی بر Entity framework
در این مطلب تعدادی از شایع‌ترین مشکلات حین کار با Entity framework که نهایتا به تولید برنامه‌هایی کند منجر می‌شوند، بررسی خواهند شد.

مدل مورد بررسی

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPost> BlogPosts { get; set; }
    }

    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        [ForeignKey("UserId")]
        public virtual User User { get; set; }
        public int UserId { get; set; }
    }
کوئری‌هایی که در ادامه بررسی خواهند شد، بر روی رابطه‌ی one-to-many فوق تعریف شده‌اند؛ یک کاربر به همراه تعدادی مطلب منتشر شده.


مشکل 1: بارگذاری تعداد زیادی ردیف
 var data = context.BlogPosts.ToList();
در بسیاری از اوقات، در برنامه‌های خود تنها نیاز به مشاهده‌ی قسمت خاصی از یک سری از اطلاعات، وجود دارند. به همین جهت بکارگیری متد ToList بدون محدود سازی تعداد ردیف‌های بازگشت داده شده، سبب بالا رفتن مصرف حافظه‌ی سرور و همچنین بالا رفتن میزان داده‌ای که هر بار باید بین سرور و کلاینت منتقل شوند، خواهد شد. یک چنین برنامه‌هایی بسیار مستعد به استثناهایی از نوع out of memory هستند.
راه حل:  با استفاده از Skip و Take، مباحث صفحه‌ی بندی را اعمال کنید.


مشکل 2: بازگرداندن تعداد زیادی ستون
 var data = context.BlogPosts.ToList();
فرض کنید View برنامه، در حال نمایش عناوین مطالب ارسالی است. کوئری فوق، علاوه بر عناوین، شامل تمام خواص تعریف شده‌ی دیگر نیز هست. یک چنین کوئری‌هایی نیز هربار سبب هدر رفتن منابع سرور می‌شوند.
راه حل: اگر تنها نیاز به خاصیت Content است، از Select و سپس ToList استفاده کنید؛ البته به همراه نکته 1.
 var list = context.BlogPosts.Select(x => x.Content).Skip(15).Take(15).ToList();


مشکل 3: گزارشگیری‌هایی که بی‌شباهت به حمله‌ی به دیتابیس نیستند
 foreach (var post in context.BlogPosts)
{
     Console.WriteLine(post.User.Name);
}
فرض کنید قرار است رکوردهای مطالب را نمایش دهید. در حین نمایش این مطالب، در قسمتی از آن باید نام نویسنده نیز درج شود. با توجه به رابطه‌ی تعریف شده، نوشتن post.User.Name به ازای هر مطلب، بسیار ساده به نظر می‌رسد و بدون مشکل هم کار می‌کند. اما ... اگر خروجی SQL این گزارش را مشاهده کنیم، به ازای هر ردیف نمایش داده شده، یکبار رفت و برگشت به بانک اطلاعاتی، جهت دریافت نام نویسنده یک مطلب وجود دارد.
این مورد به lazy loading مشهور است و در مواردی که قرار است با یک مطلب و یک نویسنده کار شود، شاید اهمیتی نداشته باشد. اما در حین نمایش لیستی از اطلاعات، بی‌شباهت به یک حمله‌ی شدید به بانک اطلاعاتی نیست.
راه حل: در گزارشگیری‌ها اگر نیاز به نمایش اطلاعات روابط یک موجودیت وجود دارد، از متد Include استفاده کنید تا Lazy loading لغو شود.
 foreach (var post in context.BlogPosts.Include(x=>x.User))


مشکل 4:  فعال بودن بی‌جهت مباحث ردیابی اطلاعات
 var data = context.BlogPosts.ToList();
در اینجا ما فقط قصد داریم که لیستی از اطلاعات را دریافت و سپس نمایش دهیم. در این بین، هدف، ویرایش یا حذف اطلاعات این لیست نیست. یک چنین کوئری‌هایی مساوی هستند با تشکیل dynamic proxies مخصوص EF جهت ردیابی تغییرات اطلاعات (مباحث AOP توکار). EF توسط این dynamic proxies، محصور کننده‌هایی را برای تک تک آیتم‌های بازگشت داده شده از لیست تهیه می‌کند. در این حالت اگر خاصیتی را تغییر دهید، ابتدا وارد این محصور کننده (غشاء نامرئی) می‌شود، در سیستم ردیابی EF ذخیره شده و سپس به شیء اصلی اعمال می‌گردد. به عبارتی شیء در حال استفاده، هر چند به ظاهر post.User است اما در واقعیت یک User دارای روکشی نامرئی از جنس dynamic proxy‌های EF است. تهیه این روکش‌ها، هزینه‌بر هستند؛ چه از لحاظ میزان مصرف حافظه و چه از نظر سرعت کار.
راه حل: در گزاشگیری‌ها، dynamic proxies را توسط متد AsNoTracking غیرفعال کنید:
 var data = context.BlogPosts.AsNoTracking().Skip(15).Take(15).ToList();


مشکل 5: باز کردن  تعداد اتصالات زیاد به بانک اطلاعاتی در طول یک درخواست

هر Context دارای اتصال منحصربفرد خود به بانک اطلاعاتی است. اگر در طول یک درخواست، بیش از یک Context مورد استفاده قرار گیرد، بدیهی است به همین تعداد اتصال باز شده به بانک اطلاعاتی، خواهیم داشت. نتیجه‌ی آن فشار بیشتر بر بانک اطلاعاتی و همچنین کاهش سرعت برنامه است؛ از این لحاظ که اتصالات TCP برقرار شده، هزینه‌ی بالایی را به همراه دارند.
روش تشخیص:
        private void problem5MoreThan1ConnectionPerRequest() 
        {
            using (var context = new MyContext())
            {
                var count = context.BlogPosts.ToList();
            }
        }
داشتن متدهایی که در آن‌ها کار وهله سازی و dispose زمینه‌ی EF انجام می‌شود (متدهایی که در آن‌ها new Context وجود دارد).
راه حل: برای حل این مساله باید از روش‌های تزریق وابستگی‌ها استفاده کرد. یک Context وهله سازی شده‌ی در طول عمر یک درخواست، باید بین وهله‌های مختلف اشیایی که نیاز به Context دارند، زنده نگه داشته شده و به اشتراک گذاشته شود.


مشکل 6: فرق است بین IList و IEnumerable
DataContext = from user in context.Users
                      where user.Id>10
                      select user;
خروجی کوئری LINQ نوشته شده از نوع IEnumerable است. در EF، هربار مراجعه‌ی مجدد به یک کوئری که خروجی IEnumerable دارد، مساوی است با ارزیابی مجدد آن کوئری. به عبارتی، یکبار دیگر این کوئری بر روی بانک اطلاعاتی اجرا خواهد شد و رفت و برگشت مجددی صورت می‌گیرد.
زمانیکه در حال تهیه‌ی گزارشی هستید، ابزارهای گزارشگیر ممکن است چندین بار از نتیجه‌ی کوئری شما در حین تهیه‌ی گزارش استفاده کنند. بنابراین برخلاف تصور، data binding انجام شده، تنها یکبار سبب اجرای این کوئری نمی‌شود؛ بسته به ساز و کار درونی گزارشگیر، چندین بار ممکن است این کوئری فراخوانی شود.
راه حل: یک ToList را به انتهای این کوئری اضافه کنید. به این ترتیب از نتیجه‌ی کوئری، بجای اصل کوئری استفاده خواهد شد و در این حالت تنها یکبار رفت و برگشت به بانک اطلاعاتی را شاهد خواهید بود.


مشکل 7: فرق است بین IQueryable و IEnumerable

خروجی IEnumerable، یعنی این عبارت را محاسبه کن. خروجی IQueryable یعنی این عبارت را درنظر داشته باش. اگر نیاز است نتایج کوئری‌ها با هم ترکیب شوند، مثلا بر اساس رابط کاربری برنامه، کاربر بتواند شرط‌های مختلف را با هم ترکیب کند، باید از ترکیب IQueryableها استفاده کرد تا سبب رفت و برگشت اضافی به بانک اطلاعاتی نشویم.


مشکل 8: استفاده از کوئری‌های Like دار
 var list = context.BlogPosts.Where(x => x.Content.Contains("test"))
این نوع کوئری‌ها که در نهایت به Like در SQL ترجمه می‌شوند، سبب full table scan خواهند شد که کارآیی بسیار پایینی دارند. در این نوع موارد توصیه شده‌است که از روش‌های full text search استفاده کنید.


مشکل 9: استفاده از Count بجای Any

اگر نیاز است بررسی کنید مجموعه‌ای دارای مقداری است یا خیر، از Count>0 استفاده نکنید. کارآیی Any و کوئری SQL ایی که تولید می‌کند، به مراتب بیشتر و بهینه‌تر است از Count>0.


مشکل 10: سرعت insert پایین است

ردیابی تغییرات را خاموش کرده و از متد جدید AddRange استفاده کنید. همچنین افزونه‌هایی برای Bulk insert نیز موجود هستند.


مشکل 11: شروع برنامه کند است

می‌توان تمام مباحث نگاشت‌های پویای کلاس‌های برنامه به جداول و روابط بانک اطلاعاتی را به صورت کامپایل شده در برنامه ذخیره کرد. این مورد سبب بالا رفتن سرعت شروع برنامه خصوصا در حالتیکه تعداد جداول بالا است می‌شود.
مطالب
چگونه یک عبارت sql را فرمت شده نمایش دهیم؟
در مورد کتابخانه‌ی SQLDom مطالبی را پیشتر در این سایت مطالعه کرده‌اید (^ و ^). یکی دیگر از کاربردهای آن، فرمت عبارات SQL است. برای مثال تبدیل عبارتی مانند
SELECT * FROM tb1 WHERE x1 = '12';
به نمونه‌ی فرمت شده‌ی آن:
SELECT *
FROM tb1
WHERE x1 = '12';
برای اینکار می‌توان از کلاس ذیل کمک گرفت:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.TransactSql.ScriptDom;
 
namespace SqlDomAnalyzer.Core
{
    public static class PrettyPrintTSql
    {
        public static string FormatTSql(string tSql)
        {
            IList<ParseError> errors;
            TSqlScript sqlFragment;
            using (var reader = new StringReader(tSql))
            {
                var parser = new TSql120Parser(initialQuotedIdentifiers: true);
                sqlFragment = (TSqlScript)parser.Parse(reader, out errors);
            }
 
            if (errors != null && errors.Any())
            {
                var sb = new StringBuilder();
                foreach (var error in errors)
                    sb.AppendLine(error.Message);
 
                throw new InvalidOperationException(sb.ToString());
            }
 
            var sql110ScriptGenerator = new Sql120ScriptGenerator(new SqlScriptGeneratorOptions
            {
                SqlVersion = SqlVersion.Sql120
            });
            string finalScript;
            sql110ScriptGenerator.GenerateScript(sqlFragment, out  finalScript);
            return finalScript;
        }
    }
}
در اینجا ابتدا عبارت SQL ورودی Parse شده و سپس به کتابخانه‌ی تولید اسکریپت ScriptDom ارسال می‌شود. خروجی آن، یک خروجی فرمت شده‌است.

نکته‌ی جالب دیگری که در اینجا وجود دارد، تهیه‌ی یک خروجی همواره یک شکل است. برای نمونه سه عبارت SQL زیر را در نظر بگیرید:
SELECT * from tb1 WHERE x1 = '12';
SELECT * from tb1 where x1 = '12';
select * from tb1 WHERE x1 = '12';
در اینجا در عبارت اول، from با حروف کوچک نمایش داده شده‌است. در عبارت دوم، where نیز با حروف کوچک نمایش داده شده‌است و در عبارت سوم اینکار در مورد select نیز تکرار شده‌است.
در هر سه حالت یا هر حالت قابل تصور دیگری، خروجی SQL فرمت شده‌ی حاصل یک چنین شکلی را دارد:
SELECT *
FROM tb1
WHERE x1 = '12';

موارد کاربرد آن؟
علاوه بر نمایش زیبای SQL فرمت نشده، احتمالا برنامه‌های Profiler ایی را دیده‌اید که عنوان می‌کنند قادرند عبارات SQL همانند را تشخیص دهند (جهت یافتن Lazy loading اشتباه). یک چنین خروجی یکسانی، قابلیت تهیه Hash عبارات SQL دریافتی را میسر می‌کند؛ چون دیگر اینبار مهم نیست که اجزای تشکیل دهنده‌ی یک عبارت SQL با حروف بزرگ هستند یا کوچک و فاصله‌ی بین آن‌ها چقدر است و آیا در این بین خطوط جدیدی نیز وجود دارند و امثال آن. خروجی نهایی نرمال شده‌ی توسط Sql120ScriptGenerator همواره یک شکل است. از این دو قابلیت در برنامه‌ی DNTProfiler استفاده شده‌است.