مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification
زیرساخت یکی کردن و فشرده سازی اسکریپت‌ها و فایل‌های CSS نگارش پیشین ASP.NET MVC، به طور کامل از ASP.NET Core حذف شده‌است. در ابتدا (تا نگارش RC2)، روش استفاده‌ی از Gulp را توصیه کردند و در زمان ارائه‌ی نگارش RTM، توصیه‌ی رسمی آن‌ها به Bundler Minifier تغییر کرد (و دیگر Gulp را توصیه نمی‌کنند).


یکی کردن و فشرده سازی فایل‌های استاتیک در ASP.NET Core

هدف از یکی کردن و فشرده سازی فایل‌های استاتیک مانند اسکریپت‌ها و فایل‌های CSS، بهبود کارآیی برنامه با کاهش حجم نهایی ارائه‌ی آن و همچنین کاهش تعداد رفت و برگشت‌های به سرور برای دریافت فایل‌های متعدد مرتبط به آن است. در عملیات Bundling، چندین فایل، به یک تک فایل تبدیل می‌شوند تا اتصالات مرورگر به وب سرور، جهت دریافت آن‌ها به نحو چشمگیری کاهش پیدا کند و در عملیات Minification، مراحل متعددی بر روی کدهای نوشته شده صورت می‌گیرد تا حجم نهایی آن‌ها کاهش پیدا کنند. مایکروسافت در ASP.NET Core RTM، ابزاری را به نام BundlerMinifier.Core جهت برآورده کردن این اهداف ارائه کرده‌است. بنابراین اولین قدم، نصب وابستگی‌های آن است.
برای اینکار یک سطر ذیل  را به فایل project.json اضافه کنید. این بسته باید به قسمت tools اضافه شود تا قابلیت فراخوانی از طریق خط فرمان را نیز پیدا کند:
"tools": {
    "BundlerMinifier.Core": "2.1.258"
},
در غیر اینصورت (ذکر آن در قسمت dependencies) خطاهای ذیل را دریافت خواهید کرد:
No executable found matching command "dotnet-bundle"
Version for package `BundlerMinifier.Core` could not be resolved.


اسکریپت نویسی برای کار با BundlerMinifier.Core

روش‌های زیادی برای کار با ابزار BundlerMinifier.Core وجود دارند؛ منجمله انتخاب فایل‌ها در solution explorer و سپس کلیک راست بر روی فایل‌های انتخاب شده و انتخاب گزینه‌ی bundler & minifier برای یکی کردن و فشرده سازی خودکار این فایل‌ها. برای این منظور افزونه‌ی Bundler & Minifier را نیاز است نصب کنید.
اما روشی که قابلیت خودکارسازی را دارد، استفاده از فایل ویژه‌ی bundleconfig.json این ابزار است. برای این منظور فایل جدید bundleconfig.json را به ریشه‌ی پروژه اضافه کرده و سپس محتوای ذیل را به آن اضافه کنید:
[
    {
        "outputFileName": "wwwroot/css/site.min.css",
        "inputFiles": [
            "wwwroot/css/site.css"
        ]
    },
    {
        "outputFileName": "wwwroot/js/site.min.js",
        "inputFiles": [
            "bower_components/jquery/dist/jquery.min.js",
            "bower_components/jquery-validation/dist/jquery.validate.min.js",
            "bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
        ],
        "minify": {
            "enabled": true,
            "renameLocals": true
        },
        "sourceMap": false
    }
]
فرمت این فایل بسیار خوانا است. برای مثال در یک مدخل آن، در ذیل خاصیت inputFiles، لیست فایل‌های css ذکر می‌شوند و سپس در outputFileName، محل نهایی فایل تولیدی باید ذکر شود. این محل نیز باید از پیش وجود داشته باشد. یعنی باید پوشه‌های js و css را در پوشه‌ی عمومی wwwroot پیشتر ایجاد کرده باشید.
با ذخیره سازی این فایل، کار یکی سازی و فشرده کردن مداخل آن به صورت خودکار صورت خواهد گرفت.


خودکار سازی فرآیند یکی کردن و فشرده سازی فایل‌های استاتیک

برای خودکار سازی این فرآیند، می‌توان به صورت زیر عمل کرد. فایل project.json را گشوده و قسمت scripts آن‌را به نحو ذیل تغییر دهید:
"scripts": {
    "precompile": [
        "dotnet bundle"
    ],
    "prepublish": [
        "bower install"
    ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
روش دستی کار با ابزار BundlerMinifier، مراجعه به خط فرمان و صدور دستور dotnet bundle است (ابتدا از طریق خط فرمان به ریشه‌ی پروژه وارد شده و سپس این دستور را صادر کنید). برای خودکار سازی آن می‌توان این دستور را در قسمت scripts فایل project.json نیز ذکر کرد تا پیش از کامپایل برنامه، کار یکی کردن، فشرده سازی و همچنین کپی فایل نهایی به پوشه‌ی wwwroot برنامه به صورت خودکار انجام شود.

یک نکته: به منوی Build گزینه‌ی Update all bundles نیز با نصب افزونه‌ی Bundler & Minifier اضافه می‌شود. همچنین اگر از منوی Tools گزینه‌ی Task runner explorer را انتخاب کنید، فایل bundleconfig.json توسط آن شناسایی شده و گزینه‌ی update all files را نیز در اینجا مشاهده خواهید کرد.



ساده سازی تعاریف فایل Layout برنامه

در یک چنین حالتی دیگر نباید در فایل layout شما، ارجاعات مستقیمی به پوشه‌ی مثلا bower_components وجود داشته باشند و یا در کلاس آغازین برنامه، نیازی نیست تا این پوشه را عمومی کنید. لیست مداخلی را که نیاز دارید، به ترتیب از پوشه‌های مختلفی تهیه و در فایل bundleconfig.json ذکر کنید تا یکی شده و خروجی js/site.min.js را تشکیل دهند. این مورد تنها مدخلی است که نیاز است در فایل layout برنامه ذکر شود (بجای چندین و چند مدخل مورد نیاز):
 <script src="~/js/site.min.js" asp-append-version="true" type="text/javascript"></script>
در مورد ویژگی asp-append-version نیز پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» بحث شد و به آن مکانیزم cache busting می‌گویند. این ویژگی سبب خواهد شد تا یک کوئری استرینگ v=xyz? مانند، به انتهای آدرس اسکریپت یا فایل css یا هر فایل استاتیک دیگری اضافه شود. با تغییر محتوای این فایل، قسمت xyz به صورت خودکار تغییر خواهد کرد و به این ترتیب مرورگر همواره آخرین نگارش این فایل را دریافت می‌کند.
مطالب
RequireJs
در طراحی و توسعه پروژه‌های تحت وب در مقیاس بزرگ برای اینکه مدیریت پروژه راحت‌تر شود کد‌های مورد نظر را در چند ماژول قرار می‌دهند در نتیجه کد‌های پروژه در بلاک‌های کوچک‌تر قرار خواهند داشت. نوشتن پروژه به صورت ماژولار قابلیت استفاده مجدد از کد‌های برنامه را افزایش می‌دهد، علاوه بر آن مدیریت پروژه در فاز نگهداری آسان‌تر خواهد شد؛ از طرفی دیگر وابستگی بین ماژول‌ها و تامین آن ها، همواره مهم‌ترین مفهوم برای توسعه دهندگان پروژه‌های وب است. RequireJs یکی از فریم ورک‌های محبوب برای مدیریت وابستگی‌های بین ماژول‌ها است و کاربرد اصلی آن راحت سازی مفهوم modularity در اینگونه پروژه هاست.
پروژه‌های بزرگ عموما دارای یک یا چند فایل جاوااسکریپ هستند که برای استفاده از آن‌ها در صفحات از تگ script استفاده می‌شود. اگر این فایل‌ها دارای وابستگی به هم باشند، ترتیب فراخوانی این فایل در تگ script مهم است. برای مثال:
یک پروژه دارای فایل‌های زیر خواهد بود:

purchase.js
function purchaseProduct(){
  console.log("Function : purchaseProduct");
  var credits = getCredits();
  if(credits > 0){
    reserveProduct();
    return true;
  }
  return false;
}
product.js
function reserveProduct(){
  console.log("Function : reserveProduct"); 
  return true;
}
credits.js
function getCredits(){
  console.log("Function : getCredits"); 
  var credits = "100";
  return credits;
}
همان طور که در فایل‌های بالا مشاهده می‌کنید در فایل purchase.js از دو فایل دیگر استفاده شده است. در فایل main.js پروژه کد زیر را برای استفاده از فایل‌های بالا می‌نویسیم:
main.js
var result = purchaseProduct();
فرض کنید در فایل Html تگ‌های script ما به صورت زیر باشد:
<script src="products.js"></script>
<script src="purchase.js"></script>
<script src="main.js"></script>
<script src="credits.js"></script>
از آنجا که لود فایل purchase.js وابستگی مستقیم به لود فایل credits.js دارد و از طرفی دیگر به دلیل این که فایل credit.Js بعد از تمام فایل‌ها لود می‌شود در نتیجه برنامه اجرا نخواهد شد و با خطای زیر روبرو می‌شویم:

فقط کافیست تصور کنید که تعداد و حجم کد‌های این فایل‌ها در یک پروژه زیاد باشد یا حتی این فایل‌ها توسط یک برنامه نویس دیگر تهیه و تدوین شده باشد؛ در نتیجه به خاطر سپردن این وابستگی‌ها به طور قطع کار سخت خواهد بود و در خیلی موارد طافت فرسا.

 RequireJs چگونه برای حل این مشکل به ما کمک می‌کند؟
با استفاده از فریم ورک RequireJs کد‌های ما به ماژول‌های کوچک‌تر شکسته می‌شود و وابستگی ماژول‌ها در تنظیمات لود فایل ثبت می‌شود. در ضمن این فریم ورک با مرورگرها جدید و محبوب کاملا سازگار است. برای شروع فایل‌های مورد نیاز را از اینجا دانلود نمایید. البته می‌توانید از nuget هم استفاده کنید:
PM> Install-Package RequireJS
در مثال بالا فایل‌های جاوااسکریپ به صورت زیر تغییر خواهد کرد:
purchase.js
define(["credits","products"], function(credits,products) { 
  console.log("Function : purchaseProduct"); 
  return {
    purchaseProduct: function() { 
      var credit = credits.getCredits();
      if(credit > 0){
        products.reserveProduct();
        return true;
      }
      return false;
    }
  }
});
همان طور که مشاهده می‌کنید در فایل بالا از دستور define برای تعریف ماژول استفاده شده است و نام دو ماژول products و credits را به صورت پارامتر برای مشخص کردن وابستگی ماژول‌ها تعریف کردیم.
products.js
define(function(products) {
  return {
    reserveProduct: function() {
      console.log("Function : reserveProduct"); 
      return true;
    }
  }
});
credits.js
define(function() {
  console.log("Function : getCredits"); 
  return {
    getCredits: function() {
      var credits = "100";
      return credits;
    }
  }
});
به دلیل این که فایل‌های بالا وابستگی به ماژول‌های دیگر ندارند در نتیجه دستور define فقط شامل تعریف تابع است.
کافیست در فایل main.js، برای استفاده از فایل purchase.js از دستور require استفاده کنید.
require(['purchase'], function( purchase ) {
    var result = purchase.purchaseProduct(); 
});
مهم‌ترین مزیتی که این روش دارد این است که دیگر نیازی به نوشتن تگ‌های Script (آن هم به ترتیب درست) برای فراخوانی فایل‌های جاوااسکریپتی نخواهد بود؛ از طرفی دیگر وابستگی بین این فایل‌ها در هنگام تعریف ماژول مشخص خواهد شد. از آن به بعد وظیفه تامین فایل‌های مورد نیاز برای لود ماژول بر عهده RequireJs است.

تفاوت بین دستور require و define
دستور define صرفا برای تعیین وابستگی ماژول استفاده می‌شود در حالی که دستور require برای فراخوانی و لود ماژول کاربرد دارد و به نوعی به عنوان نقطه شروع اجرای برنامه خواهد بود.
در پست بعدی به پیاده سازی مثال بالا به کمک RequireJs در قالب یک پروژه Asp.Net MVC خواهم پرداخت.
مطالب
چگونه از CodePlex به عنوان مخزنی جهت ذخیره سازی کدهای سایت یا وبلاگ خود استفاده کنیم؟

به شخصه اعتقادی ندارم که جهت مدیریت کار رایگانی که انجام می‌شود از امکانات غیر رایگان استفاده کرد. تابحال برای ذخیره سازی کدهای منتشر شده در این وبلاگ از persiangig تا googlepages مرحوم تا رپیدشیر تا ... استفاده کرده‌ام. نه امکان لیست کردن سریع آن‌ها موجود است و نه مشخص است که چه تعدادی از آن‌ها هنوز وجود خارجی داشته و از سرورهای یاد شده پاک نشده‌اند. اخیرا تعدادی وبلاگ برنامه نویسی را یافته‌ام که از سایت CodePlex به عنوان مخزنی برای ذخیره سازی کدها و مثال‌های منتشر شده در وبلاگ خود استفاده می‌کنند. این کار چند مزیت دارد:
- رایگان است (فضا، پهنای باند، اسکریپت و غیره)
- به صورت تضمینی تا 10 سال دیگر هم پابرجا است.
- درب آن به روی کاربران ایرانی باز است (برخلاف مثلا سایت googlecodes یا رفتار اخیر سورس فورج و غیره، سایت CodePlex در این چندسال رویه ثابتی داشته است)
- امکان مشاهده‌ی لیست تمامی کدهای منتشر شده‌ موجود است.
- امکان ثبت توضیحات کنار هر کد منتشر شده نیز وجود دارد.
- امکان دریافت یکجای آن‌ها با توجه به استفاده از ابزارهای سورس کنترل مهیا است.
- امکان دریافت بهینه‌ی موارد جدید هم برای کاربران وجود دارد. کاربری که یکبار با استفاده از ابزارهای سورس کنترل، کدهای موجود را دریافت کرده، در بار بعدی دریافت اطلاعات، تنها موارد تغییر کرده یا جدید را دریافت خواهد کرد و نه تمام اطلاعات کل مخزن را از ابتدا تا به امروز.
- امکان مشاهده‌ی آمار دریافت‌ها، مراجعات، سایت‌هایی که به شما لینک داده‌اند و غیره فراهم است.
- امکان دعوت کردن از افراد دیگر نیز جهت به روز رسانی مخزن کد تدارک دیده شده است.
- کلیه اعضای CodePlex بدون نیاز به عضویت در گروه مخزن کد شما، می‌توانند جهت تکمیل یا اصلاح کار شما patch یا وصله ارسال کنند.
و ...

اما برای استفاده از این امکانات نیاز است حداقل اطلاعاتی را در مورد کار با ابزارهای سورس کنترل داشت، که خلاصه‌ی مختصر و مفید آن‌را در ادامه ملاحظه خواهید نمود:
0 - دریافت و نصب برنامه‌ی TortoiseSVN
1- ثبت نام در سایت CodePlex
رایگان است.

2- ایجاد یک پروژه‌ی جدید


که به همراه وارد کردن مشخصات اولیه آن است:


تنها نکته‌ی مهم آن انتخاب سورس کنترل Team foundation server و سپس Subversion است چون می‌خواهیم با استفاده از TortoiseSVN کار به روز رسانی اطلاعات را انجام دهیم.

3- انتخاب مجوز برای پروژه در برگه‌ی License پروژه ایجاد شده

تا مجوزی را برای پروژه انتخاب نکنید، مجوز ارائه‌ی عمومی آن‌را نخواهید یافت. در مورد مقایسه‌ی مجوز‌های سورس باز لطفا به این مطلب مراجعه کنید.

4- checkout کردن سورس کنترل
ابتدا به برگه‌ی source code پروژه مراجعه کرده و بر روی لینک subversion در کنار صفحه کلیک کنید.

در صفحه‌ی باز شده مشخصات اتصال به مخزن کد را جهت به روز رسانی آن مشاهده خواهید نمود.
اکنون جهت استفاده از آن یک پوشه‌ی مشخص را در سیستم خود برای قرار دادن فایل‌ها و ارسال آن به مخزن کد ایجاد کنید. مثلا به نام SiteRepository . سپس جایی داخل این پوشه، کلیک راست کرده و گزینه‌ی SVN Checkout را انتخاب کنید:


در صفحه‌ی باز شده آدرس svn مربوط به پروژه خود را وارد نموده و بر روی Ok کلیک کنید:



در صفحه‌ی بعدی باید نام کاربری و کلمه‌ی عبور مرتبط با حساب کاربری سایت کدپلکس خود را وارد نمائید. همچنین بهتر است گزینه‌ی به خاطر سپاری آن‌را نیز برای سهولت کار در دفعات بعدی انتخاب کنید:



به این صورت یک پوشه‌ی مخفی svn در اینجا تشکیل خواهد شد که اطلاعات مخزن کد را در خود نگهداری می‌کند و نباید آن‌را حذف کرد، تغییر داد، یا جابجا کرد.



5- اضافه کردن فایل‌های دلخواه به مخزن کد
برای اضافه کردن کدهای مورد نظر خود، آن‌ها را به پوشه‌ی SiteRepository فوق کپی کرده و سپس بر روی آن‌ها کلیک راست نموده و گزینه‌ی Add مربوط به TortoiseSVN را انتخاب کنید:



به این صورت تنها فایل‌های مورد نظر جهت اضافه شدن به مخزن کد علامتگذاری خواهند شد (ایجاد پوشه و قرار دادن فایل‌ها درون آ‌ن‌ها نیز به همین ترتیب است):



اکنون برای تکمیل فرایند، جایی درون پوشه کلیک راست کرده و گزینه‌ی SVN Commit را انتخاب کنید:



در صفحه‌ی باز شده توضیحاتی را در مورد فایل‌های ارسالی وارد کرده و سپس بر روی دکمه‌ی OK کلیک نمائید:



پس از مدتی کار هماهنگ سازی اطلاعات با مخزن کد صورت خواهد گرفت:



همچنین آیکون فایل‌های مورد نظر نیز بر روی کامپیوتر شما به صورت زیر تغییر خواهند کرد:



6- ارائه نهایی پروژه
فراموش نکنید که پس از ایجاد یک پروژه‌ی جدید، انتخاب مجوز و ارسال فایل‌های مورد نظر، باید بر روی دکمه‌ی publish this project در بالای صفحه کلیک کرد. در غیراینصورت پروژه‌ی شما در روز بعد به صورت خودکار از سایت CodePlex حذف می‌گردد:




برای نمونه مخزن جدید کدهای وبلاگ جاری را در آدرس زیر می‌توانید مشاهده کنید:


در دفعات آتی، تنها تکرار مرحله 5 یعنی کپی کردن فایل‌های مورد نظر به پوشه‌ی SiteRepository، سپس Add و در نهایت Commit آن‌ها کفایت می‌کند و نیازی به تکرار سایر مراحل نیست. عملیات هماهنگ سازی با مخزن کد هم بسیار بهینه است و تنها فایل‌هایی که اخیرا اضافه شده و هنوز ارسال نشده‌اند، Commit خواهند شد.
کاربران نهایی هم یا از طریق اینترفیس تحت وب سایت می‌توانند از فایل‌های شما استفاده کنند و یا روش دیگری هم برای این منظور وجود دارد (همان Checkout کردن یاد شده و سپس هر بار انتخاب گزینه‌ی SVN update بجای Commit جهت دریافت فایل‌های جدید و نه کل مخزن کد به صورت یکجا).

مطالب
خواندنی‌های 23 فروردین

SubSonic یکی دیگر از ORM های تهیه شده برای دات نت فریم ورک است که توسط یکی از اعضای مایکروسافت هدایت می‌شود. این پروژه سورس باز و رایگان است.

گردآوری جالبی است؛ قابل توجه دوستانی که با php و سرورهای لینوکسی کار می‌کنند.

همانطور که مطلع هستید نگارش نهایی SP1 مربوط به SQL Server 2008 مدتی است که ارائه شده است و یکی از قابلیت‌های آن Slipstreaming نصب آن است (من ترجمه‌اش می‌کنم "نصب ممزوج"). منظور از آن هم این است که تمام پچ‌های قبلی را می‌شود با نصاب ابتدایی یکی کرد. (بنابراین نصب ممزوج معادل بدی نیست، "دایر کردن فرو رفتنی" هم به فرهنگستان پیشنهاد می‌شود :) )
برای مثال در SQL server 2005 شما مجبور هستید که ابتدا نگارش RTM آن‌را نصب کنید و بعد آخرین سرویس پک ارائه شده را، اما در حالت پشتیبانی از نصب ممزوج، می‌شود فایل‌های سرویس پک را مستقیما بر روی فایل‌های نصاب اولیه بازنویسی کرد و به این صورت با نصب آن، به یکباره SQL Server 2008 SP1 را خواهید داشت و دیگر نیازی به reboot های مکرر و توقف و راه اندازی سرویس‌های گوناگون ویندوز نیست (این مساله در یک سرور حائز اهمیت است).

نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
چند نکته‌ی تکمیلی در مورد استفاده از TypeScript در پروژه‌های Blazor

- اگر مانند مثال زیر، در یک فایل ts. نیاز به استفاده‌ی از یک کتابخانه‌ی خارجی، مانند اسکریپت‌های خود Blazor بود:
Blazor.addEventListener('enhancedload', () => {
            // ...
});
می‌توان با افزودن یک سطر زیر، به دقیقا یک سطر قبل از تعاریف فوق، از خطای کامپایل TypeScript صرفنظر کرد و خروجی js. گرفت:
// @ts-ignore

- اگر چندین فایل ts. را دارید و می‌خواهید همه‌ی آن‌ها را با هم یکی کنید و یک خروجی js. را از تمام آن‌ها بگیرید، می‌توان از فایل tsconfig.json زیر استفاده کرد:
{
  "compilerOptions": {
    "strict": true,
    "removeComments": true,
    "sourceMap": false,
    "noEmitOnError": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "target": "ES2020",
    "outFile": "wwwroot/scripts/app.js"
  },
  "include": [
    "Scripts/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}
در این حالت، تمام فایل‌های ts. در پوشه‌ی Scripts قرار گرفته‌اند و خروجی نهایی و یکی شده، در فایل wwwroot/scripts/app.js درج می‌شود. با توجه به این تنظیمات، تمام فایل‌های ts. باید دارای فضای نام باشند (مانند مثال قبل) تا کار کامپایل آن‌ها میسر شود.
مطالب
React reconciliation
در پروژه‌های React، نقطه‌ی آغازین فرآیند rendering، قطعه کد زیر میباشد که درون فایل index.js قرار دارد:
ReactDOM.render(<App />, document.getElementById('root'));
 
توسط متد ReactDOM.render یک وهله از کامپوننت App ایجاد شده و منجر به فراخوانی متد render کامپوننت مربوطه خواهد شد. درون متد ذکر شده ممکن است چندین کامپوننت تعریف شده باشند. در نتیجه به ازای هر کامپوننت، متد render متناظر با آن فراخوانی خواهد شد. در نهایت یک ساختار سلسله مراتبی از عناصر HTML تشکیل شده و توسط آرگومان دوم متد فوق، درون عنصری با آی‌دی root درج خواهند شد. 
برای مثال کامپوننت‌های زیر را در نظر بگیرید که هر کدام درون فایل مربوط به خودشان هستند:
// List.js
import React, { Component } from 'react';
import { ActionButton } from './ActionButton';
export class List extends Component {
    constructor(props) {
        super(props);
        this.state = {
            items: [
                { id: 1, title: "Item 1" },
                { id: 2, title: "Item 2" },
                { id: 3, title: "Item 3" },
                { id: 4, title: "Item 4" },
                { id: 5, title: "Item 5" },
            ]
        };
    }

    reverse = () => {
        this.setState({ items: this.state.items.reverse() });
    }

    render() {
        console.log("Render List Component");
        return (
            <div className="list-container">
                <ActionButton callback={this.reverse} />
                <ul>
                    {this.state.items.map(item => {
                        return <li key={item.id}>
                            {item.title}
                        </li>
                    })}
                </ul>
            </div>
        );
    }
}

// ActionButton.js
import React, { Component } from 'react';
export class ActionButton extends Component {
    render() {
        console.log("Render ActionButton Component");
        return (
            <button onClick={this.props.callback}>Click me</button>
        );
    }
}
در نهایت درون کامپوننت App.js نیز همچین ساختاری خواهیم داشت:
import React from 'react';
import './App.css';
import { List } from './List';

function App() {
  console.log("Render App Component");
  return (
    <div className="App">
      <h1>Reconciliation Process</h1>
      <List />
    </div>
  );
}

export default App;

 
درون متد render هر کدام از کامپوننت‌های فوق یک console.log نوشته شده است. با اجرای برنامه خروجی زیر در کنسول مرورگر قابل مشاهده است:
Render App Component
Render List Component
Render ActionButton Component

دلیل آن نیز این است که با اجرای اپلیکیشن، React از تمامی کامپوننت‌ها درخواست فراخوانی متد renderشان را خواهد کرد. به محض اینکه محتوای HTML درون صفحه نمایش داده شد، اپلیکیشن در وضعیتی تحت عنوان reconciled قرار خواهد گرفت؛ در این مرحله، خروجی نمایش داده شده با state کامپوننت‌ها سازگار است. React منتظر خواهد بود تا تغییری رخ دهد که در بیشتر اپلیکیشن‌ها این تغییر توسط کاربر انجام خواهد گرفت که در نهایت منجر به فراخوانی متد setState میشود. متد setState در واقع state dataی یک کامپوننت را بروزرسانی میکند؛ اما اینکار کامپوننت را stale یا کهنه میکند. یعنی محتوای HTMLی نمایش داده شده به کاربر قدیمی خواهد شد و با تنها یک رخداد ممکن است چندین state data تغییر کنند. این کار باعث خواهد شد که متد render برای تمامی کامپوننت‌های تغییر کرده فراخوانی شود. اما از آنجائیکه بروزرسانی DOM عملی هزینه‌بر است، در نتیجه React محتوای قبلی (کش شده با عنوان Virtual DOM) را با محتوای جدید مقایسه میکند تا کمترین میزان بروزرسانی DOM را داشته باشد. به این فرآیند Reconciliation گفته میشود.
برای درک بهتر این فرآیند، درون کامپوننت List یک آی‌دی به عنصر ul اضافه خواهیم کرد:
<ul id="list">
    {this.state.items.map(item => {
        return <li key={item.id}>
            {item.title}
        </li>
    })}
</ul>

اکنون در حالیکه پروژه در حال اجرا است، کد زیر را درون کنسول مرورگر اجرا کنید:
document.getElementById("list").classList.add("message")

همانطور که مشخص است کد فوق کلاسی با نام message را به عنصر ul اضافه کرده است:

.message {
  border: 1px solid green;
  padding: 2rem;
}


اکنون وقتی بر روی دکمه Click me کلیک کنیم، محتوای درون کامپوننت فوق تغییر پیدا میکند، اما عنصر ul همچنان دارای کلاس message است؛ دلیل آن نیز همانطور که عنوان شد این است که React محتوای تولید شده توسط کامپوننت List را با Virtual DOM خودش مقایسه میکند و چون از لحاظ ساختار DOM با هم برابر هستند تغییری در ساختار خروجی کامپوننت ایجاد نمیکند و فقط قسمت‌هایی را که تغییر کرده‌اند، بروزرسانی خواهد کرد.


اکنون کامپوننت فوق را اینگونه تغییر خواهیم داد:

import React, { Component } from 'react';
import { ActionButton } from './ActionButton';
export class List extends Component {
    constructor(props) {
        // as before
    }

    reverse = () => {
        this.setState({ items: this.state.items.reverse(), wrapInDiv: true });
    }

    generateElement = () => {
        const list = <ul id="list">
            {this.state.items.map(item => {
                return <li key={item.id}>
                    {item.title}
                </li>
            })}
        </ul>;

        return this.state.wrapInDiv ? <div>{list}</div> : list;
    }

    render() {
        console.log("Render List Component");
        return (
            <div className="list-container">
                <ActionButton callback={this.reverse} />
                {this.generateElement()}
            </div>
        );
    }
}

  در اینجا اگر مجدداً کلاس ذکر شده را به عنصر ul اضافه کنیم و سپس بر روی دکمه کلیک کنیم، خواهیم دید که کلاس message از عنصر ul حذف خواهد شد. دلیل آن نیز این است که ساختار HTML با Virtual DOM یکی نیست و React کامپوننت موردنظر را با تغییرات جدید مجدداً رندر خواهد کرد. 

نکته: برای مشاهده تغییرات DOM می‌توانید قابلیت Paint flashing را در مرورگر Chrome از قسمت Developer Tool > More tools > Rendering فعال کنید و تغییرات را به صورت visual ببینید.
مطالب
پیاده سازی INotifyPropertyChanged با استفاده از Unity Container
AOP یکی از فناوری‌های مرتبط با توسعه نرم افزار محسوب می‌شود که توسط آن می‌توان اعمال مشترک و متداول موجود در برنامه را در یک یا چند ماژول مختلف قرار داد (که به آن‌ها Aspects نیز گفته می‌شود) و سپس آن‌ها را به مکان‌های مختلفی در برنامه متصل ساخت. عموما Aspects، قابلیت‌هایی را که قسمت عمده‌ای از برنامه را تحت پوشش قرار می‌دهند، کپسوله می‌کنند. اصطلاحا به این نوع قابلیت‌های مشترک، تکراری و پراکنده مورد نیاز در قسمت‌های مختلف برنامه، Cross cutting concerns نیز گفته می‌شود؛ مانند اعمال ثبت وقایع سیستم، امنیت، مدیریت تراکنش‌ها و امثال آن. با قرار دادن این نیازها در Aspects مجزا، می‌توان برنامه‌ای را تشکیل داد که از کدهای تکراری عاری است.

پیاده سازی INotifyPropertyChanged یکی از این مسائل می‌باشد که می‌توان آن را در یک Aspect محصور و در ماژول‌های مختلف استفاده کرد.

مسئله:
کلاس زیر مفروض است:
public class Foo
{
        public virtual int Id { get; set; }

        public virtual string Name { get; set; }
}
اکنون می‌خواهیم  کلاس Foo را به INotifyPropertyChanged مزین، و  یک Subscriber به قسمت set پراپرتی‌های کلاس‌ تزریق کنیم.

راه حل:
ابتدا پکیچ‌های Unity را از Nuget دریافت کنید:
PM> Install-Package Unity.Interception
این پکیچ وابستگی‌های خود را که Unity و CommonServiceLocator هستند نیز دریافت می‌کند.

حال یک Interceptor که اینترفیس IInterceptionBehavior را پیاده سازی می‌کند، می‌نویسیم:
namespace NotifyPropertyChangedInterceptor.Interceptions
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Reflection;
    using Microsoft.Practices.Unity.InterceptionExtension;

    class NotifyPropertyChangedBehavior : IInterceptionBehavior
    {
        private event PropertyChangedEventHandler PropertyChanged;

        private readonly MethodInfo _addEventMethodInfo =
            typeof(INotifyPropertyChanged).GetEvent("PropertyChanged").GetAddMethod();

        private readonly MethodInfo _removeEventMethodInfo =
            typeof(INotifyPropertyChanged).GetEvent("PropertyChanged").GetRemoveMethod();

        
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            if (input.MethodBase == _addEventMethodInfo)
            {
                return AddEventSubscription(input);
            }

            if (input.MethodBase == _removeEventMethodInfo)
            {
                return RemoveEventSubscription(input);
            }
            
            if (IsPropertySetter(input))
            {
                return InterceptPropertySet(input, getNext);
            }
            
            return getNext()(input, getNext);
        }

        public bool WillExecute
        {
            get { return true; }
        }

        public IEnumerable<Type> GetRequiredInterfaces()
        {
            yield return typeof(INotifyPropertyChanged);
        }

        private IMethodReturn AddEventSubscription(IMethodInvocation input)
        {
            var subscriber = (PropertyChangedEventHandler)input.Arguments[0];
            PropertyChanged += subscriber;

            return input.CreateMethodReturn(null);
        }

        private IMethodReturn RemoveEventSubscription(IMethodInvocation input)
        {
            var subscriber = (PropertyChangedEventHandler)input.Arguments[0];
            PropertyChanged -= subscriber;

            return input.CreateMethodReturn(null);
        }

        private bool IsPropertySetter(IMethodInvocation input)
        {
            return input.MethodBase.IsSpecialName && input.MethodBase.Name.StartsWith("set_");
        }

        private IMethodReturn InterceptPropertySet(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            var propertyName = input.MethodBase.Name.Substring(4);

            var subscribers = PropertyChanged;
            if (subscribers != null)
            {
                subscribers(input.Target, new PropertyChangedEventArgs(propertyName));
            }

            return getNext()(input, getNext);
        }
    }
}

متد Invoke : این متد Behavior مورد نظر را پردازش می‌کند (در اینجا، تزریق یک Subscriber در قسمت set پراپرتی ها).
متد GetRequiredInterfaces : یک روش است برای یافتن کلاس هایی که با اینترفیس IInterceptionBehavior مزین شده‌اند.
پراپرتی WillExecute : ابن پراپرتی به Unity می‌گوید که این Behavior اعمال شود یا نه. اگر مقدار برگشتی آن false باشد، متد Invoke اجرا نخواهد شد.
همانطور که در متد Invoke مشاهد می‌کنید، شرط هایی برای افزودن و حذف یک  Subscriber و چک کردن متد set نوشته شده و در غیر این صورت کنترل به متد بعدی داده می‌شود.

اتصال Interceptor به کلاس ها
در ادامه Unity را برای ساخت یک نمونه از کلاس پیکربندی می‌کنیم:
var container = new UnityContainer();

container.RegisterType<Foo, Foo>(
                new AdditionalInterface<INotifyPropertyChanged>(),
                new Interceptor<VirtualMethodInterceptor>(),
                new InterceptionBehavior<NotifyPropertyChangedBehavior>())
                .AddNewExtension<Interception>();
توسط متد RegisterType یک Type را با پیکربندی دلخواه به Unity معرفی می‌کنیم. در اینجا به ازای درخواست Foo (اولین پارامتر جنریک)، یک Foo (دومین پارامتر جنریک ) برگشت داده می‌شود. این متد تعدادی InjetctionMember (بصورت params) دریافت می‌کند که در این مثال سه InjetctionMember  به آن پاس داه شده است:
  • Interceptor : اطلاعاتی در مورد IInterceptor و نحوه‌ی Intercept یک شیء را نگه داری می‌کند. در اینجا از  VirtualMethodInterceptor برای تزریق کد استفاده شده.
  • InterceptionBehavior : این کلاس Behavior مورد نظر را به کلاس تزریق می‌کند.
  • AddintionalInterface  : کلاس target را مجبور به پیاده سازی اینترفیس دریافتی از پارامتر می‌کند.  اگر کلاس behavior، متد  GetRequiredInterfaces  اینترفیس INotifyPropertyChanged را برمی گرداند، نیازی نیست از AddintionalInterface در پارامتر متد فوق استفاده کنید. 

نکته :
کلاس VirtualMethodInterceptor فقط اعضای virtual را تحت تاثیر قرار می‌دهد.
اکنون نحوه‌ی ساخت یک نمونه از کلاس Foo به شکل زیر است:
var foo = container.Resolve<Foo>();
(foo as INotifyPropertyChanged).PropertyChanged += FooPropertyChanged;
private void FooPropertyChanged (object sender, PropertyChangedEventArgs e)
 {
      // Do some things.......
 }

نکته‌ی تکمیلی
طبق مستندات MSDN، کلاس VirtualMethodInterceptor  یک کلاس جدید مشتق شده از کلاس target (در اینجا Foo) می‌سازد. بنابراین اگر کلاس‌های شما دارای Data annotation و یا در کلاس‌های Mapper یک ORM استفاده شده‌اند (مانند کلاس‌های لایه Domain)، بجای  VirtualMethodInterceptor  از TransparentProxyInterceptor استفاده کنید.
سرعت اجرای VirtualMethodInterceptor سریعتر است ؛ اما به یاد داشته که برای استفاده از  TransparentProxyInterceptor  باید کلاس target از کلاس MarshalByRefObject ارث بری کند.
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 2
برای استفاده از کلاس‏های Entity که در نوشتار پیشین ایجاد کردیم در WCF باید آن کلاس‎ها را دست‎کاری کنیم. متن کلاس tblNews را در نظر بگیرید:
namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    
    public partial class tblNews
    {
        public int tblNewsId { get; set; }
        public int tblCategoryId { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public System.DateTime RegDate { get; set; }
        public Nullable<bool> IsDeleted { get; set; }
    
        public virtual tblCategory tblCategory { get; set; }
    }
}
 مشاهده می‌کنید که برای تعریف کلاس‌ها از کلمه کلیدی partial استفاده شده است.  استفاده از کلمه کلیدی partial به شما اجازه می‌دهد که یک کلاس را در چندین فایل جداگانه تعریف کنید. به عنوان مثال می‌توانید فیلدها، ویژگی ها و سازنده‌ها را در یک فایل و متدها را در فایل دیگر قرار دهید. 
به صورت خودکار کلیه‌ی ویژگی‌ها به توجه به پایگاه داده ساخته شده اند. برای نمونه ما برای فیلد IsDeleted در SQL Server ستون Allow Nulls را کلیک کرده بودیم که در نتیجه در اینجا عبارت Nullable پیش از نوع فیلد نشان داده شده است. برای استفاده از این کلاس در WCF باید صفت  DataContract را به کلاس داد. این قرارداد به ما اجازه استفاده از ویژگی‌هایی که صفت DataMember را می‌گیرند را می‌دهد.
کلاس بالا را به شکل زیر بازنویسی کنید:
using System.Runtime.Serialization;

namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    
    [DataContract]
    public partial class tblNews
    {
        [DataMember]
        public int tblNewsId { get; set; }
        [DataMember]
        public int tblCategoryId { get; set; }
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public string Description { get; set; }
        [DataMember]
        public System.DateTime RegDate { get; set; }
        [DataMember]
        public Nullable<bool> IsDeleted { get; set; }

        public virtual tblCategory tblCategory { get; set; }
    }
}
هم‌چنین کلاس tblCategory را به صورت زیر تغییر دهید:
namespace MyNewsWCFLibrary
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;

    [DataContract]
    public partial class tblCategory
    {
        public tblCategory()
        {
            this.tblNews = new HashSet<tblNews>();
        }

        [DataMember]
        public int tblCategoryId { get; set; }
        [DataMember]
        public string CatName { get; set; }
        [DataMember]
        public bool IsDeleted { get; set; }
    
        public virtual ICollection<tblNews> tblNews { get; set; }
    }
}
با انجام کد بالا از بابت مدل کارمان تمام شده است. ولی فرض کنید در اینجا تصمیم به تغییری در پایگاه داده می‌گیرید. برای نمونه می‌خواهید ویژگی Allow Nulls فیلد IsDeleted را نیز False کنیم و مقدار پیش‌گزیده به این فیلد بدهید. برای این کار باید دستور زیر را در SQL Server اجرا کنیم:
BEGIN TRANSACTION
GO
ALTER TABLE dbo.tblNews
DROP CONSTRAINT FK_tblNews_tblCategory
GO
ALTER TABLE dbo.tblCategory SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Tmp_tblNews
(
tblNewsId int NOT NULL IDENTITY (1, 1),
tblCategoryId int NOT NULL,
Title nvarchar(50) NOT NULL,
Description nvarchar(MAX) NOT NULL,
RegDate datetime NOT NULL,
IsDeleted bit NOT NULL
)  ON [PRIMARY]
 TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_tblNews SET (LOCK_ESCALATION = TABLE)
GO
ALTER TABLE dbo.Tmp_tblNews ADD CONSTRAINT
DF_tblNews_IsDeleted DEFAULT 0 FOR IsDeleted
GO
SET IDENTITY_INSERT dbo.Tmp_tblNews ON
GO
IF EXISTS(SELECT * FROM dbo.tblNews)
 EXEC('INSERT INTO dbo.Tmp_tblNews (tblNewsId, tblCategoryId, Title, Description, RegDate, IsDeleted)
SELECT tblNewsId, tblCategoryId, Title, Description, RegDate, IsDeleted FROM dbo.tblNews WITH (HOLDLOCK TABLOCKX)')
GO
SET IDENTITY_INSERT dbo.Tmp_tblNews OFF
GO
DROP TABLE dbo.tblNews
GO
EXECUTE sp_rename N'dbo.Tmp_tblNews', N'tblNews', 'OBJECT' 
GO
ALTER TABLE dbo.tblNews ADD CONSTRAINT
PK_tblNews PRIMARY KEY CLUSTERED 
(
tblNewsId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO
ALTER TABLE dbo.tblNews ADD CONSTRAINT
FK_tblNews_tblCategory FOREIGN KEY
(
tblCategoryId
) REFERENCES dbo.tblCategory
(
tblCategoryId
) ON UPDATE  NO ACTION 
 ON DELETE  NO ACTION 

GO
COMMIT
پس از آن مدل Entity Framework را باز کنید و در جایی از صفحه راست‌کلیک کرده و از منوی بازشده گزینه Update Model from Database را انتخاب کنید. سپس در پنجره بازشده، چون هیچ جدول، نما یا روالی به پایگاه داده‌ها نیفزوده ایم؛ دگمه‌ی Finish را کلیک کنید. دوباره کلاس tblNews را بازکنید. متوجه خواهید شد که همه‌ی DataContractها و DataMemberها را حذف شده است. ممکن است بگویید می‌توانستیم کلاس یا مدل را تغییر دهیم و به وسیله‌ی Generate Database from Model به‌هنگام کنیم. ولی در نظر بگیرید که نیاز به ایجاد چندین جدول دیگر داریم و مدلی با ده‌ها Entity دارید. در این صورت همه‌ی تغییراتی که در کلاس داده ایم زدوده خواهد شد. 
در بخش پسین، درباره‌ی این‌که چه کنیم که عبارت‌هایی که به کلاس‌ها می‌افزاییم حذف نشود؛ خواهم نوشت.
مطالب
نوشتن پرس و جو در Entity Framework‌ با استفاده از LINQ To Entity قسمت سوم
اجرای پرس و جو روی داده‌های به هم مرتبط (Related Data)
اگر به موجودیت Customer دقت کنید دارای خصوصیتی با نام Orders می‌باشد که از نوع <IList<Order هست یعنی دارای لیستی از Order هاست بنابراین یک رابطه یک به چند بین Customer و Order وجود دارد. در ادامه به بررسی نحوه پرس و جو کردن روی داده‌های به هم مرتبط خواهیم پرداخت.
ابتدا به کد زیر دقت کنید:
private static void Query10()
{
    using (var context = new StoreDbContext())
    {
        var customers = context.Customers;
        foreach (var customer in customers)
        {
            Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family);
            foreach (var order in customer.Orders)
            {
                Console.WriteLine("\t Order Date: {0}", order.Date);
            }
        }
    }
}
اگر کد بالا را اجرا کنید هنگام اجرای حلقه داخلی با خطای زیر مواجه خواهید شد:
System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first
همانطور که قبلا اشاره شد EF با اجرای یک پرس و جو به یکباره داده‌ها را باز نمی‌گرداند بنابراین در حلقه اصلی که روی Customers زده شده است با هر پیمایش یک customer از Database فراخوانی می‌شود درنتیجه DataReader تا پایان یافتن حلقه باز می‌ماند. حال آنکه حلقه داخلی نیز برای خواندن Order‌ها نیاز به اجرای یک پرس و جو دارد بنابراین DataReader ای جدید باز می‌شود و در نتیجه با خطایی مبنی بر اینکه DataReader دیگری باز است، مواجه می‌شویم. برای حل این مشکل می‌بایست جهت باز بودن چند DataReader همزمان، کد زیر را به ConnectionString اضافه کنیم
MultipleActiveResultSets = true
که با این تغییر کد بالا به درستی اجرا می‌شود. 
در بارگذاری داده‌های به هم مرتبط EF سه روش را در اختیار ما قرار می‌دهد:
  •  Lazy Loading
  • Eager Loading
  • Explicit Loading
که در ادامه به بررسی آنها خواهیم پرداخت.
Lazy Loading: در این روش داده‌های مرتبط در صورت نیاز با یک پرس وجوی جدید که به صورت اتوماتیک توسط EF ساخته می‌شود، گرفته خواهند شد. کد زیر را در نظر بگیرید:
private static void Query11()
{
    using (var context = new StoreDbContext())
    {
        var customer = context.Customers.First();

        Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family);
        foreach (var order in customer.Orders)
        {
            Console.WriteLine("\t Order Date: {0}", order.Date);
        }
    }
}
اگر این کد را اجرا کنید خواهید دید که یک بار پرس و جویی مبنی بر دریافت اولین Customer روی database زده خواهد شد و پس از چاپ آن در ادامه برای نمایش Order‌های این Customer پرس و جوی دیگری زده خواهد شد. در حقیقت پرس و جوی اول فقط Customer را بازگشت می‌دهد و در ادامه،  اول حلقه، جایی که نیاز به Order‌های این Customer می‌شود EF پرس و جو دوم را بصورت هوشمندانه و اتوماتیک اجرا می‌کند. به این روش بارگذاری داده‌های مرتبط Lazy Loading گفته می‌شود که به صورت پیش فرض در EF فعال است.
برای غیرفعال کردن این روش، کد زیر را اجرا کنید:
context.Configuration.LazyLoadingEnabled = false;
EF از dynamic proxy برای Lazy Loading استفاده می‌کند. به این صورت که در زمان اجرا کلاسی جدید که از کلاس POCO مان ارث برده است، ساخته می‌شود. این کلاس proxy می‌باشد و در آن navigation property‌ها بازنویسی شده‌اند و کمی منطق برای خواندن داده‌های وابسته اضافه شده است.
برای ایجاد dynamic proxy شروط زیر لازم است:
کلاس POCO می‌بایست public بوده و sealed نباشد.
Navigation property‌ها می‌بایست virtual باشد.
در صورتیکه هرکدام از این دو شرط برقرار نباشند کلاس proxy ساخته نمی‌شود و Lazy Loading حتی در صورت فعال بودن انجام نخواهد شد. مثلا اگر پراپرتی Orders در کلاس Customer مان virtual نباشد.  در شروع حلقه کد بالا پرس و جوی جدید اجرا نشده و در نتیجه مقدار این پراپرتی null خواهد ماند.
Lazy Loading به ما در عدم بارگذاری داده‌های مرتبط که به آنها نیازی نداریم، کمک می‌کند. اما در صورتیکه به داده‌های مرتبط نیاز داشته باشیم "مسئله Select n+1" پیش خواهد آمد که باید این مسئله را مد نظر داشته باشیم.
مسئله Select n+1: کد زیر را در نظر بگیرد
private static void Query12()
{
    using (var context = new StoreDbContext())
    {
        var customers = context.Customers;
        foreach (var customer in customers)
        {
            Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family);
            foreach (var order in customer.Orders)
            {
                Console.WriteLine("\t Order Date: {0}", order.Date);
            }
        }
    }
}
هنگام اجرای کد بالا یک پرس و جو برای خواندن Customer‌ها زده خواهد شد و به ازای هر Customer یک پرس و جوی دیگر برای گرفتن Order‌ها زده خواهد شد. در این صورت پرس و جوی اول ما اگر n مشتری را برگرداند، n پرس و جو نیز برای گرفتن Order‌ها زده خواهد شد که روهم n+1 دستور Select می‌شود. این تعداد پرس و جو موجب عدم کارایی می‌شود و برای رفع این مسئله نیاز به امکانی جهت بارگذاری هم زمان داده‌های مرتبط مورد نیاز خواهد بود. این امکان با استفاده از Eager Loading برآورده می‌شود.

روش Eager Loading: هنگامی که در یک پرس و جو نیاز به بارگذاری همزمان داده‌های مرتبط نیز باشد، از این روش استفاده می‌شود. برای این منظور از متد Include استفاده می‌شود که ورودی آن navigation property مربوطه می‌باشد. این پارامتر ورودی را همانطور که در کد زیر مشاهده می‌کنید، می‌توان به صورت string و یا Lambda Expression مشخص کرد.
دقت شود که برای حالت Lambda Expression بایدSystem.Data.Entity به using‌ها اضافه شود.
private static void Query13()
{
    using (var context = new StoreDbContext())
    {
        var customers = context.Customers.Include(c => c.Orders);
        //var customers = context.Customers.Include("Orders");
        foreach (var customer in customers)
        {
            Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family);
            foreach (var order in customer.Orders)
            {
                Console.WriteLine("\t Order Date: {0}", order.Date);
            }
        }
}
در این صورت یک پرس و جو به صورت join اجرا خواهد شد.
اگر داده‌های مرتبط در چند سطح باشند، می‌‌توان با دادن مسیر داده‌های مرتبط اقدام به بارگذاری آنها کرد. به مثالهای زیر توجه کنید:
context.OrderDetails.Include(o => o.Order.Customer)
در پرس و جوی بالا به ازای هر OrderDetail داده‌های مرتبط Order و Customer آن بارگذاری می‌شود.
context.Orders.Include(o => o.OrderDetail.Select(od => od.Product))
در پرس و جوی بالا به ازای هر Order لیست  OrderDetail ها و برای هر OrderDetail داده مرتبط Product آن بارگذاری می‌شود.
context.Orders.Include(o => o.Customer).Include(o => o.OrderDetail)
در پرس و جوی بالا به ازای هر Order داده‌های مرتبط  OrderDetail  و Customer آن بارگذاری می‌شود.

روش Explicit Loading: این روش مانند Lazy Loading می‌باشد که می‌توان داده‌های مرتبط را جداگانه فراخوانی کرد اما نه به صورت اتوماتیک توسط  EF بلکه به صورت صریح توسط خودمان انجام می‌شود. این روش حتی اگر navigation property‌های ما virtual نباشند نیز قابل انجام است. برای انجام این روش از متد DbContext.Entry استفاده می‌شود.
private static void Query14()
{
    using (var context = new StoreDbContext())
    {
        var customer = context.Customers.First(c => c.Family == "Jamshidi");

        context.Entry(customer).Collection(c => c.Orders).Load();

        foreach (var order in customer.Orders)
        {
            Console.WriteLine(order.Date);
        }
    }
}
در پرس و جوی بالا تمام Order‌های یک Customer به صورت جدا گرفته شده است برای این منظور از چون Orders یک لیست می‌باشد، از متد Collection استفاده شده است.
private static void Query15()
{
    using (var context = new StoreDbContext())
    {
        var order = context.Orders.First();

        context.Entry(order).Reference(o => o.Customer).Load();

        Console.WriteLine(order.Customer.FullName);
    }
}
در پرس و جوی بالا Customer یک Order صراحتا و به صورت جداگانه از database گرفته شده است.
با توجه به دو مثال بالا مشخص است که اگر داده مرتبط ما به صورت لیست است از Collection و درغیر این صورت از Reference استفاده می‌شود.
در صورتیکه بخواهیم ببینیم آیا داده‌ی مرتبط مان بازگذاری شده است یا خیر، از خصوصیت IsLoaded به صورت زیر استفاده می‌کنیم:
if (context.Entry(order).Reference(o => o.Customer).IsLoaded)
    context.Entry(order).Reference(o => o.Customer).Load();
و در آخر اگر بخواهیم روی داده‌های مرتبط پرس و جو اجرا کنیم نیز این قابلیت وجود دارد. برای این منظور از Query استفاده می‌کنیم.
private static void Query16()
{
    using (var context = new StoreDbContext())
    {
        var customer = context.Customers.First(c => c.Family == "Jamshidi");

        IQueryable<Order> query = context.Entry(customer).Collection(c => c.Orders).Query();

        var order = query.First();
    }
}

مطالب
امکان تعریف ساده‌تر کلاس‌های Immutable در C# 9.0 با معرفی نوع جدید record
در مطلب معرفی خواص init-only، با روش معرفی خواص immutable آشنا شدیم. نوع جدیدی که به C# 9.0 به نام record اضافه شده‌است، قسمتی از آن بر اساس همان خواص init-only کار می‌کند. به همین جهت مطالعه‌ی آن مطلب، پیش از ادامه‌ی بحث جاری، ضروری است.


چرا در C# 9.0 تا این اندازه بر روی سادگی ایجاد اشیاء Immutable تمرکز شده‌است؟

به شیءای Immutable گفته می‌شود که پس از وهله سازی ابتدایی آن، وضعیت آن دیگر قابل تغییر نباشد. همچنین به کلاسی Immutable گفته می‌شود که تمام وهله‌های ساخته شده‌ی از آن نیز Immutable باشند. نمونه‌ی یک چنین شیءای را از نگارش 1 دات نت در حال استفاده هستیم: رشته‌ها. رشته‌ها در دات نت غیرقابل تغییر هستند و هرگونه تغییری بر روی آن‌ها، سبب ایجاد یک رشته‌ی جدید (یک شیء جدید) می‌شود. نوع جدید record نیز به همین صورت عمل می‌کند.

مزایای وجود Immutability:

- اشیاء Immutable یا غیرقابل تغییر، thread-safe هستند که در نتیجه، برنامه نویسی همزمان و موازی را بسیار ساده می‌کنند؛ چون چندین thread می‌توانند با شیءای کار کنند که دسترسی به آن، تنها read-only است.
- اشیاء Immutable از اثرات جانبی، مانند تغییرات آن‌ها در متدهای مختلف در امان هستند. می‌توانید آن‌ها را به هر متدی ارسال کنید و مطمئن باشید که پس از پایان کار، این شیء تغییری نکرده‌است.
- کار با اشیاء Immutable، امکان بهینه سازی حافظه را میسر می‌کنند. برای مثال NET runtime.، هش رشته‌های تعریف شده‌ی در برنامه را در پشت صحنه نگهداری می‌کند تا مطمئن شود که تخصیص حافظه‌ی اضافی، برای رشته‌های تکراری صورت نمی‌گیرد. نمونه‌ی دیگر آن نمایش حرف "a" در یک ادیتور یا نمایشگر است. زمانیکه یک شیء Immutable حاوی اطلاعات حرف "a"، ایجاد شود، به سادگی می‌توان این تک وهله را جهت نمایش هزاران حرف "a" مورد استفاده‌ی مجدد قرار داد، بدون اینکه نگران مصرف حافظه‌ی بالای برنامه باشیم.
- کار با اشیاء Immutable به باگ‌های کمتری ختم می‌شود؛ چون همواره امکان تغییر حالت درونی یک شیء، توسط قسمت‌های مختلف برنامه، می‌تواند به باگ‌های ناخواسته‌ای منتهی شوند.
- Hash list‌ها که در جهت بهبود کارآیی برنامه‌ها بسیار مورد استفاده قرار می‌گیرند، بر اساس کلیدهایی Immutable قابل تشکیل هستند.


روش تعریف نوع‌های جدید record

کلاس ساده‌ی زیر را در نظر بگیرید:
public class User
{
   public string Name { set; get; }
}
برای تبدیل آن به یک نوع جدید record فقط کافی است واژه‌ی کلیدی class آن‌را با record جایگزین کنیم (به آن nominal record هم می‌گویند):
public record User
{
   public string Name { set; get; }
}
نحوه‌ی کار با آن و وهله سازی آن نیز دقیقا مانند کلاس‌ها است:
var user = new User();
user.Name = "User 1";
و ... در اینجا امکان انتساب مقداری به خاصیت Name وجود دارد؛ یعنی این خاصیت به صورت پیش‌فرض Immutable نیست.

روش تعریف دومی نیز در اینجا میسر است (به آن positional record هم می‌گویند):
public record User(string Name);
با این‌کار، به صورت خودکار یک record جدید تشکیل می‌شود که به همراه خاصیت Name است؛ چیزی شبیه به record قبلی که تعریف کردیم (به همین جهت نیاز است نام آن‌را شروع شده‌ی با حروف بزرگ درنظر بگیریم). با این تفاوت که این record، اینبار دارای سازنده است و همچنین خاصیت Name آن از نوع init-only است. در این حالت است که کل record به صورت immutable معرفی می‌شود؛ وگرنه روش تعریف یک خاصیت معمولی که از نوع init-only نیست (مانند مثال اول)، سبب بروز Immutability نخواهد شد.

برای کار با رکورد دومی که تعریف کردیم باید سازند‌ه‌ی این record را مقدار دهی کرد:
var user = new User("User 1");
// Error: Init-only property or indexer 'User.Name' can only be assigned
// in an object initializer, or on 'this' or 'base' in an instance constructor
// or an 'init' accessor. [CS9Features]csharp(CS8852)
user.Name = "User 1";
و همانطور که ملاحظه می‌کنید، چون خاصیت Name از نوع init-only است و در سازنده‌ی record تعریف شده مقدار دهی شده‌است، دیگر نمی‌توان آن‌را مقدار دهی مجدد کرد. همچنین در اینجا امکان استفاده‌ی از object initializers مانند new User { Name = "User 1" } نیز وجود ندارد؛ چون به همراه یک سازنده‌ی به صورت خودکار تولید شده‌است که خاصیتی init-only را مقدار دهی کرده‌است.


نوع جدید record چه اطلاعاتی را به صورت خودکار تولید می‌کند؟

روش دوم تعریف recordها اگر در نظر بگیریم:
public record User(string Name);
و در این حالت برنامه را کامپایل کنیم، به کدهای زیر که حاصل از دی‌کامپایل است، می‌رسیم:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using CS9Features;

public class User : IEquatable<User>
{
 protected virtual Type EqualityContract
 {
  [System.Runtime.CompilerServices.NullableContext(1)]
  [CompilerGenerated]
  get
  {
   return typeof(User);
  }
 }

 public string Name
 {
  get;
  set/*init*/;
 }

 public User(string Name)
 {
  this.Name = Name;
  base..ctor();
 }

 public override string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("User");
  stringBuilder.Append(" { ");
  if (PrintMembers(stringBuilder))
  {
   stringBuilder.Append(" ");
  }
  stringBuilder.Append("}");
  return stringBuilder.ToString();
 }

 protected virtual bool PrintMembers(StringBuilder builder)
 {
  builder.Append("Name");
  builder.Append(" = ");
  builder.Append((object?)Name);
  return true;
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator !=(User? r1, User? r2)
 {
  return !(r1 == r2);
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator ==(User? r1, User? r2)
 {
  return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
 }

 public override int GetHashCode()
 {
  return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
 }

 public override bool Equals(object? obj)
 {
  return Equals(obj as User);
 }

 public virtual bool Equals(User? other)
 {
  return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name);
 }

 public virtual User <Clone>$()
 {
  return new User(this);
 }

 protected User(User original)
 {
  Name = original.Name;
 }

 public void Deconstruct(out string Name)
 {
  Name = this.Name;
 }
}
این خروجی به صورت خودکار تولید شده‌ی توسط کامپایلر، چنین نکاتی را به همراه دارد:
- record‌ها هنوز هم در اصل همان class‌های استاندارد #C هستند (یعنی در اصل reference type هستند).
- این کلاس به همراه یک سازنده و یک خاصیت init-only است (بر اساس تعاریف ما).
- متد ToString آن بازنویسی شده‌است تا اگر آن‌را بر روی شیء حاصل، فراخوانی کردیم، به صورت خودکار نمایش زیبایی را از محتوای آن ارائه دهد.
- این کلاس از نوع  <IEquatable<User است که امکان مقایسه‌ی اشیاء record را به سادگی میسر می‌کند. برای این منظور متدهای GetHashCode و Equals آن به صورت خودکار بازنویسی و تکمیل شده‌اند (یعنی مقایسه‌ی آن شبیه به value-type است).
- این کلاس امکان clone کردن اطلاعات جاری را مهیا می‌کند.
- همچنین به همراه یک متد Deconstruct هم هست که جهت انتساب خواص تعریف شده‌ی در آن، به یک tuple مفید است.

بنابراین یک رکورد به همراه قابلیت‌هایی است که سال‌ها در زبان #C وجود داشته‌اند و شاید ما به سادگی حاضر به تشکیل و تکمیل آن‌ها نمی‌شدیم؛ اما اکنون کامپایلر زحمت کدنویسی خودکار آن‌ها را متقبل می‌شود!


ساخت یک وهله‌ی جدید از یک record با clone کردن آن

اگر به کدهای حاصل از دی‌کامپایل فوق دقت کنید، یک قسمت جدید clone هم با syntax خاصی در آن ظاهر شده‌است:
public virtual User <Clone>$()
{
  return new User(this);
}
زمانیکه یک شیء Immutable است، دیگر نمی‌توان مقادیر خواص آن‌را در ادامه تغییر داد. اما اگر نیاز به اینکار وجود داشت، باید چکار کنیم؟ در C# 9.0 برای ایجاد وهله‌ی جدید معادلی از یک record، واژه‌ی کلیدی جدیدی را به نام with، اضافه کرده‌اند. برای نمونه اگر record زیر را در نظر بگیریم که دارای دو خاصیت نام و سن است:
public record User(string Name, int Age);
وهله سازی متداول آن به صورت زیر خواهد بود:
var user1 = new User("User 1", 21);
اما اگر خواستیم خاصیت سن آن‌را تغییر دهیم، می‌توان با استفاده از واژه‌ی کلیدی with، به صورت زیر عمل کرد:
var user2 = user1 with { Age = 31 };
کاری که در اصل در اینجا انجام می‌شود، ابتدا clone کردن شیء user1 است (یعنی دقیقا یک وهله‌ی جدید از user1 را با تمام اطلاعات قبلی آن در اختیار ما قرار می‌دهد که این وهله، ارجاعی را به شیء قبلی ندارد و از آن منقطع است). بنابراین نام user2، دقیقا همان "User 1" است که پیشتر تنظیم کردیم؛ با این تفاوت که اینبار مقدار سن آن متفاوت است. با استفاده از cloning، هنوز شیء user1 که immutable است، دست نخورده باقی مانده‌است و توسط with می‌توان خواص آن‌را تغییر داد و حاصل کار، یک شیء کاملا جدید است که مکان آن در حافظه، با مکان شیء user1 در حافظه، یکی نیست.


مقایسه‌ی نوع‌های record

در کدهای حاصل از دی‌کامپایل فوق، قسمت عمده‌ای از آن به تکمیل اینترفیس <IEquatable<User پرداخته شده بود. به همین جهت اکنون دو رکورد با مقادیر خواص یکسانی را ایجاد می‌کنیم:
var user1 = new User("User 1", 21);
var user2 = new User("User 1", 21);
سپس یکبار آن‌ها را از طریق عملگر == و بار دیگر به کمک متد Equals، مقایسه می‌کنیم:
Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
خروجی هر دو حالت، True است:
user1.Equals(user2) -> True
user1 == user2 -> True
این مورد، یکی از مهم‌ترین تفاوت‌های recordها با classها هستند.
- زمانیکه عملگر == را بر روی شیء user1 و user2 اعمال می‌کنیم، اگر User، از نوع کلاس معمولی باشد، حاصل آن false خواهد بود؛ چون این دو، به یک مکان از حافظه اشاره نمی‌کنند، حتی با اینکه مقادیر خواص هر دو شیء یکی است.
- اما اگر به قطعه کد دی‌کامپایل شده دقت کنید، در یک رکورد که هر چند در اصل یک کلاس است، حتی عملگر == نیز بازنویسی شده‌است تا در پشت صحنه همان متد Equals را فراخوانی کند و این متد با توجه به پیاده سازی اینترفیس <IEquatable<User، اینبار دقیقا مقادیر خواص رکورد را یک به یک مقایسه کرده و نتیجه‌ی حاصل را باز می‌گرداند:
public virtual bool Equals(User? other)
{
   return (object)other != null &&
 EqualityContract == other!.EqualityContract &&
 EqualityComparer<string>.Default.Equals(Name, other!.Name) && 
EqualityComparer<int>.Default.Equals(Age, other!.Age);
}
این متدی است که به صورت خودکار توسط کامپایلر جهت مقایسه‌ی مقادیر خواص رکورد جدید تعریف شده، تشکیل شده‌است. به عبارتی recordها از لحاظ مقایسه، شبیه به value objects عمل می‌کنند؛ هرچند در اصل یک کلاس هستند.

یک نکته: بازنویسی عملگر == در SDK نگارش rc2 فعلی رخ‌داده‌است و در نگارش‌های قبلی preview، اینگونه نبود.


امکان ارث‌بری در recordها

دو رکورد زیر را در نظر بگیرید که اولی به همراه Name است و نمونه‌ی مشتق شده‌ی از آن، خاصیت init-only سن را نیز به همراه دارد:
    public record User
    {
        public string Name { get; init; }

        public User(string name)
        {
            Name = name;
        }
    }

    public record UserWithAge : User
    {
        public int Age { get; init; }

        public UserWithAge(string name, int age) : base(name)
        {
            Age = age;
        }
    }
در اینجا روش دیگر تعریف recordها را ملاحظه می‌کنید که شبیه به کلاس‌ها است و خواص آن init-only هستند. در این حالت اگر مقایسه‌ی زیر را انجام دهیم:
var user1 = new User("User 1");
var user2 = new UserWithAge("User 1", 21);

Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
به خروجی زیر خواهیم رسید:
user1.Equals(user2) -> False
user1 == user2 -> False
علت آن را هم پیشتر بررسی کردیم. تساوی رکوردها بر اساس مقایسه‌ی مقدار تک تک خواص آن‌ها صورت می‌گیرد و چون user1 به همراه سن نیست، مقایسه‌ی این دو، false را بر می‌گرداند.

امکان تعریف ارث‌بری رکوردها به صورت زیر نیز وجود دارد و الزاما نیازی به روش تعریف کلاس مانند آن‌ها، مانند مثال فوق نیست:
public abstract record Food(int Calories);
public record Milk(int C, double FatPercentage) : Food(C);


رکوردها متد ToString را بازنویسی می‌کنند

در مثال قبلی اگر یک ToString را بر روی اشیاء تشکیل شده فراخوانی کنیم:
Console.WriteLine(user1.ToString());
Console.WriteLine(user2.ToString());
به این خروجی‌ها می‌رسیم:
User { Name = User 1 }
UserWithAge { Name = User 1, Age = 21 }
که حاصل بازنویسی خودکار متد ToString در پشت صحنه است.


امکان استفاده‌ی از Deconstruct در رکوردها

دو روش برای تعریف رکوردها وجود دارند؛ یکی شبیه به تعریف کلاس‌ها است و دیگری تعریف یک سطری، که positional record نیز نامیده می‌شود:
public record Person(string Name, int Age);
 فقط در حالت تعریف یک سطری positional record فوق است که خروجی خودکار نهایی تولیدی، به همراه public void Deconstruct نیز خواهد بود:
public void Deconstruct(out string Name, out int Age)
{
  Name = this.Name;
  Age = this.Age;
}
در این حالت می‌توان از tuples نیز برای کار با آن استفاده کرد:
var (name, age) = new Person("User 1", 21);
واژه‌ی «positional» نیز دقیقا به همین قابلیت اشاره می‌کند که بر اساس موقعیت خواص تعریف شده‌ی در رکورد، امکان Deconstruct آن‌ها به متغیرهای یک tuple وجود دارد. حالت تعریف کلاس مانند رکوردها، nominal نام دارد.


امکان استفاده‌ی از نوع‌های record در ASP.NET Core 5x

سیستم model binding در ASP.NET Core 5x، از نوع‌های record نیز پشتیبانی می‌کند؛ یک مثال:
 public record Person([Required] string Name, [Range(0, 150)] int Age);

 public class PersonController
 {
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
    // ...
   }
 }


پرسش و پاسخ

آیا نوع‌های record به صورت value type معرفی می‌شوند؟
پاسخ: خیر. رکوردها در اصل reference type هستند؛ اما از لحاظ مقایسه، شبیه به value types عمل می‌کنند.

آیا می‌توان در یک کلاس، خاصیتی از نوع رکورد را تعریف کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان در رکوردها، از struct و یا کلاس‌ها جهت تعریف خواص استفاده کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان از واژه‌ی کلیدی with با کلاس‌ها و یا structها استفاده کرد؟
پاسخ: خیر. این واژه‌ی کلیدی در C# 9.0 مختص به رکوردها است.

آیا رکوردها به صورت پیش‌فرض Immutable هستند؟
پاسخ: اگر آن‌ها را به صورت positional records تعریف کنید، بله. چون در این حالت خواص تشکیل شده‌ی توسط آن‌ها از نوع init-only هستند. در غیراینصورت، می‌توان خواص غیر init-only را نیز به تعریف رکوردها اضافه کرد.