مطالب دوره‌ها
بررسی کارآیی و ایندکس گذاری بر روی اسناد XML در SQL Server - قسمت دوم
تا اینجا ملاحظه کردید که XQuery ایندکس نشده چگونه بر روی Query Plan تاثیر دارد. در ادامه، مباحث ایندکس گذاری بر روی اسناد XML ایی را مرور خواهیم کرد.


ایندکس‌های XML ایی

ایندکس‌های XML ایی، ایندکس‌های خاصی هستند که بر روی ستون‌هایی از نوع XML تعریف می‌شوند. هدف از تعریف آن‌ها، بهینه سازی اعمال مبتنی بر XQuery، بر روی داده‌های این نوع ستون‌ها است. چهار نوع XML Index قابل تعریف هستند؛ اما primary xml index باید ابتدا ایجاد شود. در این حالت جدولی که دارای ستون XML ایی است نیز باید دارای یک clustered index باشد. هدف از primary XML indexها، ارائه‌ی تخمین‌های بهتری است به بهینه ساز کوئری‌ها در SQL Server.


جزئیات primary XML indexها

زمانیکه یک primary xml index را ایجاد می‌کنیم، node table یاد شده در قسمت قبل را، بر روی سخت دیسک ذخیره خواهیم کرد (بجای هربار محاسبه در زمان اجرا). متادیتای این اطلاعات ذخیره شده را در جداول سیستمی sys.indexes و sys.columns می‌توان مشاهده کرد. باید دقت داشت که تهیه‌ی این ایندکس‌ها، فضای قابل توجهی را از سخت دیسک به خود اختصاص خواهند داد؛ چیزی حدود 2 تا 5 برابر حجم اطلاعات اولیه. بدیهی است تهیه‌ی این ایندکس‌ها که نتیجه‌ی تجزیه‌ی اطلاعات XML ایی است، بر روی سرعت insert تاثیر خواهند گذاشت. Node table دارای ستون‌هایی مانند نام تگ، آدرس تگ، نوع داده آن، مسیر و امثال آن است.
زمانیکه یک Primary XML Index تعریف می‌شود، اگر به Query Plan حاصل دقت کنید، دیگر خبری از XML Readerها مانند قبل نخواهد بود. در اینجا Clustered index seek قابل مشاهده‌است.


ایجاد primary XML indexها

همان مثال قسمت قبل را که دو جدول از آن به نام‌های xmlInvoice و xmlInvoice2 ایجاد کردیم، درنظر بگیرید. اینبار یک xmlInvoice3 را با همان ساختار و همان 6 رکوردی که معرفی شدند، ایجاد می‌کنیم. بنابراین برای آزمایش جاری، در مثال قبل، هرجایی xmlInvoice مشاهده می‌کنید، آن‌را به xmlInvoice3 تغییر داده و مجددا جدول مربوطه و داده‌های آن‌را ایجاد کنید.
اکنون برای ایجاد primary XML index بر روی ستون invoice آن می‌توان نوشت:
 CREATE PRIMARY XML INDEX invoice_idx ON xmlInvoice3(invoice)
 SELECT * FROM sys.internal_tables
کوئری دومی که بر روی sys.internal_tables انجام شده، محل ذخیره سازی این ایندکس را نمایش می‌دهد که دارای نامی مانند xml_index_nodes_325576198_256000 خواهد بود. دو عدد پس از آن table object id و column object id هستند.
در ادامه علاقمند هستیم که بدانیم داخل آن چه چیزی ذخیره شده‌است:
 SELECT * FROM sys.xml_index_nodes_325576198_256000
اگر این کوئری را اجرا کنید احتمالا به خطای Invalid object name برخواهید خورد. علت اینجا است که برای مشاهده‌ی اطلاعات جداول داخلی مانند این، نیاز است حین اتصال به SQL Server، در قسمت server name نوشت admin:(local) و حالت authentication نیز باید بر روی Windows authentication باشد. به آن اصطلاحا Dedicated administrator connection نیز می‌گویند. برای این منظور حتما نیاز است از طریق منوی File -> New -> Database Engine Query شروع کنید در غیراینصورت پیام Dedicated administrator connections are not supported را دریافت خواهید کرد.
اگر به این جدول دقت کنید، 6 ردیف اطلاعات XML ایی، به حدود 100 ردیف اطلاعات ایندکس شده، تبدیل گردیده‌است. با استفاده از دستور ذیل می‌توان حجم ایندکس تهیه شده را نیز مشاهده کرد:
 sp_spaceused 'xmlInvoice3'
در صورت نیاز برای حذف ایندکس ایجاد شده می‌توان به نحو ذیل عمل کرد:
 --DROP INDEX invoice_idx ON xmlInvoice3


تاثیر primary XML indexها بر روی سرعت اجرای کوئری‌ها

همان 10 کوئری قسمت قبل را درنظر بگیرید. اینبار برای مقایسه می‌توان به نحو ذیل عمل کرد:
 SELECT * FROM xmlInvoice
WHERE invoice.exist('/Invoice[@InvoiceId = "1003"]') = 1

SELECT * FROM xmlInvoice3
WHERE invoice.exist('/Invoice[@InvoiceId = "1003"]') = 1
دو کوئری یکی هستند اما اولی بر روی xmlInvoice اجرا می‌شود و دومی بر روی xmlInvoice3. هر دو کوئری را انتخاب کرده و با استفاده از منوی Query، گزینه‌ی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمه‌های Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده نمود.


چند نکته در این تصویر حائز اهمیت است:
- Query plan کوئری انجام شده بر روی جدول دارای primary XML index، مانند قسمت قبل، حاوی XML Readerها نیست.
- هزینه‌ی انجام کوئری بر روی جدول دارای XML ایندکس نسبت به حالت بدون ایندکس، تقریبا نزدیک به صفر است. (بهبود کارآیی فوق العاده)
اگر کوئری‌های دیگر را نیز با هم مقایسه کنید، تقریبا به نتیجه‌ی کمتر از یک سوم تا یک چهارم حالت بدون ایندکس خواهید رسید.
همچنین اگر برای حالت دارای Schema collection نیز ایندکس ایجاد کنید، اینبار کوئری پلن آن اندکی (چند درصد) بهبود خواهد یافت ولی نه آنچنان.


ایندکس‌های XML‌ایی ثانویه یا secondary XML indexes

سه نوع ایندکس XML ایی ثانویه نیز قابل تعریف هستند:
- VALUE : کار آن بهینه سازی کوئری‌های content و wildcard است.
- PATH : بهینه سازی انتخاب‌های مبتنی بر XPath را انجام می‌دهد.
- Property: برای بهینه سازی انتخاب خواص و ویژگی‌ها بکار می‌رود.

این ایندکس‌ها یک سری non-clustered indexes بر روی node tables هستند. برای ایجاد سه نوع ایندکس یاد شده به نحو ذیل می‌توان عمل کرد:
 CREATE XML INDEX invoice_path_idx ON xmlInvoice3(invoice)
 USING XML INDEX invoice_idx FOR PATH
در اینجا یک path index جدید ایجاد شده‌است. ایندکس‌های ثانویه نیاز به ذکر ایندکس اولیه نیز دارند.
پس از ایجاد ایندکس ثانویه بر روی مسیرها، اگر اینبار کوئری دوم را اجرا کنیم، به Query Plan ذیل خواهیم رسید:


همانطور که مشاهده می‌کنید، نسبت به حالت primary index، وضعیت clustered index seek به index seek تغییر کرده‌است و همچنین دقیقا مشخص است که از کدام ایندکس استفاده شده‌است.
در ادامه دو نوع ایندکس دیگر را نیز ایجاد می‌کنیم:
 CREATE XML INDEX invoice_value_idx ON xmlInvoice3(invoice)
 USING XML INDEX invoice_idx FOR VALUE
 
 CREATE XML INDEX invoice_prop_idx ON xmlInvoice3(invoice)
 USING XML INDEX invoice_idx FOR PROPERTY
 

سؤال: اکنون پس از تعریف 4 ایندکس یاد شده، کوئری دوم از کدام ایندکس استفاده خواهد کرد؟

در اینجا مجددا کوئری دوم را اجرا کرده و به قسمت Query Plan آن دقت خواهیم کرد:


برای مشاهده دقیق نام ایندکس مورد استفاده، کرسر ماوس را بر روی index seek قرار می‌دهیم. در اینجا اگر به قسمت object گزارش ارائه شده دقت کنیم، نام invoice_value_idx یا همان value index ایجاد شده، قابل مشاهده‌است؛ به این معنا که در کوئری دوم، اهمیت مقادیر بیشتر است از اهمیت مسیرها.

کوئری‌هایی مانند کوئری ذیل از property index استفاده می‌کنند:
 SELECT * FROM xmlInvoice3
WHERE invoice.exist('/Invoice//CustomerName[text() = "Vahid"]') = 1
در اینجا با بکارگیری // به دنبال CustomerName در تمام قسمت‌های سند Invoice خواهیم گشت. البته کوئری پلن آن نسبتا پیچیده‌است و شامل primary index اسکن و clusterd index اسکن نیز می‌شود. برای بهبود قابل ملاحظه‌ی آن می‌توان به نحو ذیل از عملگر self استفاده کرد:
 SELECT * FROM xmlInvoice3
WHERE invoice.exist('/Invoice//CustomerName[. = "Vahid"]') = 1


خلاصه نکات بهبود کارآیی برنامه‌های مبتنی بر فیلدهای XML

- در حین استفاده از XPath، ذکر  محور parent یا استفاده از .. (دو دات)، سبب ایجاد مراحل اضافه‌ای در Query Plan می‌شوند. تا حد امکان از آن اجتناب کنید و یا از روش‌هایی مانند cross apply و xml.nodes برای مدیریت اینگونه موارد تو در تو استفاده نمائید.
- ordinals را به انتهای Path منتقل کنید (مانند ذکر [1] جهت مشخص سازی نودی خاص).
- از ذکر predicates در وسط یک Path اجتناب کنید.
- اگر اسناد شما fragment با چند root elements نیستند، بهتر است document بودن آ‌ن‌ها را در حین ایجاد ستون XML مشخص کنید.
- xml.value را به xml.query ترجیح دهید.
- عملیات casting در XQuery سنگین بوده و استفاده از ایندکس‌ها را غیرممکن می‌کند. در اینجا استفاده از اسکیما می‌تواند مفید باشد.
- نوشتن sub queryها بهتر هستند از چندین XQuery در یک عبارت SQL.
- در ترکیب اطلاعات رابطه‌ای و XML، استفاده از متدهای xml.exist و sql:column نسبت به xml.value جهت استخراج و مقایسه اطلاعات، بهتر هستند.
- اگر قصد تهیه خروجی XML از جدولی رابطه‌ای را دارید، روش select for xml کارآیی بهتری را نسبت به روش FLOWR دارد. روش FLOWR برای کار با اسناد XML موجود طراحی و بهینه شده‌است؛ اما روش select for xml در اصل برای کار با اطلاعات رابطه‌ای بهینه سازی گردیده‌است.
نظرات مطالب
استفاده از reCAPTCHA در ASP.NET
کد شما قسمت else نداره یا حداقل اینجا نوشته نشده. بنابراین حتی اگر صفحه معتبر هم باشه، قسمت نمایش خطا اجرا میشه.
نظرات مطالب
فعال سازی عملیات CRUD در Kendo UI Grid
چگونه میتوان بعد از اعمال تمام تغییرات ویرایش شده ، حذف شده و یا ایجاد شده ، فقط یک پیغام به کاربر نمایش داد که عملیات به پایان رسیده است؟ تشکر.
نظرات مطالب
Vue.js - بحث Event Handling - آشنایی و شیوۀ استفاده از رویدادها - قسمت پنجم
یک نکته‌ی تکمیلی: نحوه‌ی انتخاب یک المنت در Vue.js

در Vue.js  چندین روش برای انتخاب یک المنت وجود دارند که در ادامه آن‌ها را مورد بررسی قرار میدهیم.
 کد زیر را در نظر بگیرید، ما قصد داریم value موجود در input  را نمایش دهیم.

روش اول: با استفاده از جاوا اسکریپت
<!DOCTYPE html>

<head>
    <meta charset="UTF-8">
    <title>Vue.js گرفتن یک المنت در </title>
</head>

<body>
    <div id="main">
        <input type="text" name="fullName" id="fullName">
        <button @click='getFullNameValue()'>Show Me</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        new Vue({
            el: '#main',
            data() {
                return {

                }
            },
            methods: {
                getFullNameValue() {
                    let fullNameValue = document.querySelector('#fullName').value;
                    alert(fullNameValue);
                }
            }
        });
    </script>
</body>

</html>
با استفاده از دستور زیر در جاوا اسکریپت براحتی میتوانیم یک المنت را انتخاب کنیم و آن را به یک متغیر انتساب دهیم.
 let fullNameValue = document.querySelector('#fullName')

روش دوم: انتخاب یک المنت با جی کوئری (با سلکتور دلخواه)
getFullNameValue() {
                    //let fullNameValue = document.querySelector('#fullName').value;
                    let fullNameValue = $('#fullName');
                    alert(fullNameValue.val());
}

روش سوم: انتخاب یک المنت در حیطه نمونه سازی شده  Vue.js
getFullNameValue() {
                    //let fullNameValue = document.querySelector('#fullName').value;
                    //let fullNameValue = $('#fullName').val();
                    let fullNameValue = this.$el.querySelector('#fullName');
                    alert(fullNameValue.value );
}

روش چهارم: انتخاب یک المنت از طریق امکان ref  در  Vue.js
<!DOCTYPE html>

<head>
    <meta charset="UTF-8">
    <title>Vue.js گرفتن یک المنت در </title>
</head>

<body>
    <div id="main">
        <input type="text" name="fullName" id="fullName" ref="refFullName">
        <button @click='getFullNameValue()'>Show Me</button>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        new Vue({
            el: '#main',
            data() {
                return {

                }
            },
            methods: {
                getFullNameValue() {
                    //let fullNameValue = document.querySelector('#fullName').value;
                    //let fullNameValue = $('#fullName').val();
                    //let fullNameValue = this.$el.querySelector('#fullName').value;
                    let fullNameValue = this.$refs.refFullName;
                    alert(fullNameValue.value);
                }
            }
        });
    </script>
</body>

</html>

نتیجه گیری:
از لحاظ performance  روش سوم و چهارم بهینه می‌باشند؛ زیرا فقط در حیطه نمونه سازی شده  Vue.js  مربوطه جستجوی المنت را انجام میدهند. درحالیکه در روش اول و دوم کل DOM  مورد بررسی قرار میگیرد. روش چهارم بخاطر سهولت نوشتاری بهتر از سایر موارد می‌باشد.
چند نکته در مورد کد فوق:
A) در ES6 نیازی به نوشتن کلمه کلیدی function برای تعریف یک متد نیست.
 getFullNameValue: function() {
                  ...
                }
و میتوان بصورتی که در مثال کد استفاده شده، آن را خلاصه نمود.
B)  در ورژن جدید  Vue.js  بجای استفاده از دایرکتیو  v-on  ( کد زیر ) برای تعریف یک رویداد میتوان از  @ استفاه نمود. در مثال مورد استفاده‌ی مقاله از @ استفاده شده است.
  <button v-on:click='getFullNameValue()'>Show Me</button>

مطالب دوره‌ها
ارتباطات بلادرنگ و SignalR
زمانیکه صحبت از برنامه‌های بلادرنگ می‌شود با کاربرانی سر و کار داریم که نیاز دارند تا اطلاعات مورد نیاز خود را همواره و بلافاصله در آخرین وضعیت به روز آن مشاهده کنند. در این بین، کلاینت می‌خواهد یک برنامه وب باشد یا سیلورلایت و یا یک برنامه نوشته شده با WPF. حتی برنامه‌های موبایل را نیز باید به این لیست اضافه کرد.
در اینجا کلمه بلادرنگ به معنای ارسال اطلاعات از طرف سرور به کلاینت‌ها با فاصله زمانی بسیار کوتاهی از به روز رسانی اطلاعات صورت گرفته در سمت سرور است.
نمونه‌ای از این برنامه‌ها شامل موارد ذیل هستند:
- اطلاع رسانی همزمان به گروهی از کاربران
- جستجوهای زنده و به روز رسانی‌هایی از این دست
- نمایش بلادرنگ قیمت‌ها و وضعیت تجاری محصولات و سهام‌ها
- بازی‌های تعاملی
- برنامه‌های گروهی و تعاملی (مانند برنامه‌های Chat)
- برنامه‌های شبکه‌های اجتماعی (برای مثال پیام جدیدی دارید؛ شخص خاصی آنلاین یا آفلاین شد و امثال آن)

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

معرفی الگوی Push service

البته باید دقت داشت که الگوی push service یک الگوی رسمی ذکر شده در گروه‌های مرسوم الگوهای طراحی نیست، اما مفهوم آن سرویسی است که چندین کار ذیل را انجام می‌دهد:
الف) پذیرش اتصالات از چندین مصرف کننده. مصرف کننده‌ها در اینجا الزاما محدود به کلاینت‌های وب یا دسکتاپ نیستند؛ می‌توانند حتی یک سرور یا سرویس دیگر نیز باشند.
ب) قادر است اطلاعات را به مصرف کننده‌های خود ارسال کند. این سرویس می‌تواند یک برنامه ASP.NET باشد یا حتی یک سرویس متداول ویندوز.
ج) در اینجا چندین منبع خارجی مانند یک بانک اطلاعاتی یا تغییرات رخ داده توسط یک سخت افزار که می‌توانند سبب بروز رخدادهایی در push service گردند نیز می‌تواند وجود داشته باشند. هر زمان که تغییری در این منابع خارجی رخ دهد، مایل هستیم تا مصرف کننده‌ها را مطلع سازیم.


پروتکل HTTP و ارتباطات بلادرنگ

پروتکلی که در ارتباطات بلادرنگ مبتنی بر SignalR مورد استفاده قرار می‌گیرد، HTTP است و از قابلیت‌های Request و Response آن در اینجا بیشترین بهره برده می‌شود. پیاده سازی Push عموما بر مبنای یکی از روش‌های متداول زیر است:
1) Periodic polling
به این معنا که مثلا هر 10 ثانیه یکبار، کاری را انجام بده؛ مانند ارسال متناوب: آیا تغییری رخ داده؟ آیا تغییری رخ داده؟ و .... به همین ترتیب. این روش اصلا بهینه نبوده و منابع زیادی را خصوصا در سمت سرور مصرف خواهد کرد. برای مثال:
function getInfo() {
         $.ajax("url", function ( newInfo){
                  if ( newInfo != null) {
                      // do something with the data
                  }
         });
    // poll again after 20 seconds
    setTimeout(getInfo,20000);
}
// start the polling loop
getInfo();

2) Long polling
به آن HTTP Streaming یا Comet هم گفته می‌شود. این روش نسبتا هوشمند بوده و کلاینت اتصالی را به سرور برقرار خواهد کرد. سرور در این حالت تا زمانیکه اطلاعاتی را در دسترس نداشته باشد، پاسخی نخواهد داد. برای نمونه:
function getNewInfo(){
  $.ajax("url", function (newinfo) {
      // do something with the data
  // start the new request
      getNewINfo();
  });
}
// start the polling loop
getNewInfo();

این روش نسبت به حالت Periodic polling بهینه‌تر است اما نیاز به اتصالات زیادی داشته و همچنین تردهای بسیاری را در سمت سرور به خود مشغول خواهد کرد.

3) Forever frame
فقط در IE پشتیبانی می‌شود. در این روش یک Iframe مخفی توسط مرورگر تشکیل شده و از طریق آن درخواستی به سرور ارسال می‌شود. سپس سرور متناوبا با تزریق اسکریپت‌هایی به این Iframe سبب فراخوانی مجدد وضعیت خود می‌گردد. در این روش نیز به ازای هر درخواست و پاسخ، ارتباطات گشوده و بسته خواهند شد.

4) Server Sent Events یا SSE
این مورد جزو استاندارد HTML5 است. در اینجا اتصالی برقرار شده و داده‌ها از طریق اتصالات HTTP منتقل می‌شوند.
var eventSrc = new EventSource("url");
    // register event handler for the message
    eventSrc.addEventListener( "message",function (evt) {
    //process the data
});
این روش نیز بسیار شبیه به حالت long polling است. سرور تا زمانیکه اطلاعاتی را برای پاسخ دهی فراهم نداشته باشد، اتصال را باز نگه می‌دارد. به این ترتیب از لحاظ مقیاس پذیری گزینه بهتری است (نسبت به حالتیکه مدام اتصال برقرار و قطع می‌شود). اکثر مرورگرها منهای نگارش‌های قدیمی IE از این روش پشتیبانی می‌کنند.
تنها تفاوت آن با حالت long polling در این است که پس از ارائه پاسخ به کلاینت، اتصال را قطع نمی‌کند. Long polling نیز اتصال را باز نگه می‌دارد، اما این اتصال را بلافاصله پس از ارائه پاسخ، می‌بندد.

5) Web sockets
Web sockets در سکوی کاری ویندوز، تنها در ویندوز‌های 8، ویندوز سرور 2012 و دات نت 4 و نیم پشتیبانی می‌شود. هرچند این روش در حال حاضر به عنوان بهترین روش Push مطرح است اما به دلیل محدودیتی که یاد شد، مدتی طول خواهد کشید تا استفاده گسترده‌ای پیدا کند.
var socket = new WebSocket("url");
socket.onmessage = function (msg) {
var newInfo = msg.data;
// do something with the data
}
// client can also send request to server
socket.send(.... )
با این اوصاف آیا راه حل بهتر و میانه‌تری وجود دارد؟
بلی. اگر به وضعیت فعلی سکوی کاری ASP.NET نگاه کنیم:

SignalR را می‌توان مشاهده کرد که در گروه ساخت سرویس‌های آن قرار گرفته است. همانطور که ملاحظه می‌کنید، این سرویس جدید آنچنان وابستگی به سایر اجزای آن نداشته و می‌تواند خارج از ASP.NET نیز مورد استفاده قرار گیرد.

SignalR چیست؟

SignalR راه حلی است سمت سرور برای نوشتن push services. همچنین به همراه کتابخانه‌های سمت کاربری است که ارتباطات push services را در انواع و اقسام سکوهای کاری میسر می‌سازد. SignalR سورس باز بوده و برای اعمال غیرهمزمان (asynchronous) بهینه سازی شده است.
SignalR بر اساس مدل ذهنی اتصالات ماندگار (persistent connections) طراحی شده است. اتصالات ماندگار را باید به عنوان اتصالاتی سریع و غیرطولانی درنظر گرفت. در اینجا Signal یک اتصال است که اطلاعاتی به آن ارسال می‌گردد و هدف، انتقال قطعات کوچکی از اطلاعات است و هدف، ارسال حجم عظیمی از اطلاعات نیست. برای مثال اطلاع رسانی سریعی صورت گیرد که تغییراتی رخ داده است و سپس ادامه کار و دریافت اطلاعات واقعی توسط فرآیندهای متداول مثلا HTTP GET انجام شود. البته باید دقت داشت SignalR نیز نهایتا از یکی از 5 روش push بررسی شده در این قسمت استفاده می‌کند. اما بر اساس توانایی‌های کلاینت و سرور، به صورت هوشمند بهترین و بهینه‌ترین انتخاب را به کاربر ارائه می‌دهد.
اتصالات ماندگار قسمت سطح پایین SignalR را تشکیل می‌دهند. سطح بالاتر آن که این مفاهیم را به شکلی کپسوله شده ارائه می‌دهد، Hubs نام دارد که پایه اصلی دوره جاری را تشکیل خواهد داد.



همانطور که عنوان شد، SignalR سورس باز بوده و دارای مخزن کدی عمومی در GitHub است. همچنین بسته‌های تشکیل دهنده‌ی آن از طریق NuGet نیز قابل دریافت هستند. این بسته‌ها شامل هسته SignalR و کلاینت‌های آن مانند کلاینت‌های WinRT، سیلورلایت، jQuery، ویندوز فون8 و امثال آن هستند.

شروع کار با SignalR

تیم SignalR مثالی مقدماتی از نحوه کار با SignalR را به صورت یک بسته NuGet ارائه داده‌اند که از طریق آدرس و فرمان ذیل قابل دریافت است:
 PM> Install-Package Microsoft.AspNet.SignalR.Sample
قبل از اینکه این مثال را دریافت کنید نیاز است ابتدا یک برنامه ASP.NET جدید را آغاز نمائید (تفاوتی نمی‌کند که MVC باشد یا Web forms). سپس دستور فوق را فراخوانی کنید.

پس از دریافت مثال، یکبار پروژه را کامپایل کرده و سپس بر روی فایل StockTicker.html آن کلیک راست نموده و گزینه مشاهده در مرورگر را انتخاب کنید. همچنین برای اینکه این مثال را بهتر مشاهده کنید، بهتر است دو وهله از مرورگر را باز کرده و آدرس باز شده را در آن بررسی کنید تا اعمال تغییرات همزمان به کلاینت‌های متفاوت را بهتر بتوان بررسی و مشاهده کرد.

مطالب
React 16x - قسمت 5 - کامپوننت‌ها - بخش 2 - نمایش لیست‌ها و مدیریت رویدادها و حالات
در قسمت قبل، اولین کامپوننت React خود را ایجاد کردیم و سپس جزئیات بیشتری از عبارات JSX را مانند نحوه‌ی تعریف المان‌های مختلف و تنظیم مقادیر ویژگی‌های آن‌را بررسی کردیم. در ادامه‌ی همان مثال، در این قسمت، نحوه‌ی نمایش لیست‌ها و تعریف و مدیریت رویدادها را در کامپوننت‌های React، بررسی می‌کنیم.


نحوه‌ی رندر لیستی از اشیاء در کامپوننت‌های React

فرض کنید می‌خواهیم لیستی از تگ‌ها را رندر کنیم. برای این منظور ابتدا داده‌های مرتبط را به خاصیت state کامپوننت، اضافه می‌کنیم:
class Counter extends Component {
  state = {
    count: 0,
    tags: ["tag 1", "tag 2", "tag 3"]
  };
اکنون می‌خواهیم tags را توسط المان‌های ul و ui رندر کنیم. اگر با Angular کار کرده باشید، به همراه یک دایرکتیو ngFor است که توسط آن می‌توان یک حلقه را در قالب جاری، پیاده سازی و رندر کرد. اما در React و عبارات JSX، چیزی به نام مفهوم حلقه‌ها وجود خارجی ندارد؛ چون JSX یک templating engine نیست. فقط بیان ساده‌ی المان‌هایی است که قرار است توسط کامپایلر Babel به کدهای جاوا اسکریپتی ترجمه شوند. بنابراین اکنون این سؤال وجود دارد که چگونه می‌توان لیستی از عناصر را در اینجا رندر کرد؟
در مطلب «React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2» در مورد متد Array.map بحث شد. در اینجا می‌توان توسط متد map، هر المان آرایه‌ی تگ‌ها را به یک المان React تبدیل و سپس رندر کرد:
class Counter extends Component {
  state = {
    count: 0,
    tags: ["tag 1", "tag 2", "tag 3"]
  };

  render() {
    return (
      <div>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
        <ul>
          {this.state.tags.map(tag => (
            <li>{tag}</li>
          ))}
        </ul>
      </div>
    );
  }
در این مثال، داخل المان ul، با یک {} شروع می‌کنیم تا بتوان به صورت پویا به مقدار آرایه‌ی this.state.tags دسترسی پیدا کرد. سپس متد map را بر روی این آرایه فراخوانی می‌کنیم. متد map، هر عضو آرایه‌ی tags را به callback function آن ارسال کرده و خروجی آن‌را به صورت یک عبارت JSX که در نهایت به یک المان جاوا اسکریپتی خالص ترجمه خواهد شد، تبدیل می‌کند. این فرآیند سبب رندر لیست tags می‌شود:


هرچند اکنون لیستی از تگ‌ها در مرورگر رندر شده‌اند، اما در کنسول توسعه دهندگان مرورگر، یک اخطار نیز درج شده‌است. علت اینجا است که React نیاز دارد تا بتواند هر آیتم رندر شده را به صورت منحصربفردی شناسایی کند. هدف این است که بتواند در صورت تغییر state هر المان در DOM مجازی خودش، خیلی سریع تشخیص دهد که چه چیزی تغییر کرده و فقط کدام قسمت خاص را باید در DOM اصلی، درج و به روز رسانی کند. برای رفع این مشکل، ویژگی key را به هر المان li در کدهای فوق اضافه می‌کنیم:
<li key={tag}>{tag}</li>
البته در مثال ما تگ‌ها منحصربفرد هستند؛ بنابراین استفاده‌ی از آن‌ها به عنوان key، مشکلی را ایجاد نمی‌کند. در یک برنامه‌ی مفصل‌تر، تگ‌ها می‌توانند شیء بوده و هر شیء دارای خاصیت id باشد که در این حالت فرضی می‌توان از tag.id به عنوان key استفاده کرد. همچنین باید دانست که این key فقط نیاز است در لیست ul، منحصربفرد باشد و نیازی نیست تا در کل DOM منحصربفرد باشد.


رندر شرطی عناصر در کامپوننت‌های React

در اینجا می‌خواهیم اگر تگی وجود نداشت، پیام متناسبی ارائه شود؛ در غیراینصورت لیست تگ‌ها همانند قبل نمایش داده شود (رندر شرطی یا conditional rendering). برای انجام اینکار در React، برخلاف Angular، دارای دایرکتیوهای ساختاری if/else نیستیم؛ چون همانطور که عنوان شد، JSX یک templating engine نیست. به همین جهت برای رندر شرطی المان‌ها در React، باید از همان جاوا اسکریپت خالص کمک بگیریم:
  renderTags() {
    if (this.state.tags.length === 0) {
      return <p>There are no tags!</p>;
    }

    return (
      <ul>
        {this.state.tags.map(tag => (
          <li key={tag}>{tag}</li>
        ))}
      </ul>
    );
  }
یک روش حل این مساله، نوشتن متدی است که به همراه یک if/else است. در اینجا اگر آرایه‌ی تگ‌ها، دارای عنصری نبود، یک پاراگراف متناظر نمایش داده می‌شود، در غیراینصورت همان قسمت رندر لیست تگ‌ها را که توسعه دادیم، بازگشت می‌دهیم. بنابراین این متد، دو خروجی JSX را بسته به شرایط مختلف می‌تواند داشته باشد. سپس از این متد به صورت {()this.renderTags} در متد render اصلی استفاده می‌کنیم:
  render() {
    return (
      <div>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
        {this.renderTags()}
      </div>
    );
  }
برای آزمایش آن هم یکبار آرایه‌ی tags را به نحو زیر خالی کنید:
  state = {
    count: 0,
    tags: []
  };

روش دوم حل این نوع مساله‌ها، استفاده از روش زیر است؛ در این حالت خاص، فقط یک if را داریم، بدون وجود قسمت else:
{this.state.tags.length === 0 && "Please create a new tag!"}
ابتدا شرط مدنظر نوشته می‌شود، سپس پیامی را که باید در این حالت ارائه شود، پس از && می‌نویسیم. در مثال فوق اگر آرایه‌ی tags خالی باشد، پیامی نمایش داده می‌شود.
اما این روش چگونه کار می‌کند؟! در اینجا && را به دو مقدار مشخص اعمال کرده‌ایم. یکی حاصل یک مقایسه است و دیگری یک مقدار رشته‌ای مشخص. در جاوا اسکریپت برخلاف سایر زبان‌های برنامه نویسی، می‌توان && را بین دو مقدار غیر Boolean نیز اعمال کرد. در جاوا اسکریپت، یک رشته‌ی خالی به false تعبیر می‌شود و اگر تنها دارای یک حرف باشد، true درنظر گرفته می‌شود. برای نمونه در ترکیب 'true && 'Hi، هر دو قسمت به true تفسیر می‌شوند. در این حالت موتور جاوا اسکریپت، دومین عبارت (آخرین عبارت && شده) را بازگشت می‌دهد. همچنین در جاوا اسکریپت عدد صفر به false تفسیر می‌شود. بنابراین ترکیب true && 'Hi' && 1 مقدار 1 را بازگشت می‌دهد؛ چون عدد 1 هم از دیدگاه جاوا اسکریپت به true تفسیر خواهد شد.


مدیریت رخ‌دادها در React


همانطور که در تصویر فوق نیز مشاهده می‌کنید، رخ‌دادهای استاندارد DOM، دارای خواص معادل React ای نیز هستند. برای مثال زمانیکه می‌نویسیم onClick، دقیقا متناظر است با یک خاصیت المان React در عبارات JSX. بنابراین این نام‌ها حساس به کوچکی و بزرگی حروف نیز هستند.
روش تعریف متدهای رخ‌دادگردان در اینجا، با ذکر فعل handle شروع می‌شود:
  handleIncrement() {
    console.log("Increment clicked!");
  }
سپس ارجاعی از این متد را (نه فراخوانی آن‌را)، به خاصیت برای مثال onClick ارسال می‌کنیم:
<button
    onClick={this.handleIncrement}
    className="btn btn-secondary btn-sm"
>
    Increment
</button>
اگر دقت کنید، onClick، ارجاع this.handleIncrement را دریافت کرده‌است (یعنی بدون () ذکر شده‌است) و نه فراخوانی این متد را (با ذکر ()).
اکنون اگر این فایل را ذخیره کرده و خروجی را در مرورگر بررسی کنیم، با هربار کلیک بر روی دکمه‌ی Increment، یک console.log صورت می‌گیرد.

در ادامه می‌خواهیم در این رخ‌دادگردان، مقدار this.state.count را افزایش دهیم. برای این منظور ابتدا مقدار this.state.count را به نحو زیر لاگ می‌کنیم:
  handleIncrement() {
    console.log("Increment clicked!", this.state.count);
  }
پس از ذخیره‌ی فایل و اجرای برنامه، اینبار با کلیک بر روی دکمه‌ی Increment، بلافاصله خطای «Uncaught TypeError: Cannot read property 'state' of undefined» در کنسول توسعه دهنده‌های مرورگر ظاهر می‌شود. عنوان می‌کند که شیء this در این متد، undefined است؛ بنابراین امکان خواندن خاصیت state از آن وجود ندارد.


bind مجدد شیء this در رخ‌دادگردان‌های React

در مورد this و bind مجدد آن در مطلب «React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1» مفصل بحث کردیم و در اینجا می‌خواهیم از نتایج آن استفاده کنیم.
همانطور که مشاهده کردید، در متد رویدادگران handleIncrement، به شیء this دسترسی نداریم. چرا؟ چون this در جاوا اسکریپت نسبت به سایر زبان‌های برنامه نویسی، متفاوت رفتار می‌کند. بسته به اینکه یک متد یا تابع، چگونه فراخوانی می‌شود، this می‌تواند اشیاء متفاوتی را بازگشت دهد. اگر تابعی به عنوان یک متد و جزئی از یک شیء فراخوانی شود، this در این حالت همواره ارجاعی را به آن شیء باز می‌گرداند. اما اگر آن تابع به صورت متکی به خود فراخوانی شد، به صورت پیش‌فرض ارجاعی را به شیء سراسری window مرورگر، بازگشت می‌دهد و اگر strict mode فعال باشد، تنها undefined را بازگشت می‌دهد. به همین جهت است که در اینجا خطای undefined بودن this را دریافت می‌کنیم.
یک روش حل این مشکل که پیشتر نیز در مورد آن توضیح دادیم، استفاده از متد bind است:
  constructor() {
    super();
    console.log("constructor", this);
    this.handleIncrement = this.handleIncrement.bind(this);
  }
زمانیکه شیءای از نوع کلاس جاری ایجاد می‌شود، متد constructor آن نیز فراخوانی خواهد شد. در این مرحله دسترسی کاملی به شیء this وجود دارد که نمونه‌ی آن‌را با console.log نوشته شده می‌توانید آزمایش کنید. در اینجا چون کامپوننت جاری از کلاس Component مشتق شده‌است، پیش از دسترسی به شیء this، نیاز است سازنده‌ی کلاس پایه توسط متد super فراخوانی شود. اکنون که به this دسترسی داریم، می‌توان توسط متد bind، مقدار شیء this شیءای دیگر مانند this.handleIncrement را تنظیم مجدد کنیم (متدها نیز در جاوا اسکریپت شیء هستند). خروجی آن، یک وهله‌ی جدید از شیء handleIncrement است که this آن اینبار به وهله‌ای از شیء جاری اشاره می‌کند. به همین جهت خروجی آن‌را به this.handleIncrement انتساب می‌دهیم تا مشکل تعریف نشده بودن this آن برطرف شود.
اکنون اگر برنامه را اجرا کنید، با کلیک بر روی دکمه‌ی Increment، بجای this.state.count لاگ شده، مقدار آن که صفر است، در کنسول توسعه دهنده‌های مرورگر ظاهر می‌شود.


این یک روش است که کار می‌کند؛ اما کمی طولانی است و به ازای هر روال رویدادگردانی باید دقیقا به همین نحو تکرار شود. روش دیگر، تبدیل متد handleIncrement به یک arrow function است و همانطور که در قسمت دوم این سری نیز بررسی کردیم، arrow functionها، this شیء جاری را بازنویسی نمی‌کنند؛ بلکه آن‌را به ارث می‌برند. بنابراین ابتدا کدهای سازنده‌ی فوق را حذف می‌کنیم (چون دیگر نیازی به آن‌ها نیست) و سپس متد handleIncrement سابق را به صورت زیر، تبدیل به یک arrow function می‌کنیم:
  handleIncrement = () => {
    console.log("Increment clicked!", this.state.count);
  }
به این ترتیب با کلیک بر روی دکمه‌ی Increment، مجددا همان خروجی تصویر قبلی را دریافت می‌کنیم؛ این روش ساده‌تر و تمیزتر است و نیازی به rebind دستی تک تک رویدادگردان‌های کامپوننت جاری در این حالت وجود ندارد.


به روز رسانی state در کامپوننت‌های React

اکنون که در روال رویدادگردان handleIncrement به شیء this و سپس مقدار this.state.count آن دسترسی پیدا کرده‌ایم، می‌خواهیم با هربار کلیک بر روی این دکمه، یک واحد مقدار آن‌را افزایش داده و در UI نمایش دهیم.
در React، خواص شیء state را جهت نمایش آن‌ها در UI، مستقیما تغییر نمی‌دهیم. به عبارت دیگر نوشتن یک چنین کدی در React برای به روز رسانی UI، مرسوم نیست:
  handleIncrement = () => {
    this.state.count++;
  };
اگر تغییر فوق را اعمال و سپس برنامه را اجرا کنید، با کلیک بر روی دکمه‌ی Increment ... اتفاقی رخ نمی‌دهد! رفتار React با Angular متفاوت است و در اینجا هرچند توسط فراخوانی {()this.formatCount} کار نمایش خاصیت count انجام می‌شود، اما به ظاهر، تغییرات مقدار count، به عبارات JSX متصل نیست. در کامپوننت‌های Angular اگر مقدار خاصیتی را تغییر دهید و اگر این خاصیت در قالب آن کامپوننت، به آن خاصیت bind شده باشد، شاهد به روز رسانی آنی UI خواهید بود (Change Detection آنی و به ازای هر تغییری)؛ اما در React خیر. هرچند در همان Angular هم توصیه می‌شود که از حالت changeDetection: ChangeDetectionStrategy.OnPush برای رسیدن به حداکثر کارآیی نمایشی کامپوننت‌ها استفاده شود؛ حالت OnPush در Angular، به روش تشخیص تغییرات React که در ادامه توضیح داده می‌شود، بیشتر شبیه است.

در کدهای فوق هرچند با کلیک بر روی دکمه‌ی Increment، مقدار count افزایش یافته‌است، اما React از وقوع این تغییرات مطلع نیست. به همین جهت است که هیچ تغییری را در UI برنامه مشاهده نمی‌کنید.
با اجرای قطعه کد فوق، یک چنین اخطاری نیز در کنسول توسعه دهندگان مرورگر ظاهر می‌شود:
  Line 33:5:  Do not mutate state directly. Use setState()  react/no-direct-mutation-state

برای رفع این مشکل باید از یکی از متدهای به ارث برده شده‌ی از کلاس پایه‌ی Component، به نام setState استفاده کرد. به این ترتیب به React اعلام می‌کنیم که state تغییر کرده‌است (فعالسازی Change Detection، فقط در صورت نیاز). سپس React شروع به محاسبه‌ی تغییرات کرده و در نتیجه قسمت‌های متناظری از UI را برای هماهنگ سازی DOM مجازی خودش با DOM اصلی، به روز رسانی می‌کند.
زمانیکه از متد setState استفاده می‌کنیم، شیءای را باید به صورت یک پارامتر به آن ارسال کنیم. در این حالت مقادیر آن یا به خاصیت state جاری اضافه می‌شوند و یا در صورت از پیش موجود بودن، همان خواص را بازنویسی می‌کنند:
  handleIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };
در اینجا به متد this.setState که از قسمت extends Component جاری به ارث رسیده‌است، یک شیء را با خاصیت count و مقدار جدیدی، ارسال می‌کنیم.
در این مرحله، فایل جاری را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر، بر روی دکمه‌ی Increment کلیک کنید. اینبار ... کار می‌کند! چون React از تغییرات مطلع شده‌است:


وقتی state تغییر می‌کند، چه اتفاقاتی رخ می‌دهند؟

با فراخوانی متد this.setState، به React اعلام می‌کنیم که state یک کامپوننت قرار است تغییر کند. سپس React فراخوانی مجدد متد Render را در صف اجرایی خودش قرار می‌دهد تا در زمانی در آینده، اجرا شود؛ این فراخوانی async است. کار متد render، بازگشت یک المان جدید React است. در اینجا DOM مجازی React از چند المان، به صورت یک div و دو فرزند دکمه و span تشکیل شده‌است. در این حالت یک DOM مجازی قدیمی نیز از قبل (پیش از اجرای مجدد متد render) وجود دارد. در این لحظه، React این دو DOM مجازی را کنار هم قرار می‌دهد و محاسبه می‌کند که در اینجا دقیقا کدام المان‌ها نسبت به قبل تغییر کرده‌اند. برای نمونه در اینجا تشخیص می‌دهد که span است که تغییر کرده، چون مقدار count، توسط آن نمایش داده می‌شود. در این حالت از کل DOM اصلی، تنها همان span تغییر کرده را به روز رسانی می‌کند و نه کل DOM را (و نه اعمال مجدد کل المان‌های حاصل از متد render را).
این مورد را می‌توان به نحو زیر آزمایش و مشاهده کرد:
در مرورگر بر روی المان span که شماره‌ها را نمایش می‌دهد، کلیک راست کرده و گزینه‌ی inspect را انتخاب کنید. سپس بر روی دکمه‌ی Increment کلیک نمائید. مرورگر قسمتی را که به روز می‌شود، با رنگی مشخص و متمایز، به صورت لحظه‌ای نمایش می‌دهد:



ارسال پارامترها به متدهای رویدادگردان

تا اینجا متد handleIncrement، بدون پارامتر تعریف شده‌است. فرض کنید در یک برنامه‌ی واقعی قرار است با کلیک بر روی این دکمه، id یک محصول را نیز به handleIncrement، منتقل و ارسال کنیم. اما در onClick={this.handleIncrement} تعریف شده، یک ارجاع را به متد handleIncrement داریم. بنابراین برای حل این مساله نمی‌توان از روشی مانند onClick={this.handleIncrement(1)} استفاده کرد که در آن عدد فرضی 1 به صورت آرگومان متد handleIncrement ذکر شده‌است.
یک روش حل این مساله، تعریف متد دومی است که متد handleIncrement پارامتر دار را فراخوانی می‌کند:
  doHandleIncrement = () => {
    this.handleIncrement({ id: 1, name: "Product 1" });
  };
و در این حالت برای مثال متد handleIncrement یک شیء را پذیرفته‌است:
  handleIncrement = product => {
    console.log(product);
    this.setState({ count: this.state.count + 1 });
  };
سپس بجای تعریف onClick={this.handleIncrement}، از متد doHandleIncrement استفاده خواهیم کرد؛ یعنی onClick={this.doHandleIncrement}

هرچند این روش کار می‌کند، اما بیش از اندازه طولانی شده‌است. راه حل بهتر، استفاده از یک inline function است:
onClick={() => this.handleIncrement({ id: 1, name: "Product 1" })}
یعنی کل arrow function مربوط به doHandleIncrement را داخل onClick قرار می‌دهیم و چون یک سطری است، نیازی به ذکر {} و سمی‌کالن انتهای آن‌را هم ندارد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-04-part02.zip
مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت سوم
قسمت دوم 

8.ORM Lazy Load
در هنگام استفاده از ORM‌ها دقت کنید کجا از Lazy Load استفاده می‌کنید. Lazy Load باعث می‌شود وقتی شما اطلاعات مرتبط را از بانک اطلاعات واکشی می‌کنید، این واکشی اطلاعات در چند query از بانک انجام شود. درعوض عدم استفاده از Lazy Load باعث می‌شود تمامی اطلاعات مورد نیاز شما در یک query از بانک اطلاعاتی دریافت شود. این موضوع یعنی سربار کمتر در شبکه، در بانک اطلاعاتی، در منابع حافظه و منابع پر ازرش cpu در سرورها. البته استفاده از include در حالت فعال بودن یا نبودن lazy هم داستان مجزایی دارد که اگر عمری باقی باشد راجع به آن مقاله ای خواهم نوشت.
 به این نمونه دقت کنید:
List<Customer> customers = context.Customers.ToList();
foreach (Customer cust in context.Customers){
   Console.WriteLine("Customer {0}, Account {1}", cust.Person.LastName.Trim() + ", " + cust.Person.FirstName, cust.AccountNumber); 
}
همچین کدی (در صورت فعال بودن Lazy Load در ORM) در صورتی که جدول Customers دارای 1000 رکورد باشد، باعث می‌شود برنامه 1001 دستور sql تولید و در بانک اجرا گردد.
برای اطلاع بیشتر می‌توانید به این مقاله مراجعه نمایید.

9.استفاده از MiniProfiler
سعی کنید از MiniProfiler در تمامی پروژه‌ها استفاده کنید. البته وقتی نرم افزار را در اختیار مصرف کننده قرار می‌دهید، آن را غیر فعال کنید. می‌توانید از متغیرهای compiler برای مجزا کردن build‌های متفاوت در برنامه خود استفاده کنید:
#if DEBUG then
// فعال سازی MiniProfiler
#endif
ایده دیگری هم وجود دارد. شما می‌توانید MiniProfiler را برای کاربر Admin یا کاربر Debugger فعال و برای بقیه غیر فعال کنید. در باب MiniProfiler مسائل زیادی وجود دارد که چند نمونه از آن در همین سایت در این مقاله و این مقاله در دسترس است. البته می‌توانید از ابزارهای دیگری مانند Glimpse که در این زمینه وجود دارد نیز استفاده کنید. لب کلام این نکته استفاده از profiler برای نرم افزار خود می‌باشد.

10. Data Paging در بانک اطلاعاتی
هنگامیکه از کامپوننت‌های شرکت‌های دیگر (Third party) استفاده می‌کنید، اطمینان حاصل کنید که صفحه بندی اطلاعات در بانک اطلاعاتی انجام می‌شود. برای نمونه کاپوننت گرید شرکت Telerik چند نوع صفحه بندی را پشتیبانی می‌کند. صفحه بندی سمت کاربر (توسط JavaScript)، صفحه بندی سمت سرور توسط کامپوننت و صفحه بندی مجازی. صفحه بندی سمت کاربر یعنی تمامی اطلاعات از سرور به کاربر فرستاده شده و در سمت کاربر عمل صفحه بندی انجام می‌شود. این یعنی واکشی تمامی اطلاعات از بانک و در مورد نرم افزارهای پرکاربر با حجم اطلاعات زیاد یعنی فاجعه. صفحه بندی سمت سرور ASP.NET هم یعنی واکشی اطلاعات از سرور بانک به سرور برنامه و سپس صفحه بندی توسط برنامه. این موضوع هم ممکن است مشکلات زیادی را ایجاد نماید چون باید حداقل تمامی رکوردها از اولین رکورد تا آخرین رکورد صفحه جاری از بانک واکشی شود که این عمل علاوه بر ایجاد سربار شبکه، سربار IO در بانک اطلاعاتی و سربار cpu در سرور ASP.NET ایجاد می‌کند. استفاده از صفحه بندی مجازی، شما را قادر می‌کند بتوانیم اطلاعات را در بانک صفحه بندی کرده و فقط صفحه مورد نظر خود را از بانک واکشی کنیم.
این حالت مجازی در اکثر component‌ها که توسط شرکت‌های مختلف ایجاد شده وجود دارد ولی ممکن است نام‌های متفاوتی داشته باشد. برای این موضوع باید به راهنمای component خریداری شده مراجعه کنید و یا به فروم‌ها و... مراجعه نمایید.

11. بررسی تعداد کوئری‌های صادر شده در یک صفحه و تعداد رکوردهای بازگشت داده شده توسط آن‌ها
این به این معنا نیست که برای هر query یک context مجزا ایجاد کنید، منظور این است که به بهانه اینکه اطلاعات مختلفی از جداول مختلف مورد نیاز است، query خود را آن قدر پیچیده یا گسترده ننویسیم که یا process آن در بانک زمان و سربار زیادی ایجاد کند و یا حجم اطلاعات بلا استفاده ای را از بانک به سرور برنامه لود نماید. به جای این موضوع می‌توانید در یک یا چند context دستورات مجزای واکشی اطلاعات صادر کنید تا تنها اطلاعات مورد نیاز خود را واکشی نمایید. البته این موضوع باعث نشود که تعداد query‌ها مثلا به 1000 عدد برسد! یعنی باید فیمابین query‌های پیچیده و query‌های ساده ولی با تعداد یکی را که مناسبتر با پروژه است انتخاب کنید که این موضوع با تجربه و تست حاصل می‌شود.
نظرات مطالب
React 16x - قسمت 7 - ترکیب کامپوننت‌ها - بخش 1 - ارسال داده‌ها، مدیریت رخ‌دادها
 این استاندارد HTML 5 هست. در این استاندارد اگر یک attribute یا ویژگی به صورت boolean تعریف شود، اگر بدون مقدار ذکر شود، مقدار آن true بازگشت داده خواهد شد. البته این نکته فقط در مورد booleanها است. اگر از ویژگی‌های خاصی مانند data-checked استفاده کنید و مقداری برای آن ذکر نشود، مقدار انتساب داده شده‌ی به آن، یک رشته‌ی خالی خواهد بود.
A number of attributes are boolean attributes.
The presence of a boolean attribute on an element represents the true value,
and the absence of the attribute represents the false value.
نظرات نظرسنجی‌ها
کدامیک از سرویس دهنده‌های ابری زیر را پیشنهاد می‌کنید
سلام. از سرویس زس البته غیر از PaaS برای سرویس‌های کنترل پروژه و چندین وب سایت و اپلیکیشن استفاده کردیم. میزان down بودنشون بالا بود. پشتیبانی هم طول می‌کشید جواب بدن. الان کلیه سرویسها رو آوردیم رو ابر آروان (البته این گزینه در نظرسنجی شما نبود). کیفیت سرویس‌ها که خیلی خوبه کاملا راضی هستم. فعلا PaaS بصورت آزمایشی هست و SLA اون آماده نشده. پشتیبانی هم چند بار نیاز بود و سرعت پاسخگوییشون خوب بود مشکلمون هم حل شد. در مورد کنترل پنل، شاید از ساده‌ترین ها باشه. همه چیز رو میشه داخل پنل تنظیم کرد و بعضی موارد اصلا نیاز نیست داخل VM تنظیمات رو انجام بدید. پلن‌های امنیتی و DNS و CDN هاشون هم خیلی خوبه و من راضیم. هزینه سرویس‌ها هم در مقایسه با شرکتهای دیگه تقریبا مشابه هستش. شاید ایراد اصلی که میشه ازش گرفت عدم پشتیبان گیری هستش که ظاهرا برنامه دارن اضافه کنن.