مطالب
آیا از وضعیت رویه‌های ذخیره شده‌ی دیتابیس‌های اس کیوال سرور خود خبر دارید؟

به لطف امکانات سیستمی اس کیوال سرورهای 2005 به بعد و DMV های آن‌ها، آمارگیری از ریز اتفاقات رخ داده در یک اس کیوال سرور این روزها بسیار ساده شده است و نیازی به ابزارهای جانبی برای انجام این نوع عملیات نیست (یا کمتر هست). در ادامه مروری خواهیم داشت بر یک سری کوئری که اطلاعات جالبی را در مورد وضعیت رویه‌های ذخیره شده‌ی دیتابیس‌های شما ارائه می‌دهند. لازم به ذکر است که اکثر این آمارها با هر بار ری استارت سرور، صفر خواهند شد.

آیا می‌دانید در یک دیتابیس خاص کدامیک از رویه‌های ذخیره شده‌ی شما بیش از سایرین مورد استفاده بود و آماری از این دست؟

use dbName;
SELECT TOP(100) qt.text AS 'SP Name',
qs.execution_count AS 'Execution Count',
qs.execution_count / DATEDIFF(Second, qs.creation_time, GETDATE()) AS
'Calls/Second',
qs.total_worker_time / qs.execution_count AS 'AvgWorkerTime',
qs.total_worker_time AS 'TotalWorkerTime',
qs.total_elapsed_time / qs.execution_count AS 'AvgElapsedTime',
qs.max_logical_reads,
qs.max_logical_writes,
qs.total_physical_reads,
DATEDIFF(Minute, qs.creation_time, GETDATE()) AS 'Age in Cache'
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt
WHERE qt.dbid = DB_ID() -- Filter by current database
ORDER BY
qs.execution_count DESC

البته مرتب سازی پیش فرض این کوئری بر اساس تعداد بار اجرا است (رویه‌های ذخیره شده‌ی محبوب!)، می‌شود آن‌را بر اساس total_worker_time (فشار بر روی CPU سیستم)، total_logical_reads (فشار بر روی حافظه)، total_physical_reads (فشار I/O کوئری‌ها)، total_logical_writes نیز مرتب کرد و نتایج جالب توجهی را بدست آورد.


آیا می‌دانید کدامیک از رویه‌های ذخیره شده‌ی شما بیش از سایرین کامپایل مجدد شده است؟

select top 50
sql_text.text,
sql_handle,
plan_generation_num,
execution_count,
dbid,
objectid
from
sys.dm_exec_query_stats a
cross apply sys.dm_exec_sql_text(sql_handle) as sql_text
where
plan_generation_num >1
order by plan_generation_num desc

آیا می‌دانید آخرین باری که رویه‌های ذخیره شده‌ی شما ویرایش شده‌اند چه زمانی بوده است؟

SELECT NAME,
create_date,
modify_date
FROM sys.objects
WHERE TYPE = 'P'
ORDER BY
Modify_Date DESC,
NAME

مطالب
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
پیشنیاز این بحث مطالعه‌ی مطلب «صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC» است و در اینجا جهت کوتاه شدن بحث، صرفا به تغییرات مورد نیاز جهت اعمال بر روی مثال اول اکتفاء خواهد شد.


تغییرات مورد نیاز جهت فعال سازی ویرایش، حذف و افزودن رکوردهای jqGrid

می‌خواهیم در بدو نمایش گرید، یک ستون خاص دارای دکمه‌های ویرایش و حذف ظاهر شوند:


برای اینکار تنها کافی است در انتهای ستون‌های تعریف شده، یک ستون خاص را با formatter مساوی actions ایجاد کنیم:
                colModel: [
                    {
                        // سایر ستون‌ها
                        name: 'myac', width: 80, fixed: true, sortable: false,
                        resize: false, formatter: 'actions',
                        formatoptions: {
                            keys: true
                        }
                    }
                ],
برای اینکه دکمه‌های ویرایش و حذف ردیف‌های آن عمل کنند:


نیاز است تعاریف سایر ستون‌هایی را که باید قابلیت ویرایش داشته باشند، به نحو ذیل تغییر دهیم:
                colModel: [
                    {
                        name: 'Id', index: 'Id', align: 'right', width: 70,
                        editable: false
                    },
                    {
                        name: 'Name', index: 'Name', align: 'right', width: 100,
                        editable: true, edittype: 'text',
                        editoptions: {
                            maxlength: 40
                        },
                        editrules: {
                            required: true
                        }
                    },
                    {
                        name: 'Supplier.Id', index: 'Supplier.Id', align: 'right', width: 110,
                        editable: true, edittype: 'select',
                        editoptions: {
                            dataUrl: '@Url.Action("SuppliersSelect","Home")'
                        },
                        editrules: {
                            required: true
                        }
                    },
                    {
                        name: 'Category.Id', index: 'Category.Id', align: 'right', width: 110,
                        editable: true, edittype: 'select',
                        editoptions: {
                            dataUrl: '@Url.Action("CategoriesSelect","Home")'
                        },
                        editrules: {
                            required: true
                        }
                    },
                    {
                        name: 'Price', index: 'Price', align: 'center', width: 100,
                        formatter: 'currency',
                        formatoptions:
                        {
                            decimalSeparator: '.',
                            thousandsSeparator: ',',
                            decimalPlaces: 2,
                            prefix: '$'
                        },
                        editable: true, edittype: 'text',
                        editrules: {
                            required: true,
                            number: true,
                            minValue: 0
                        }
                    },
                    {
                        name: 'myac', width: 80, fixed: true, sortable: false,
                        resize: false, formatter: 'actions',
                        formatoptions: {
                            keys: true
                        }
                    }
                ],
- در اینجا هر ستونی که دارای خاصیت editable مساوی true است، قابلیت ویرایش پیدا می‌کند.
- edittype آن بیانگر کنترلی است که باید حین ویرایش آن سلول خاص ظاهر شود. برای مثال اگر text باشد، یک text box و اگر مانند حالت Supplier.Id مساوی select تعریف شود، یک drop down را ظاهر خواهد کرد. برای مقدار دهی این drop down می‌توان editoptions و سپس dataUrl آن‌را مقدار دهی نمود.
        public ActionResult SuppliersSelect()
        {
            var list = ProductDataSource.LatestProducts;
            var suppliers = list.Select(x => new SelectListItem
            {
                Text = x.Supplier.CompanyName,
                Value = x.Supplier.Id.ToString(CultureInfo.InvariantCulture)
            }).ToList();
            return PartialView("_SelectPartial", suppliers);
        }
در مثال فوق، این dataUrl به اکشن متد SuppliersSelect اشاره می‌کند که نهایتا لیستی از تولید کننده‌ها را توسط partial view ذیل بازگشت می‌دهد:
 @model IList<SelectListItem>

@Html.DropDownList("srch", Model)
در کل مقادیر قابل تنظیم در اینجا شامل  text، textarea، select، checkbox، password، button، image، file و custom هستند.
- خاصیت editrules، برای مباحث اعتبارسنجی اطلاعات ورودی توسط کاربر پیش بینی شده‌است. برای مثال اگر required: true در آن تنظیم شود، کاربر مجبور به تکمیل این سلول خاص خواهد بود. در اینجا خواصی مانند number و integer از نوع bool، خاصیت‌های minValue و maxValue از نوع عددی، email, url, date, time از نوع bool و custom قابل تنظیم است (مثال‌های حالت custom را در منابع انتهای بحث می‌توانید مطالعه کنید).
- پس از اینکه مشخص شدند کدامیک از ستون‌ها باید قابلیت ویرایش داشته باشند، مسیری که باید اطلاعات نهایی را به سرور ارسال کند، توسط خاصیت editurl مشخص می‌شود:
$('#list').jqGrid({
   caption: "آزمایش چهارم",
   //url from wich data should be requested
   url: '@Url.Action("GetProducts","Home")',
   //url for edit operation
   editurl: '@Url.Action("EditProduct","Home")',
اکشن متد متناظر با این آدرس یک چنین شکلی را می‌تواند داشته باشد:
        [HttpPost]
        public ActionResult EditProduct(Product postData)
        {
            //todo: Edit product based on postData

            return Json(true);
        }
- تعاریف مسیرهای ارسال اطلاعات Add و Delete، در قسمت تنظیمات navGrid باید ذکر شوند:
            $('#list').navGrid(
                '#pager',
                //enabling buttons
                { add: true, del: true, edit: false, search: false },
                //edit options
                {},
                //add options
                { width: 'auto', url: '@Url.Action("AddProduct","Home")' },
                //delete options
                { url: '@Url.Action("DeleteProduct","Home")' }
                );
امضای این اکشن متدها نیز بسیار شبیه به اکشن متد ویرایش است:
        [HttpPost]
        public ActionResult DeleteProduct(string id)
        {
            //todo: Delete product
            return Json(true);
        }

        [HttpPost]
        public ActionResult AddProduct(Product postData)
        {
            //todo: Add product to repository
            return Json(true);
        }
- حالت ویرایش و حذفی که تا اینجا بررسی شد (ستون actions)، جزو خواص توکار این گرید است. اگر بخواهیم آن‌ها را دستی فعال کنیم (جهت اطلاعات عمومی) می‌توان از فراخوانی متد ذیل نیز کمک گرفت:
        var lastSel;
        function inlineEdit() {
            $('input[name=rdEditApproach]').attr('disabled', true);
            $('#list').navGrid(
                '#pager',
                //enabling buttons
                { add: true, del: true, edit: false, search: false },
                //edit options
                {},
                //add options
                { width: 'auto', url: '@Url.Action("AddProduct","Home")' },
                //delete options
                { url: '@Url.Action("DeleteProduct","Home")' }
                );
            //add onSelectRow event to support inline edit
            $('#list').setGridParam({
                onSelectRow: function (id) {
                    if (id && id != lastSel) {
                        //save changes in row
                        $('#list').saveRow(lastSel, false);
                        lastSel = id;
                    }
                    //trigger inline edit for row
                    $('#list').editRow(id, true);
                }
            });
        };
در اینجا ابتدا همان تنظیمات مسیرهای Add و Delete انجام شده‌است. سپس با فراخوانی دستی متد editRow در زمان کلیک بر روی یک ردیف، همان کاری را که ستون actions در جهت فعال سازی خودکار حالت ویرایش سلول‌ها انجام می‌دهد، می‌توان شبیه سازی کرد. متد saveRow نیز کار ارسال اطلاعات تغییر کرده را به سرور انجام می‌دهد.
- برای فعال سازی خودکار فرم‌های افزودن رکوردها و یا ویرایش ردیف‌های موجود می‌توان از فراخوانی متد formEdit ذیل کمک گرفت:
        function formEdit() {
            $('input[name=rdEditApproach]').attr('disabled', true);
            $('#list').navGrid(
                '#pager',
                //enabling buttons
                { add: true, del: true, edit: true, search: false },
                //edit option
                {
                    width: 'auto', checkOnUpdate: true, checkOnSubmit: true,
                    beforeShowForm: function (form) {
                        centerDialog(form, $('#list'));
                    }
                },
                //add options
                {
                    width: 'auto', url: '@Url.Action("AddProduct","Home")',
                    reloadAfterSubmit: false, checkOnUpdate: true, checkOnSubmit: true,
                    beforeShowForm: function (form) {
                        centerDialog(form, $('#list'));
                    }
                },
                //delete options
                {
                    url: '@Url.Action("DeleteProduct","Home")', reloadAfterSubmit: false
                })
            .jqGrid('navButtonAdd', "#pager", {
                caption: "حذف ردیف‌های انتخابی", title: "Delete Toolbar", buttonicon: 'ui-icon ui-icon-trash',
                onClickButton: function () {
                    var idsList = jQuery("#list").jqGrid('getGridParam', 'selarrrow');
                    alert(idsList);
                    //jQuery("#list").jqGrid('delGridRow',idsList,{reloadAfterSubmit:false});
                }
            });
        };

        function centerDialog(form, grid) {
            var dlgDiv = $("#editmod" + grid[0].id);
            var parentDiv = dlgDiv.parent(); // div#gbox_list
            var dlgWidth = dlgDiv.width();
            var parentWidth = parentDiv.width();
            var dlgHeight = dlgDiv.height();
            var parentHeight = parentDiv.height();
            var parentTop = parentDiv.offset().top;
            var parentLeft = parentDiv.offset().left;
            dlgDiv[0].style.top =  Math.round(  parentTop  + (parentHeight-dlgHeight)/2  ) + "px";
            dlgDiv[0].style.left = Math.round(  parentLeft + (parentWidth-dlgWidth  )/2 )  + "px";
        }
ابتدای تنظیمات آن، شاهد add: true, del: true, edit: true هستید. این مورد سبب می‌شود تا در فوتر گرید، سه دکمه‌ی افزودن، ویرایش و حذف ردیف‌ها ظاهر شوند:


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


و با کلیک بر روی دکمه‌ی ویرایش ردیفی انتخاب شده، صفحه‌ی ویرایش آن ردیف به همراه مقادیر سلول‌های آن ظاهر خواهند شد:


تنظیمات قسمت‌های Add و Delete ویرایش توسط فرم‌ها، با حالت ویرایش داخل ردیفی آنچنان تفاوتی ندارد. فقط در اینجا پیش از نمایش فرم، از متد centerDialog برای نمایش صفحات افزودن و ویرایش رکوردها در وسط صفحه، استفاده شده‌است. توسط checkOnUpdate: true, checkOnSubmit: true سبب خواهیم شد تا اگر کاربر مقادیر موجود فرمی را تغییر داده‌است و سعی در بستن فرم، بدون ذخیره سازی اطلاعات کند، پیغام هشدار دهنده‌ای به او نمایش داده شود که آیا می‌خواهید تغییرات را ذخیره کنید یا خیر؟


- در انتهای متد formEdit، به کمک متد jqGrid و پارامتر navButtonAdd یک دکمه‌ی سفارشی را نیز اضافه کرده‌ایم. اگر به ستون پس از شماره‌های خودکار ردیف‌ها، در سمت راست گرید دقت کنید، یک سری chekbox قابل مشاهده هستند. برای فعال سازی خودکار آن‌ها کافی است خاصیت multiselect گرید به true تنظیم شود. اکنون برای دسترسی به این ستون‌های انتخاب شده، می‌توان از متد jqGrid به همراه پارامترهای getGridParam و selarrrow استفاده کرد. خروجی آن، لیست idهای ستون‌ها است.


برای مطالعه بیشتر

Common Editing Properties
Inline Editing
Form Editing
Cell Editing


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid04.zip
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 13 - بررسی سیستم ردیابی تغییرات
متد EF.Property در Selectها هم قابل استفاده‌است:
var items = (from p in context.Categories
                select new  
                {  
                    Id = p.Id,  
                    Name = p.Name,  
                    DateAdded = EF.Property<DateTime>(p, "DateAdded")  
                }).ToList();

و یا خارج از کوئری اصلی توسط Change Tracking API:
var cList = context.Categories
        .OrderBy(b => EF.Property<DateTime>(b, "DateAdded")).ToList();
foreach (var cat in cList)
{
   Console.Write("Category Name: " + cat.CategoryName);
   Console.WriteLine(" Created: " + context.Entry(cat).Property("DateAdded").CurrentValue);
}
بازخوردهای دوره
آشنایی با نحوه ایجاد یک IoC Container
پاسخ به این سؤال نیاز به مطالعه قسمت‌های بعدی دارد. 
- هدف از قسمت جاری آشنایی اولیه با مراحل ابتدایی کار با یک IoC Container است.
- در حالت اول هنوز شما هستید که مسئول وهله سازی‌های اولیه می‌باشید و کار وهله سازی را به لایه‌ای دیگر واگذار نکرده‌اید.
- در کد حالت اول نمی‌شود این وابستگی ارسالی به سازنده کلاس را به سادگی تعویض کرد. در حالیکه با استفاده از یک IoC container فقط کافی است تنظیمات اولیه نگاشت‌های آن‌را مشخص کنیم تا نوع کلاسی که باید در سازنده‌ها تزریق شوند مشخص شود. مزیت اینکار ساده‌تر شدن نوشتن آزمون‌های واحد و تهیه کلاس‌های Fake است؛ بدون نیازی به تغییری حتی در حد یک سطر در کدهای اصلی برنامه.
- در مورد وهله سازی خودکار چند سطح وابستگی‌ها، در قسمت‌های بعد تحت عنوان Object graph بیشتر بحث شده است و مثال زده شده. همیشه با یک کلاس ساده ویزا مانند مثال فوق سر و کار نداریم. عموما با سرویس‌هایی سر و کار داریم که خودشان نیز از سرویس‌های دیگری استفاده می‌کنند. برای مثال یک سرویس ارسال ایمیل از سرویس کاربران برای دریافت ایمیل‌های کاربران کمک می‌گیرد. وهله سازی تمام این وابستگی‌ها را در چند سطح می‌شود با استفاده از IoC Containers خودکار کرد و به کد‌های نهایی بسیار تمیزتری رسید.
- در حالت اول، طول عمر یک شیء را نمی‌شود مشخص کرد (یا حداقل نیاز به کد نویسی قابل توجهی دارد). برای مثال با استفاده از یک IoC Container می‌شود وهله ایجاد شده را Singleton کرد تا در سراسر برنامه یک وهله از آن استفاده شود یا حتی می‌شود در طول یک درخواست رسیده وب، یک وهله را در اختیار تمام کلاس‌های درگیر قرار داد. به این ترتیب به سربار کمتری در حالت‌های خاصی مانند وهله سازی ObjectContext یا DbContext در EF خواهیم رسید.
- زمان استفاده از IoC Containerها کارهای فراتری از تزریق وابستگی‌ها را هم می‌شود انجام داد. برای مثال فراخوانی‌های متدها را هم تحت نظر قرار داد (برنامه نویسی AOP یا جنبه گرا) و مثلا بدون نوشتن کد اضافه‌ای در برنامه، خروجی متدها را کش کرد. AOP یک سری بحث مفصل را در طی یک دوره جدا به همراه دارد.

نظرات مطالب
مبانی TypeScript؛ ماژول‌ها
به مسیر زیر دسترسی دارید؟
http://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.31/system.js
و یا از این روش استفاده کنید برای دریافت فایل‌های آن:
npm install systemjs
نظرات مطالب
امکان استفاده‌ی از قیود مسیریابی سفارشی ASP.NET Core در Blazor SSR برای رمزگشایی خودکار پارامترهای دریافتی

در یک پروژه دات نت 8 با بک اند api و فرانت blazor wasm با توجه به نیاز مندیهای زیر چه راه حلی پیشنهاد میشه؟

1- تبدیل id از نوع int درهمه مدلهای برگشتی به یک مقدار غیر قابل حدس توسط کاربر

2- دریافت مدل یا کوئری استرینگ شامل Id و تبدیل آن به مقدار اصلی

فرض براین است که قصد استفاده از Guid به عنوان کلید اصلی جداول را بخاطر مشکلاتی نظیر حجم جدوال و ایندکس گذاری و... رو نداریم.

مطالب
بررسی کارآیی کوئری‌ها در SQL Server - قسمت ششم - بررسی عملگرهای دسترسی به داده‌ها در یک Query Plan
پس از آشنایی مقدماتی با نحوه‌ی خواندن یک Query Plan، اکنون نوبت به بررسی عملگرهایی است که در آن مشاهده می‌شوند و همچنین تغییرات در کوئری‌ها چگونه بر روی آن‌ها تاثیر گذاشته و آن‌ها را تغییر می‌دهند و این تغییرات چه تاثیری را بر روی کارآیی خواهند داشت.


عملگرهای Scans و Seeks

در حالت کلی می‌توان دو نوع جدول بدون و با ایندکس را درنظر گرفت. در حالت جداول بدون ایندکس، برای جستجوی اطلاعات نیاز به Table Scan وجود دارد و برعکس آن شامل یک Clustered index scan خواهد بود. گاهی از اوقات Clustered index scanها بهترین روش دریافت اطلاعات هستند و گاهی از اوقات خیر و نیاز به بررسی بیشتری دارند. بنابراین قانون کلی، حذف آن‌ها به محض مشاهده، نیست.
نوع دیگر عملگرهای دسترسی به داده‌ها، Seeks هستند که شامل Clustered index seeks و Non-clustered index seeks می‌شوند. در بسیاری از موارد عنوان می‌شود که Seeks کارآیی بهتری را به همراه دارند. هرچند این مورد نیاز به بررسی بیشتری دارد که در ادامه با مثال‌هایی آن‌ها را مرور خواهیم کرد.


بررسی عملگر Table scan در یک Query Plan

در ادامه تعدادی از عملگرهای مرتبط با data access را از لحاظ نحوه‌ی انتخاب و تغییر آن‌ها توسط بهینه ساز کوئری‌های SQL Server بررسی می‌کنیم. برای این منظور ابتدا در management studio از منوی Query، گزینه‌ی Include actual execution plan را انتخاب می‌کنیم. سپس کوئری‌های زیر را اجرا می‌کنیم:
SET STATISTICS IO ON;
GO
SET STATISTICS TIME ON;
GO

SELECT *
INTO [Sales].[Copy_Orders]
FROM [Sales].[Orders];
GO

SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Copy_Orders]
WHERE [CustomerID] > 550;
GO
در اینجا در ابتدا، تمام رکوردهای جدول [Sales].[Orders]، به جدول [Sales].[Copy_Orders] کپی می‌شوند. سپس یک کوئری را بر روی این جدول کپی، اجرا کرده‌ایم.


همانطور که مشاهده می‌کنید، برای برآورده کردن قسمت where این کوئری، یک Table Scan صورت گرفته‌است؛ چون این جدول کپی، به همراه هیچ ایندکسی نیست. به همین جهت برای یافتن رکوردهای مدنظر، راه دیگری بجز اسکن کل جدول بانک اطلاعاتی وجود ندارد که بسیار ناکارآمد است.
همچنین اگر به برگه‌ی messages دقت کنیم، با توجه به روشن بودن STATISTICS IO، میزان logical reads نیز قابل مشاهده‌است:
(33035 rows affected)
Table 'Copy_Orders'. Scan count 1, logical reads 689, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
به علاوه اجرای آن نیز کمی بیشتر از نیم ثانیه، طول کشیده‌است:
SQL Server Execution Times:
CPU time = 79 ms,  elapsed time = 762 ms.


بررسی عملگر Index Seek در یک Query Plan

اکنون سؤال اینجا است که آیا می‌توان این وضعیت را بهبود بخشید؟
بله. برای این منظور یک NONCLUSTERED INDEX را بر روی جدول کپی، ایجاد می‌کنیم؛ به نحوی که CustomerID لحاظ شده‌ی در قسمت where کوئری را پوشش دهد:
CREATE NONCLUSTERED INDEX [IX_Copy_Orders_CustomerID]
ON [Sales].[Copy_Orders] (
[CustomerID]
)
INCLUDE (
[OrderID], [OrderDate]
);
GO
چون مطابق کوئری، [OrderID] و [OrderDate] در قسمت where ذکر نشده‌اند، در اینجا INCLUDE شده‌اند.

در ادامه مجددا همان کوئری را اجرا می‌کنیم:
SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Copy_Orders]
WHERE [CustomerID] > 550;
GO
که سبب تولید کوئری پلن زیر می‌شود:


اینبار عملگر Table Scan قبلی به یک عملگر Index Seek بر روی NONCLUSTERED INDEX تعریف شده، تغییر کرده‌است و اگر به آمار I/O آن دقت کنیم، logical reads 106 قابل مشاهده‌است که بهبود قابل ملاحظه‌ای است نسبت به عدد 689 قبلی.


بررسی عملگر Clustered index scan در یک Query Plan

در ادامه همین کوئری را بر روی جدول [Sales].[Orders] اصلی اجرا می‌کنیم:
SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Orders]
WHERE [CustomerID] > 550;
GO
که به صورت پیش‌فرض شامل این ایندکس‌ها است:


اجرای کوئری فوق، چنین کوئری پلنی را تولید می‌کند:


جدول [Sales].[Orders]، یک CLUSTERED INDEX را بر روی [OrderID] دارد و یک NONCLUSTERED INDEX را بر روی [CustomerID].
در کوئری پلن تولید شده، یک Clustered index scan مشاهده می‌شود. علت اینجا است که هرچند در جدول [Sales].[Orders] یک NONCLUSTERED INDEX بر روی  [CustomerID] تعریف شده‌است:
CREATE NONCLUSTERED INDEX [FK_Sales_Orders_CustomerID] ON [Sales].[Orders]
(
[CustomerID] ASC
)
اما قسمت INCLUDE ایندکس قبلی را که تعریف کردیم، ندارد و به همراه [CustomerID] و [OrderDate] نیست. به همین جهت اینبار logical reads 692 است.

بنابراین وجود عملگر Clustered index scan در یک کوئری پلن، یعنی نیاز به خواندن و اسکن کل جدول وجود دارد. برای اثبات آن، همین کوئری قبلی را که بر روی [Sales].[Orders] انجام دادیم، اینبار بدون قسمت where آن اجرا کنید. یعنی کوئری بر روی کل جدول انجام شود:
SELECT
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Orders]
سپس به برگه‌ی messages مراجعه کرده و عدد logical reads آن‌را مشاهده کنید. این عدد دقیقا با عدد logical reads کوئری where دار، یکی است؛ که بیانگر اسکن کامل جدول در حالت Clustered index scan است.

سؤال: آیا Clustered index scan همواره کل یک جدول را اسکن می‌کند؟
پاسخ: خیر. اگر یک کوئری برای مثال دارای top/min/max باشد، کل جدول اسکن نخواهد شد:
SELECT TOP 10
    [CustomerID],
    [OrderID],
    [OrderDate]
FROM [Sales].[Orders]
WHERE [CustomerID] > 550;
تفاوت این کوئری با کوئری‌های قبلی، در داشتن یک top 10 است. اگر آن‌را اجرا کنیم، به کوئری پلن زیر خواهیم رسید:


هرچند در اینجا هم یک Clustered index scan صورت گرفته، اما اگر به برگه‌ی messages آن مراجعه کنیم، آمار I/O آن بیانگر تنها logical reads 5 است که معادل اسکن کل جدول نیست:
(10 rows affected)
Table 'Orders'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 510, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.


مقایسه‌ی عملگرهای Index Scan و Index Seek

ابتدا کوئری زیر را اجرا می‌کنیم:
SELECT
    [CustomerID],
    [OrderID]
FROM [Sales].[Orders]
WHERE [OrderID] > 30000;
این کوئری با کوئری قبلی از لحاظ قسمت select اندکی متفاوت بوده و در آن OrderDate حذف شده‌است. در قسمت where نیز کوئری بر روی OrderID صورت گرفته‌است.
در این جدول ایندکسی بر روی CustomerID وجود دارد و همچنین کلید اصلی جدول، OrderID است.

پس از اجرای این کوئری، به کوئری پلن زیر خواهیم رسید:


که بیانگر یک Index Scan است و نکته‌ی جالب آن، استفاده‌ی از ایندکس FK_Sales_Orders_CustomerID می‌باشد (نام این شیء، ذیل آیکن عملگر، مشخص است). یعنی SQL Server در اینجا از یک non-clustered index تعریف شده‌ی بر روی CustomerID استفاده کرده‌است.
اکنون اگر OrderID را تغییر دهیم چه اتفاقی رخ می‌دهد؟
SELECT
    [CustomerID],
    [OrderID]
FROM [Sales].[Orders]
WHERE [OrderID] > 60000;
اینبار به یک clustered index seek رسیدیم که بر روی کلید اصلی جدول یا همان PK_Sales_Orders که ذیل عملگر مشخص شده، رخ داده‌است:


در این مثال با دو ورودی مختلف، دو کوئری پلن مختلف تولید شده‌است؛ که مرتبط است با میزان اطلاعاتی که قرار است بازگشت داده شود.

اگر این دو کوئری را با هم اجرا کنیم (در طی یک batch)، به پلن مقایسه‌ای زیر خواهیم رسید که در آن هزینه‌ی Index Scan بیشتر است از clustered index seek:


به همراه آمار CPU و I/O ای به صورت زیر که اولی مرتبط است با index scan و دومی با clustered index seek:
(43595 rows affected)
Table 'Orders'. Scan count 1, logical reads 191, physical reads 1, read-ahead reads 182, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
 SQL Server Execution Times:
CPU time = 31 ms,  elapsed time = 754 ms.


(13595 rows affected)
Table 'Orders'. Scan count 1, logical reads 131, physical reads 0, read-ahead reads 127, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
 SQL Server Execution Times:
CPU time = 16 ms,  elapsed time = 276 ms.
به همین جهت است که عنوان می‌شود، scanها خوب نیستند و seekها بهترند.
مطالب
انتشار عمومی localhost به صورت HTTPS توسط ngrok
شاید برای شما هم پیش آمده باشد که با Webhookها کار کنید و یا در حین اجرای پروژه‌ی وب خودتان بخواهید خروجی آن را به اطرافیان خود نشان دهید. یکی از راه‌ها این است  که پروژه را بر روی یک مخزن Git ارسال کنید و سپس دوستان خودتان را اضافه کنید تا بتوانند پروژه را دریافت و اجرا کنند. البته در این حالت شاید نخواهید کسی سورس شما را ببیند! روش دیگر این است که هاست و دامین بخرید و پروژه را بر روی آن آپلود کنید و در مرحله‌ی آخر، آدرس وبسایت را برای اطرافیان خود بفرستید که این راه، هم  پرهزینه هست و هم ممکن است پروژه کامل نشده باشد که بخواهید آن‌را آپلود کنید. اینجاست که Ngrok وارد شده و پروژه‌ی لوکال شما را بر روی Https ارائه میکند.


شروع به کار با ngrok 

- قبل از ادامه‌ی این آموزش، وارد سایت https://ngrok.com شده و بعد از ثبت نام، مطابق سیستم عاملی که دارید، فایل مخصوص آن را دریافت کنید.
- بعد از دریافت، فایل زیپ را از حالت فشرده خارج کنید. به محل فایل استخراج شده رفته و در یک مکان خالی در پوشه‌ی جاری، دکمه‌ی Shift را فشرده و سپس کلیک راست کنید و در آخر گزینه‌ی Open Command Window Here را انتخاب نمائید و یا کلا از طریق cmd وارد پوشه‌ی مربوطه شوید.
- سپس پروژه‌ی خود را توسط ویژوال استودیو (IIS Express) و یا بر روی IIS لوکال خود، اجرا کنید.

- همانطور که در تصویر مشاهده می‌کنید؛ آدرس پروژه‌ی لوکال ما به شکل زیر می‌باشد: 
 http://localhost:51095/Home/Index
توجه! این پروژه‌ی آزمایشی صرفا یک صفحه‌ی HTML خیلی ساده بوده که فقط عبارت ngrok را نشان می‌دهد.

- حال به cmd برمی‌گردیم و سپس با دستور زیر میتوانیم لوکال‌هاست خود را بر روی https به اشتراک بگذاریم:
 ngrok http [port] -host-header="localhost:[port]"
اگر کار را به درستی انجام داده باشید، خروجی شبیه به تصویر زیر را خواهید داشت:


همانطور که ملاحظه می‌فرمائید، هم http و هم https را در اختیار ما قرار می‌دهد. برای مثال اگر نیاز است با webhookهای تلگرام کار کنید، باید از آدرس https آن استفاده کنید.
در اینجا در قسمت  HTTP Requests  می‌توانید درخواست‌هایی را که فرستاده می‌شوند، ببینید و یا میتوانید با رفتن به آدرس زیر، دستورات بالا را در نمایی گرافیکی و بصورت کامل نظاره‌گر باشید:
 http://127.0.0.1:4040

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



 
نظرات مطالب
نحوه ایجاد یک تصویر امنیتی (Captcha) با حروف فارسی در ASP.Net MVC
با تشکر؛ ممکنه دوستان به اروری مانند زیر بربخورن:
 CryptographicException was unhandled: System cannot find the specified file
جهت حل این مشکل باید دسترسی IIS به KeyStore  رو فراهم کنن با دستور زیر :
 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
بازخوردهای دوره
استفاده از StructureMap به عنوان یک IoC Container
سلام
من در یک پروژه که دارای دو قسمت ویندوز سرویس و وب می‌باشد از تزریق وابستگی با  StructureMap  استفاده می‌کنم که دارای دو تعریف از UOW برای این دو هستم می‌خواستم بدونم به چه شکلی آن را تعریف کنم که در هر زمان که لازم بود یک نمونه از آن را با توجه به پروژه ای که هستم (ویندوز سرویس یا وب) داشته باشم و آیا اصولا این کار امکان پذیر است؟
x.For<UnitOfWorkScopeBase<TModelUnitOfWork>>()
                    .HttpContextScoped()
                    .Use<PerThreadUnitOfWorkScope<TModelUnitOfWork>>();
x.For<UnitOfWorkScopeBase<TModelUnitOfWork>>()
                   .HybridHttpOrThreadLocalScoped()
                   .Use<PerThreadUnitOfWorkScope<TUnitOfWork>>();
با تشکر از شما