مطالب
معرفی و راهنمایی جهت انتخاب پلتفرم‌های توسعه‌ی رابط کاربری جدید مایکروسافت
اگر به تکنولوژی‌های شرکت مایکروسافت علاقمند باشید و اخبار آن را دنبال کرده باشید قطعا در جریان هستید که علاوه بر تکنولوژی‌های قدیمی (WPF, UWP, Xamarin) تکنولوژی‌های جدیدی (Project Reunion, Maui, WinUI, Uno, Xaml Island) نیز بصورت همزمان در حال توسعه هستند. اکثر این تکنولوژی‌ها شبیه و نزدیک به هم هستند و برای کسی که تازه کار باشد ممکن است دچار سردرگمی شود و چون بصورت همزمان در حال توسعه می‌باشند ممکن سوالاتی برای شما پیش بیاید. در این مطلب هر کدام از این تکنولوژی‌ها را معرفی کرده و در انتخاب صحیح به شما کمک خواهیم کرد.

ساخت برنامه با WPF
به کمک تکنولوژی WPF میتوانیم نرم افزارهای دسکتاپ را توسعه دهیم. WPF همچنان پشتیبانی میشود و در سال‌های اخیر بصورت متن باز نیز منتشر شده‌است. اگر نیاز دارید که برنامه شما در ویندوز‌های 7 تا ویندوز 11 اجرا شود، میتوانید از WPF استفاده کنید. لازم به ذکر است که برنامه‌های WPF به عنوان Win32 یا Desktop نیز شناخته میشوند.

ساخت برنامه با UWP
UWP بعد از WPF و با انتشار ویندوز 10 معرفی شد. علت انتشار، هماهنگی برنامه‌ها با سیستم عامل ویندوز 10 و امنیت بیشتر بود. به‌طور فنی برنامه‌ای که بصورت UWP ساخته میشود، همان WPF است؛ با این تفاوت که داخل SandBox اجرا میشود و با محیط خارج ارتباطی ندارد. بدلیل مسائل امنیتی، بسیاری از کارهای ساده و مهم در UWP غیرممکن (البته راه حل‌هایی نیز وجود دارد) می‌باشد و نیاز به دسترسی کاربر دارد. به عنوان مثال، API‌های system.Io.File یا Process قابل استفاده نمی‌باشند.
نرم افزارهایی که با UWP ساخته میشوند فقط بر روی ویندوز 10 به بالا قابلیت اجرایی دارند و توزیع آن از طریق استور مایکروسافت امکان پذیر است. در صورت نیاز به توزیع دستی (فایل نصبی)، توسعه دهنده باید فایل نصبی را بصورت دیجیتال، امضاء کند که دردسر‌های خودش را دارد.

ساخت برنامه با Xamarin
اگر نیاز دارید که برای سیستم عامل اندروید و مک برنامه بنویسید، زامارین میتواند به شما کمک کند.

ساخت برنامه با WinUI
بعد از معرفی UWP نیاز به یک فریمورک رابط کاربری قوی جهت جذب کاربران به سمت UWP احساس شد. در نتیجه مایکروسافت فریمورک WinUI را ایجاد کرد. WinUI در 2 نسخه در حال توسعه می‌باشد:

WinUI 2.X
این نسخه از WinUI فقط قابلیت استفاده در برنامه‌های مبتنی بر UWP را دارد. اخیرا نسخه 2.6 آن منتشر شده که شامل تغییرات بصری عظیمی می‌باشد. لازم به ذکر است که ویندوز 11 که اخیرا معرفی شد، بر پایه WinUI 2.6 ایجاد شده است.

WinUI 3.X
این نسخه از WinUI قابلیت استفاده در پلتفرم‌های دیگر را محیا می‌کند و هم اکنون بصورت پیش نمایش است و بر پایه WinUI 2.5 می‌باشد. در 3 ماهه آخر سال 2021 تمامی استایل‌ها بر پایه نسخه 2.6 خواهد بود.
 

پلتفرم Uno
پلتفرم اونو توسط مایکروسافت ایجاد نشده، اما توسط آن پشتیبانی میشود. شما به کمک پلتفرم اونو میتوانید به کمک WinUI 3، برنامه‌های خود را در ویندوز 7 (به کمک موتور رندر Skia ) تا ویندوز 11، لینوکس (به کمک Skia)، مک و حتی موبایل اجرا کنید.

پلتفرم Maui
مائویی در واقع نسل بعدی زامارین می‌باشد و بصورت تک پروژه‌ای ایجاد شده‌است. در زامارین شما برای هر پلتفرم (ویندوز، اندروید، مک و...) یک پروژه جداگانه داشتید، اما در مائویی فقط یک پروژه واحد وجود دارد. پس اگر نیاز دارید که برای گوشی‌های همراه برنامه نویسی کنید، میتوانید از مائویی استفاده کنید. لازم به ذکر است به کمک مائویی میتوانید برای لینوکس و مک هم برنامه ایجاد کنید. اما بدلیل وجود WinUI در سایر پلتفرم‌ها، بهتر است از مائویی فقط برای ایجاد برنامه‌های موبایل استفاده کنید.
 

پلتفرم Project Reunion
اخیرا نام این پروژه به Windows App SDK تغییر یافته‌است. به کمک این پروژه میتوانید از WinUI 3 در برنامه‌های WPF و سایر تکنولوژی‌های Desktop استفاده کنید و کل برنامه خود را مدرن کنید. لازم به ذکر است که برنامه‌های ساخته شده توسط Reunion فقط در ویندوز 10 به بالا اجرا میشوند. در حال حاضر جهت اجرای برنامه نیاز هست که برنامه بصورت MSIX پکیج بشود. در نسخه 1.0 که تا چند ماه آینده منتشر خواهد شد، نیازی به پکیج کردن نخواهد بود.

پلتفرم Xaml Island
این پلتفرم در واقع پلی است که میتوانید از کنترل‌های UWP یا WinUI در برنامه‌های دسکتاپ (WPF) استفاده کنید. تفاوت این پلتفرم با Reunion در این است که شما فقط میتوانید بخشی از برنامه خود را مدرن کنید و قسمت‌های مدرن شده در ویندوز‌های پایین‌تر از ویندوز 10، کار نخواهند کرد. اما در Reunion تمام بخش‌های برنامه شما مدرن خواهد شد.

سخن آخر اینکه اگر نیاز به برنامه‌های موبایل دارید، بهتر است از مائویی استفاده کنید بدلیل اینکه:
  • نسل بعدی زامارین است
  • از دات نت 6 به بالا استفاده می‌کند
  • روان، سریع و انعطاف پذیر است
  • خطاهای بسیار کمتری دارد
  • مخصوص موبایل طراحی شده است
  • تجربه بیشتری نسبت به سایر پلتفرم‌ها دارد

اگر نیاز به اجرای برنامه بصورت کراس پلتفرم دارید (ویندوز/لینوکس/مک) بهتر است از Uno استفاده کنید بدلیل اینکه:
  • مخصوص کراس پلتفرم طراحی شده
  • از WinUI 3 استفاده می‌کند
  • برای ویندوز 10 به بالا از تکنولوژی UWP و برای ویندوز 7 و لینوکس از Skia استفاده می‌کند

اگر نیاز دارید برنامه شما فقط در ویندوز 10 به بالا اجرا شود بهتر است از Project Reunion استفاده کنید بدلیل اینکه:
  • از WinUI 3 استفاده می‌کند
  • تمام ویژگی‌های UWP را دارد
  • محدودیت‌های UWP را ندارد
  • بصورت Full Trust اجرا میشود
  • پیچیدگی‌های UWP را ندارد
  • از پلتفرم WPF برای اجرا استفاده می‌کند

اگر نیاز دارید که برنامه شما در ویندوز 7 به بالا اجرا شود و در ویندوز 10 ظاهر مدرن‌تری به خود بگیرد بهتر است از Xaml Island استفاده کنید بدلیل اینکه:
  • فقط بخشی از برنامه را مدرن می‌کند
  • قسمت‌های مدرن شده در نسخه‌های قبل ویندوز 10 اجرا نمیشود

مطالب
کار با دیتاتایپ JSON در MySQL - قسمت دوم
توابع ایجاد محتوای JSON
در قسمت قبل برای ذخیره‌سازی محتوای JSON از string literal استفاده کردیم؛ یعنی در واقع همانند یک مقدار رشته‌ای، فیلد JSON را مقداردهی کردیم:
INSERT INTO tableName VALUES (
'{ "name": "User1", "age": 41 }'
);
یک روش دیگر، استفاده از توابع JSON_OBJECT یا JSON_ARRAY میباشد:
INSERT INTO tableName VALUES (
 JSON_ARRAY(
 JSON_OBJECT(
 "id", 1,
 "name", "User1",
 "age", 31,
 "skills", JSON_ARRAY("JS", "DB", "Git"),
 "address", JSON_OBJECT(
"country", "Iran",
"city", "Tehran")
 ),
 JSON_OBJECT(
   "id", 2,
   "name", "User2",
   "age", 31,
   "skills", JSON_ARRAY("C#"),
   "address", JSON_OBJECT(
 "country", "Iran",
 "city", "Sanandaj"
   )
 )
 )
);


در ادامه با یکسری از توابع دیگر کار با آرایه‌ها و بطور کلی با توابعی جهت تغییر محتوای JSON آشنا خواهیم شد.

JSON_ARRAY_APPEND
فرض کنید برای کاربر User2 میخواهیم یک آیتم به پراپرتی skills اضافه کنیم. برای اینکار میتوانیم از تابع JSON_ARRAY_APPEND استفاده کنیم:
UPDATE experiments.tableName 
SET jsonData = JSON_ARRAY_APPEND(jsonData,
              '$[1].skills',
              'JS',
              '$[1].skills',
              'DB',
              '$[1].skills',
              'Kotlin'
            )

-- ["C#", "JS", "DB", "Kotlin"]

JSON_ARRAY_INSERT
این تابع نیز شبیه تابع قبلی است؛ با این تفاوت که به جای append کردن مقداری به آخر لیست، میتوانیم این مقدار جدید را در مکان مورد  نظر اضافه کنیم:
UPDATE experiments.tableName 
SET 
    jsonData = JSON_ARRAY_INSERT(jsonData, '$[1].skills[4]', 'TS')
    
-- ["C#", "JS", "DB", "Kotlin", "TS"]

JSON_INSERT
از این تابع جهت درج یک مقدار جدید به محتوای JSON استفاده میشود. دقت داشته باشید که این تابع مقادیر موجود را overwrite نخواهد کرد و فقط در صورت عدم وجود آن key، مقدار را اضافه میکند:
UPDATE experiments.tableName 
SET 
    jsonData = JSON_INSERT(jsonData,
            '$[1].address.location',
            JSON_OBJECT('phone', 8989898))

JSON_REPLACE
از این تابع جهت جایگزینی مقادیر استفاده خواهد شد. به عنوان مثال میتوانیم محتوای قبلی را اینگونه به روز کنیم:
UPDATE experiments.tableName 
SET 
    jsonData = JSON_REPLACE(jsonData,
            '$[1].address.location.phone',
            12345656)

JSON_REMOVE
از این تابع میتوانیم جهت حذف یک مقدار، یا پراپرتی خاصی استفاده کنیم:
UPDATE experiments.tableName 
SET 
    jsonData = JSON_REMOVE(jsonData, '$[1].address')

JSON_SET
توسط این تابع میتوانیم دیتایی را به محتوای JSON، اضافه یا به‌روزرسانی کنیم. این تابع همانند JSON_INSERT عمل میکند؛ با این تفاوت که در صورت وجود path، مقدار را overwrite خواهد کرد، در غیراینصورت مقدار جدید را اضافه می‌کند:
UPDATE experiments.tableName 
SET 
    jsonData = JSON_SET(jsonData,
              '$[1].address',
              JSON_OBJECT('country',
                      'Iran',
                      'city',
                      '-',
                      'phone',
                      12345
              ));

/*
  { location: { "city": "-", "phone": 12345, "country": "Iran" } }
*/

UPDATE experiments.tableName 
SET 
    jsonData = JSON_SET(jsonData,
            '$[1].address.city',
            'Tehran');

/*
  { location: { "city": "-", "phone": 12345, "country": "Iran" } }
*/


UPDATE experiments.tableName 
SET jsonData = JSON_SET(jsonData, '$[1].address.postcode', '0098');

/*
  { location: {"city": "Tehran", "phone": 12345, "country": "Iran", "postcode": '0098' } }
*/


JSON_UNQUOTE
توسط این تابع میتوانیم خروجی را به صورت unquote شده ببینیم. بدون استفاده از این تابع، خروجی داخل quotation میباشد:
SELECT 
    JSON_EXTRACT(jsonData, '$[1].address.city')
FROM
    experiments.tableName;
    
-- "Tehran"

SELECT 
    JSON_UNQUOTE(JSON_EXTRACT(jsonData, '$[1].address.city'))
FROM
    experiments.tableName;
    
-- Tehran

همانطور که مشاهده میکنید از تابع JSON_EXTRACT برای کوئری گرفتن از پراپرتی city استفاده کرده‌ایم. خروجی تابع را نیز به JSON_UNQUOTE جهت حذف quotation ارسال کرده‌ایم. یک سینتکس دیگر نیز برای خلاصه‌سازی JSON_EXTRACT وجود دارد: 
SELECT 
    jsonData -> '$[1].address.city'
FROM
    experiments.tableName;

-- "Tehran"

همچنین برای حذف quoteها میتوانیم اپراتور فوق را اینگونه بنویسیم که همان کار تابع JSON_UNQUOTE را انجام میدهد: 
SELECT 
    jsonData ->> '$[1].address.city'
FROM
    experiments.tableName;

-- Tehran

نکته: هر دو حالت را میتوانیم در قسمت WHERE نیز استفاده کنیم: 
SELECT 
    jsonData ->> '$[1].address.city'
FROM
    experiments.tableName
WHERE jsonData ->> '$[1].address.city' = 'Tehran';

ادغام محتوای JSON با یکدیگر
در MySQL دو تابع با نامهای JSON_MERGE_PATCH و JSON_MERGE_PRESERVE برای ادغام دو یا چند محتوای JSON وجود دارد. تابع JSON_MERGE_PRESERVE همانطور که از نامش پیداست، مقادیر را نگه میدارد؛ یعنی کلیدهای یکسان را با هم ادغام میکند و مقادیر را به صورت آرایه به عنوان valueی شیء در نظر میگیرد:
SELECT 
    JSON_MERGE_PRESERVE('{
                "id": "1",
                "name": "Product One",
                "price": 12.45,
                "discount": 10,
                "rating": 4,
                "category": ["fashion", "men"],
                "tags": ["fashion", "men", "jacket", "full sleeve"]
            }',
            '{
                "id": "2",
                "name": "Product Two",
                "price": 30,
                "discount": 0,
                "rating": 3,
                "category": ["fashion", "men"],
                "tags": ["fashion", "men", "jacket", "full sleeve"]
            }');

خروجی کوئری فوق به اینصورت خواهد بود:
{
  "id": ["1", "2"],
  "name": ["Product One", "Product Two"],
  "tags": [
    "fashion",
    "men",
    "jacket",
    "full sleeve",
    "fashion",
    "men",
    "jacket",
    "full sleeve"
  ],
  "price": [12.45, 30],
  "rating": [4, 3],
  "category": ["fashion", "men", "fashion", "men"],
  "discount": [10, 0]
}

اما تابع JSON_MERGE_PATCH در نهایت یک خروجی را خواهد داشت؛ کاری که انجام میدهد به‌روزرسانی (patch) مقدار جدید، با مقدار قبلی است. یعنی کلیدهای آبجکت اول را که در آبجکت دوم قرار دارند، حذف میکند. همچنین کلیدهای جدید را در شیء یکی شده‌ی نهایی نیز اضافه خواهد کرد. به عنوان مثال برای آبجکت اول، یک پراپرتی جدید را با نام sku اضافه کرده‌ایم:
SELECT JSON_MERGE_PATCH('{
                "id": "1",
                "name": "Product One",
                "price": 12.45,
                "discount": 10,
                "rating": 4,
                "category": ["fashion", "men"],
                "tags": ["fashion", "men", "jacket", "full sleeve"],
                "sku": "asdf123"
            }',
            '{
                "id": "2",
                "name": "Product Two",
                "price": 30,
                "discount": 0,
                "rating": 3,
                "category": ["fashion", "men"],
                "tags": ["fashion", "men", "jacket", "full sleeve"]
            }');

خروجی کوئری فوق این چنین خواهد بود:
{
  "id": "2",
  "sku": "asdf123",
  "name": "Product Two",
  "tags": ["fashion", "men", "jacket", "full sleeve"],
  "price": 30,
  "rating": 3,
  "category": ["fashion", "men"],
  "discount": 0
}
مطالب
Garbage Collector در #C - قسمت اول

Garbage Collector

Garbage Collection



فرض کنید متغیری را ایجاد کرده و به آن مقدار داده‌‎‎اید:
string message = "Hello World!";

آیا تابحال به این موضوع فکر کرده‌اید که طول عمر متغیر message تا چه زمانی است و چه زمانی باید از بین برود؟
چه زمانی باید توسط کامپایلر ( یا بهتر بگوییم ، Runtime ) طول عمر این متغیر به پایان برسد و از حافظه حذف شود؟

زبان‎‌های برنامه نویسی به دو دسته‌ی Managed و Unmanaged تقسیم میشوند:

  1. Unmanaged: در این دسته از زبان ها، وظیفه‌ی ایجاد اشیاء، تشخیص زمان درست برای از بین بردن و از بین بردن آنها، برعهده‌ی شماست. زبان‌های C و ++C جز این نوع زبان‌ها میباشند.

  2. Managed: در این دسته از زبان‌ها، ایجاد اشیاء مانند قبل بر عهده‌ی شماست، اما وظیفه تشخیص و از بین بردن آنها در زمان درست را Runtime برعهده میگیرد.
    در این نوع زبان‌‎ها ما دغدغه‌ی حذف متغیری را که در چندین خط بالاتر، آن را ایجاد کرده و به آن مقدار داده‎، به چندین متد آن را پاس داده و انواع تغییرات را روی آن انجام داده‎ایم، نداریم. زبان‌های #C و Java جزو این نوع زبان‌ها میباشند.

ایده کلی ایجاد زبان‌های Managed بر این است که شما درگیر مباحث مربوط به Memory Management نشوید و تمرکز اصلیتان روی Business باشد.

اکثر پروژه‌ها به اندازه کافی پیچیدگی‌های Business ای دارند و ترکیب کردن این نوع پیچیدگی‌ها با پیچیدگی‌های Low Level و Technical ای همچون مباحث Memory Management، در اکثر اوقات باعث میشود که نگهداری از پروژه کاری بسیار دشوار شده و به تدریج مانند بسیاری از پروژه‌های دیگر، نام Legacy را با خود به یدک بکشد.

این بدان معنا نیست که پروژه‌هایی که با زبان هایی همچون C و ++C نوشته میشوند، از همان روز اول Legacy بوده و قابل نگهداری نیستند، بلکه بدین معناست که نگهداری کد چنین زبان‌هایی نسبت به زبان‌های Managed، به مراتب مشکل‌تر است و همچنین قابل ذکر است که قابلیت نگهداری یک پروژه به مباحث بسیار زیاد دیگری نیز بستگی دارد.

Legacy Code



نمونه ای از ترکیب این نوع پیچیدگی‌ها را با مثالی بسیار ساده در دو زبان C و #C بررسی میکنیم.

برنامه‌ای داریم که یک متغییر عددی از نوع int را ایجاد میکند. عدد 25 را بعنوان مقدار به آن میدهد و سپس این متغیر به یک متد پاس داده میشود تا مقدارش چاپ شود.

کد این برنامه در زبان C :
#include <stdio.h>
#include <stdlib.h>

void printReport(int* data)
{
    printf("Report: %d", *data);
}

int main(void)
{
    int *myNumber;
    myNumber = (int*)malloc(sizeof(int));
    if (myNumber == 0)
    {
        printf("ERROR: Out of memory");
        return 1;
    }
    
    *myNumber = 25;
    printReport(myNumber);
    
    free(myNumber);
    myNumber = NULL;
    
    return 0;
}

"هدف" و Business اصلی این برنامه، چاپ و یک گزارش ساده بود، اما مسائل بسیار بیشتری در این مثال دخیل شده اند:

1- Allocate و دریافت فضای مورد نیاز برای یک عدد ( int ) از Memory ، توسط تابع malloc
2- Cast کردن مقدار برگشت داده شده ( *void ) به type مدنظرمان یعنی *int
3- نگهداری آدرس متغییر allocate شده داخل یک pointer
4- بررسی موفقیت آمیز بودن عمل allocation روی memory ( اگر حافظه فضای کافی نداشته باشد، عدد 0 بعنوان آدرس و به معنای عدم موفقیت بازگردانده میشود)
5- مقداردهی متغیر allocate شده با عدد 25
6- صدا زدن و پاس دادن pointer متغییر myNumber به تابع printReport
7- خالی کردن مقدار allocate شده متغییر myNumber توسط تابع free در زمانی که دیگر به آن در برنامه نیازی نیست.
8- مقداردهی NULL به myNumber ( جهت جلوگیری از مشکلات Dangling Pointers )


چند مرحله از این مراحل ذکر شده، واقعا نیاز Business ای برنامه ما بود؟ این مثال، بسیار ساده و غیر واقعی بود؛ اما تصور کنید با این روش، یک برنامه بزرگ با Business Rule‌ها و پیچیدگی‌های خودش، چه حجمی از کد و پیچیدگی را خواهد داشت.


کد این برنامه در زبان #C :
using System;

public class Program
{
    public static void Main()
    {
        int myNumber = 25;
        PrintReport(myNumber);
    }
    
    private static void PrintReport(int number)
    {
        Console.WriteLine($"Report: {number}");
    }
}

همانطور که میبینید در اینجا تمرکزمان روی هدف اصلی و Business است و درگیر پیچیدگی مباحث جانبی نظیر Manual Memory Management نشده‌ایم و Runtime زبان #C یعنی CoreCLR وظیفه Memory Management را در پشت صحنه برعهده گرفته است.



تفاوت بین زبان‌های Managed و Unmanaged را میتوانیم به رانندگی با ماشین دنده‌ای و اتومات تشبیه کنیم.

اکثر اوقات هدف اصلی رانندگی، رفتن از یک مبدا به یک مقصد است. با استفاده از یک ماشین دنده‌ای، علاوه بر هدف اصلی یعنی رسیدن به یک مقصد، ذهن ما درگیر تعویض دنده در سرعت‎ مناسب در طول مسیر میشود و اینکار را ممکن است بیش از صدها یا هزاران بار انجام دهیم. در این روش طبیعتا ما کنترل بیشتری داریم و در بعضی مواقع بسیار بهتر به نسبت یک سیستم خودکار عمل میکنیم، اما از هدف اصلی خود یعنی رفتن از نقطه A به B دور شده‌ایم.

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

در زبان‎های Managed و Unmanaged هم دقیقا چنین تفاوت هایی وجود دارد.


در زبان‏‎های Unmanaged، شما کنترل کاملی را روی طول عمر اشیا و مدیریت حافظه دارید و همه چیز برعهده شماست؛ اما علاوه بر هدف اصلیتان، درگیر مباحث جانبی دیگری نیز شده‌اید. اکثر اوقات قدرت زبان‏‎های Unmanaged را در Game Engine‌ها و Real-Time Processing System‌ها میتوانید ببینید که در آنها مدیریت حافظه بصورت دستی انجام میشود و برنامه نویس‎های آن سیستم، تعیین کننده اصلی این هستند که طول عمر اشیاء تا چه زمانی باشد و چه زمانی از بین بروند که باعث اختلال یا کندی یک سیستم حتی برای چند میلی ثانیه نشوند.

در زبان‌های Managed، اکثر اوقات حتی نیازی نیست که شما درگیر مباحث جانبی مدیریت حافظه شوید و تمام کار را Runtime شما بصورت خودکار انجام میدهد. اما گاهی اوقات لازم است که قسمت‌های حساس برنامه ( اصطلاحا Hot-Path‌ها ) خود را پیدا کنید، از این قسمت‌ها Benchmark گرفته و مطمئن شوید که با حجم تعداد بالای درخواست و بار، به خوبی عمل میکند و همچنین قسمتی از برنامه، نشستی حافظه ( اصطلاحا Memory Leak ) ندارد. همانطور که گفتیم، گاهی اوقات یک انسان ( سیستم دستی ) بهتر از یک سیستم خودکار میتواند تصمیم بگیرد که در یک لحظه چه اتفاقی داخل برنامه رخ دهد.


در این سری مقالات قصد داریم وارد مبحث Memory Management در #C شده، با Garbage Collector آشنا و دیدی کلی از نحوه‌ی انجام کار آن داشته باشیم.

مطالب
C# 8.0 - Default implementations in interfaces
اگر مطلب «تفاوت بین Interface و کلاس Abstract در چیست؟» را مطالعه کرده باشید، به این نتیجه می‌رسید که طراحی یک کتابخانه‌ی عمومی با اینترفیس‌ها، بسیار شکننده‌است. اگر عضو جدیدی را به یک اینترفیس عمومی اضافه کنیم، تمام پیاده سازی کننده‌های آن‌را از درجه‌ی اعتبار ساقط می‌کند و آن‌ها نیز باید این عضو را حتما پیاده سازی کنند تا برنامه‌ای که پیش از این به خوبی کار می‌کرده، باز هم بدون مشکل کامپایل شده و کار کند. هدف از ویژگی جدید «پیاده سازی‌های پیش‌فرض در اینترفیس‌ها» در C# 8.0، پایان دادن به این مشکل مهم است. با استفاده از این ویژگی جدید، می‌توان یک عضو جدید را با پیاده سازی پیش‌فرضی داخل خود اینترفیس قرار داد. به این ترتیب تمام برنامه‌هایی که از کتابخانه‌های عمومی شما استفاده می‌کنند، با به روز رسانی آن، به یکباره از کار نخواهند افتاد.
همچنین مزیت دیگر آن، انتقال ساده‌تر کدهای جاوا به سی‌شارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سال‌ها است که وجود دارد.


یک مثال از ویژگی «پیاده سازی‌های پیش‌فرض در اینترفیس‌ها»

interface ILogger
{
    void Log(string message);
}

class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}
فرض کنید کتابخانه‌ی شما، اینترفیس ILogger را ارائه داده‌است و در برنامه‌ای دیگر، استفاده کننده، کلاس ConsoleLogger را بر مبنای آن پیاده سازی و استفاده کرده‌است.
مدتی بعد بر اساس نیازمندی‌های مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامه‌ی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامه‌ی خود نخواهد شد. اکنون در C# 8.0 می‌توان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیش‌فرض را نیز قرار داد:
interface ILogger
{
    void Log(string message);
    void Log(Exception exception) => Console.WriteLine(exception);
}
به این ترتیب استفاده کنندگان از این اینترفیس، برای کامپایل برنامه‌ی خود به مشکلی برنخواهند خورد و اگر از این overload جدید استفاده کنند، از همان پیاده سازی پیش‌فرض آن بهره خواهند برد. بدیهی است هنوز هم پیاده سازی کننده‌های اینترفیس ILogger می‌توانند پیاده سازی‌های سفارشی خودشان را در مورد این overload جدید ارائه دهند. در این حالت از پیاده سازی پیش‌فرض صرفنظر خواهد شد.


ویژگی «پیاده سازی‌های پیش‌فرض در اینترفیس‌ها» چگونه پیاده سازی شده‌است؟

واقعیت این است که امکان پیاده سازی این ویژگی، سال‌ها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شده‌است.


تاثیر زمینه‌ی کاری بر روی دسترسی به پیاده سازی‌های پیش‌فرض

مثال زیر را درنظر بگیرید:
    interface IDeveloper
    {
        void LearnNewLanguage(string language, DateTime dueDate);

        void LearnNewLanguage(string language)
        {
            // default implementation
            LearnNewLanguage(language, DateTime.Now.AddMonths(6));
        }
    }

    class BackendDev : IDeveloper // compiles OK
    {
        public void LearnNewLanguage(string language, DateTime dueDate)
        {
            // Learning new language...
        }
    }
در اینجا اینترفیس IDeveloper، به همراه یک پیاده سازی پیش‌فرض است و بر این اساس، کلاس BackendDev پیاده سازی کننده‌ی آن، دیگر نیازی به پیاده سازی اجباری متد LearnNewLanguage ای که تنها یک رشته را می‌پذیرد، ندارد.
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل می‌شود و کدامیک خیر؟
IDeveloper dev1 = new BackendDev();
dev1.LearnNewLanguage("Rust");

var dev2 = new BackendDev();
dev2.LearnNewLanguage("Rust");
پاسخ: فقط مورد اول. مورد دوم با خطای کامپایلر زیر مواجه خواهد شد:
 There is no argument given that corresponds to the required formal parameter 'dueDate' of 'BackendDev.LearnNewLanguage(string, DateTime)' (CS7036) [ConsoleApp]
به این معنا که اگر کلاس BackendDev را به خودی خود (دقیقا از نوع BackendDev) و بدون معرفی آن از نوع اینترفیس IDeveloper، بکار بگیریم، فقط همان متدهایی که داخل این کلاس تعریف شده‌اند، قابل دسترسی می‌باشند و نه متدهای پیش‌فرض تعریف شده‌ی در اینترفیس مشتق شده‌ی از آن.


ارث‌بری چندگانه چطور؟

احتمالا حدس زده‌اید که این قابلیت ممکن است ارث‌بری چندگانه را که در سی‌شارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر می‌تواند مشتق شود؛ اما این محدودیت در مورد اینترفیس‌ها وجود ندارد. به علاوه تاکنون اینترفیس‌ها مانند کلاس‌ها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح می‌شود که آیا می‌توان با ارائه‌ی پیاده سازی پیش‌فرض متدها در اینترفیس‌ها، ارث‌بری چندگانه را در سی‌شارپ پیاده سازی کرد؛ مانند مثال زیر؟!
using System;

namespace ConsoleApp
{
    public interface IDev
    {
        void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a default way.");
    }

    public interface IBackendDev : IDev
    {
        void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a backend way.");
    }

    public interface IFrontendDev : IDev
    {
        void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a frontend way.");
    }

    public interface IFullStackDev : IBackendDev, IFrontendDev { }

    public class Dev : IFullStackDev { }
}
سؤال: کد فوق بدون مشکل کامپایل می‌شود. اما در فراخوانی زیر، دقیقا از کدام متد LearnNewLanguage استفاده خواهد شد؟ آیا پیاده سازی آن از IBackendDev فراهم می‌شود و یا از IFrontendDev؟
IFullStackDev dev = new Dev();
dev.LearnNewLanguage("TypeScript");
پاسخ: هیچکدام! برنامه با خطای زیر کامپایل نخواهد شد:
The call is ambiguous between the following methods or properties: 'IBackendDev.LearnNewLanguage(string)' and 'IFrontendDev.LearnNewLanguage(string)' (CS0121)
کامپایلر سی‌شارپ در این مورد خاص از قانونی به نام «the most specific override rule» استفاده می‌کند. یعنی اگر برای مثال در IFullStackDev متد LearnNewLanguage به صورت صریحی بازنویسی و تامین شد، آنگاه امکان استفاده‌ی از آن وجود خواهد داشت. یا حتی می‌توان این پیاده سازی را در کلاس Dev نیز ارائه داد و از نوع آن (بجای نوع اینترفیس) استفاده کرد.


تفاوت امکانات کلاس‌های Abstract با متدهای پیش‌فرض اینترفیس‌ها چیست؟

اینترفیس‌ها هنوز نمی‌توانند مانند کلاس‌ها، سازنده‌ای را تعریف کنند. نمی‌توانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیس‌ها همه‌چیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیس‌ها، ارائه‌ی «یک رفتار» است و هدف از تعریف کلاس‌ها، ارائه «یک حالت».


یک نکته: در نگارش‌های پیش از C# 8.0 هم می‌توان ویژگی «متدهای پیش‌فرض» را شبیه سازی کرد

واقعیت این است که توسط ویژگی «متدهای الحاقی»، سال‌ها است که امکان افزودن «متدهای پیش‌فرضی» به اینترفیس‌ها در زبان سی‌شارپ وجود دارد:
namespace MyNamespace
{
    public interface IMyInterface
    {
        IList<int> Values { get; set; }
    }

    public static class MyInterfaceExtensions
    {
        public static int CountGreaterThan(this IMyInterface myInterface, int threshold)
        {
            return myInterface.Values?.Where(p => p > threshold).Count() ?? 0;
        }
    }
}
و در این حالت هرچند به نظر اینترفیس IMyInterface دارای متدی نیست، اما فراخوانی زیر مجاز است:
var myImplementation = new MyInterfaceImplementation();
// Note that there's no typecast to IMyInterface required
var countGreaterThanFive = myImplementation.CountGreaterThan(5);
مطالب
Pipeها در Angular 2 – قسمت سوم – Pipeهای Pure و Impure
 در قسمت قبل بیان شد که Angular برای اعمال Pipe بر روی Template expressions باید تمامی رخدادهای برنامه را تحت نظر قرار داده و با مشاهده‌ی هر تغییری بر روی عبارت ورودی Pipe، فراخوانی Pipe را آغاز کند. از جمله این رخدادها می‌توان به رخدادهای mouse move، timer tick، server response و فشرده شدن کلیدهای ماوس و یا کیبورد اشاره کرد. واضح است که بررسی تغییرات عبارت در این همه رخداد می‌تواند مخرب باشد و بر روی کارآئی (Performance) تاثیر منفی خواهد گذاشت. اما Angular برای حل این مشکل و همچنین هنگام مشاهده سریع تغییرات هنگام استفاده از Pipeها، الگوریتم‌های سریع و ساده‌ای در نظر گرفته است که آن‌ها را در این بخش مورد برسی قرار خواهیم داد.


Pipeهای Pure و Impure

Pipeها کلا در دو دسته‌ی Pure و Impure قرار می‌گیرند. هنگام ساخت Pipe سفارشی در صورتیکه نوع Pipe مشخص نشود، به صورت پیش فرض از نوع Pure خواهد بود. برای تعریف Pipeهایی از نوع Impure کافی است در متادیتای Pipe@، پرچم Pure را به مقدار false تنظیم کنید.
@Pipe({ name: 'impurePipe', pure: false })
تفاوت این Pipeها در زمان فراخوانی دوباره آنها است.


Pure Pipe

این نوع Pipeها تنها زمانی فراخوانی مجدد می‌شوند که یک تغییر محض (Pure Change) بر روی عبارت ورودی آنها رخ دهد. هر نوع تغییری بر روی عبارات ورودی از جنس string ، number ، Boolean ، Symbol و عبارات اولیه، یا هرنوع تغییری در ارجاع یک شیء مانند  Date ، Array ، Function و Object نیز تغییر محض محسوب می‌شود. به عنوان مثال هیچکدام از تغییرات زیر یک تغییر محض محسوب نمی‌شوند:
numbers.push(10);
obj.name = ‘javad’;
زیرا با اضافه شدن عنصری به یک آرایه یا تغییر خصوصیتی از یک شیء، باعث تغییری در ارجاع آنها نمی‌شود و همانطور که اشاره شد، در عبارات از نوع آرایه و Object، فقط تغییر در ارجاع آن‌ها یک تغییر محض محسوب می‌شود.
حالا می‌توان به این نتیجه رسید که اضافه شدن مقداری به آرایه یا به‌روزرسانی یک property از object، باعث فراخوانی مجدد Pure Pipe نخواهد نشد. شاید این نوع از Pipeها محدود کننده باشند، اما بسیار سریع هستند (برسی تغییر در ارجاع یک شیء بسیار سریعتر از بررسی کامل یک شیء، صورت می‌گیرد).


Impure Pipe

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

مثال: قصد داریم Pipe سفارشی را پیاده سازی کنیم تا آرایه‌ای از اعداد را دریافت و فقط اعداد زوج را فیلتر کرده و نمایش دهد.
برای این منظور یک فایل جدید را با نام even-numbers.pipe.ts با محتویات زیر ایجاد می‌کنیم: 
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'evenNumbers'
})
export class EvenNumbersPipe implements PipeTransform {
  transform(numbers: Array<number>): Array<number> {
    var x=numbers.filter(r => r % 2 == 0);
    return x;
  }
}
همانطور که مشخص است این Pipe در متد transform، آرایه‌ای از اعداد را دریافت کرده و فقط اعداد زوج را بازگشت می‌دهد. حالا باید Pipe تعریف شده خود را در AppModule در قسمت declares تعریف کنیم.
// . . .
import { EvenNumbersPipe } from './pipes/even-numbers.pipe'
@NgModule({
  declarations: [
    . . .
    EvenNumbersPipe
  ],
 . . .
})
export class AppModule { }

سپس در کامپوننت مورد نظر خود متغیری را به نام numbers از نوع آرایه، با مقدار اولیه‌ی اعداد از یک تا ده، تعریف می‌کنیم:
numbers: Array<number> = [1,2,3,4,5,6,7,8,9,10];
برای نمایش این اعداد در رابط کاربری تگ‌های زیر را به قالب کامپوننت خود اضافه می‌کنیم:
<h1>All numbers</h1>
<span *ngFor="let number of numbers">
  {{number}}
</span>
همچنین با استفاده از تگ‌های زیر یک input برای اضافه کردن مقدار جدید به آرایه درنظر می‌گیریم:
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers.push(number.value)" value="Add number"/>
</p>

تگ‌های زیر را نیز برای اعمال Pipe نمایش اعداد زوج، به قالب کامپوننت اضافه می‌کنیم:
<h1>even numbers</h1>
<span *ngFor="let number of numbers | evenNumbers">
  {{number}}
</span>
بعد از اجرای برنامه، یک عدد جدید زوج را به آرایه اضافه کنید. متوجه خواهید شد با اینکه لیست اعداد در قسمت All numbers به‌روز می‌شوند، ولی Pipe، متوجه تغییری بر روی آرایه نشده‌است و همچنان اعداد قبلی را نمایش می‌دهد. دلیل این امر همانطور که قبلا نیز اشاره شد، بخاطر Pure بودن Pipe و عدم فراخوانی مجدد این نوع Pipe‌ها در زمان اضافه شدن مقداری به آرایه یا تغییری در خصوصیت یک شیء است.

برای حل این مشکل، هنگام اضافه شدن عدد به آرایه، اگر ارجاع آرایه را تغییر دهیم، Pure Pipe متوجه تغییرات خواهد شد و لیست اعداد را به‌روز رسانی می‌کند (تغییر در ارجاع یک شیء، از نوع تغییرات محض است):
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers = numbers.concat(number.value)" value="Add number"/>
</p>
با تغییر نحوه اضافه شدن عنصر به آرایه به شکل بالا خواهیم دید که با افزودن اعداد جدید، لیست اعداد زوج نیز در لحظه اعمال خواهند شد. این راه‌حل همیشه کارآمد نخواهد بود. همیشه تشخیص محل اضافه شدن عنصر به آرایه در برنامه کار ساده‌ای نیست تا در آنجا ارجاع آرایه را نیز تغییر دهیم. راه‌حل، استفاده از Impure Pipe است. کافی است متادیتای Pipe@ را هنگام تعریف به شکل زیر تغییر دهید:
@Pipe({
  name: 'evenNumbers',
  pure: false
})
export class EvenNumbersPipe implements PipeTransform {
   //…
}

کسانیکه با Angular 1.x آشنایی دارند، شاید اکنون متوجه این شده‌اند که چرا در Angular به مشابه Angular 1.x دیگر خبری ازfilter و orderBy نیست. با توجه به اینکه این دو فیلتر فقط با عبارات از نوع object سروکار داشتند، پیاده‌سازی آنها فقط با Impure Pipeها امکان پذیر بود و با توجه به اینکه Impure Pipeها در هر بار چرخه تغییرات کامپوننت اجرا خواهند شد، باعث کندی در صفحات خواهند شد. 
مطالب
الگوریتم‌های داده کاوی در SQL Server Data Tools یا SSDT - قسمت سوم - الگوریتم‌های Decision trees و Linear Regression

در قسمت قبل با الگوریتم Naive Bayes به عنوان الگوریتمی جهت شروع امر داده کاوی آشنا شدیم. در این قسمت به الگوریتم‌های Decision trees و Linear Regression می‌پردازیم.


مقدمه 

خودتان را جای یک متصدی اعطای وام بانکی درنظر بگیرید. یک زوج جوان برای دریافت وام به بانک مراجعه می‌کنند. برای اعطای وام، ممکن است جوان بودن آن‌ها یک علامت مثبت نباشد. حال شما شروع به مصاحبه با آن‌ها می‌کنید و متوجه می‌شوید که ازدواج کرده‌اند. متاهل بودن آن‌ها یک نکته مثبت است. همچنین متوجه می‌شوید که هر دو یک شغل دارند و به مدت سه سال است که مشغول همان کار هستند. درست حدس زدید، پایداری شغل می‌تواند یک نکته مثبت باشد. پس از بررسی حساب بانکی آن‌ها متوجه می‌شوید که در یکسال اخیر سه چک برگشتی دارند. این موضوع، یک منفی بزرگ را سر راه قرار می‌دهد. درنهایت، شما جهت تصمیم گیری برای اعطای وام، براساس تجربه کاری خود در ذهنتان یک درخت ایجاد می‌کنید که رتبه بندی امتیاز برای اعطای وام را تسهیل می‌کند. کاری که الگوریتم Decision Trees انجام می‌دهد شبیه به همین کار است.


چرا الگوریتم درخت تصمیم؟

این الگوریتم به دلایل سرعت و کارآیی بالا در آماده سازی داده‌ها و دقت بالا و درک راحت الگو توسط انسان، محبوب‌ترین تکنیک داده کاوی است. رایج‌ترین کاری که معمولا با استفاده از این الگوریتم انجام می‌گردد دسته بندی داده‌ها است. برای مثال متقاضی وام می‌تواند به دو دسته با درجه ریسک پایین و درجه ریسک بالا تقسیم شود و این الگوریتم به ما کمک می‌کند تا قاعده‌ای برای انجام این دسته بندی بر اساس داده‌های قبلی پیدا نماییم.


تفسیر الگوریتم

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


شرح نوار ابزار


  • کمبوی مربوط به ،Tree شامل درخت‌های تصمیم مربوط به خروجی‌ها (ویژگی‌هایی که می‌خواهیم پیش بینی کنیم) می‌باشد.
  • Default Expansion اندازه درخت را مشخص می‌کند. به عبارتی مشخص می‌کند که درخت چند سطحی باشد.
  • هیستوگرام تعداد حالات ویژگی قابل پیش بینی را مشخص می‌کند که از طریق آن می‌توان در یک نگاه با توجه به رنگ حالت مورد نظر در هر نود، یک مسیر مشخص را در درخت طی کرد. برای مثال فرض کنید که یک ویژگی دارای 10 حالت باشد که برای شما 5 حالت از این 10 حالت مهمتر است. بنابراین تعداد را روی 5 تنظیم می‌کنیم. مابقی حالات در یک گروه قرار گرفته به رنگ خاکستری نشان داده می‌شوند.
  • کمبوی Background جهت کنترل رنگ پیش زمینه نود‌ها می‌باشد. در حالت پیش فرض، این کمبو تمامی حالات ویژگی مورد پیش بینی را در نظر می‌گیرد. در این حالت رنگ تیره‌تر نود نشان دهنده تعداد موردها در آن نود می‌باشد. هرچه این رنگ تیره‌تر باشد، یعنی موارد بیشتری در آن دسته قرار می‌گیرند. شما همچنین می‌توانید یک حالت خاص از ویژگی مورد پیش بینی را انتخاب کنید. در این حالت رنگ پس زمینه هر نود احتمال پیش بینی با توجه به حالت انتخاب شده را نشان می‌دهد. نود با پس زمینه پر رنگ‌تر احتمال بالاتری با توجه به حالت انتخاب شده دارد. 


آموزش بیش از اندازه

این الگوریتم، درخت را به صورت بازگشتی رشد می‌دهد. درنتیجه گاهی اوقات ممکن است که با یک درخت بزرگ مواجه شوید. این درخت می‌تواند شامل سطح‌ها و شاخه‌های زیادی باشد. بنابراین شامل قوانین زیادی هم خواهد بود. اما در نظر داشته باشید که ارتباط مستقیمی بین کیفیت پیش بینی و اندازه درخت وجود ندارد. حقیقت امر این است، هرگاه که درخت بیش از اندازه عمیق شود، بجای اینکه تعمیم قوانین صورت گیرد، آموزش حالات مختلف نشان داده می‌شود و این خوب نیست. الگوریتم درخت تصمیم مایکروسافت ویژگی دارد به نام forward pruning که رشد درخت را با استفاده از امتیاز بایزین کنترل می‌کند. به عبارتی زمانیکه اطلاعات کافی برای بخش کردن یک نود وجود نداشته باشد، از این امر جلوگیری می‌کند. این کار توسط پارامتر Complexity_Penalty انجام می‌گردد که مقداری اعشاری بین 0 و 1 را می‌گیرد. هرچه مقدار بالاتری به این پارامتر اختصاص داده شود، محدودیت بیشتری برای تقسیم درخت درنظر گرفته می‌شود و بنابراین سایز درخت کوچکتر می‌گردد.


پارامترهای الگوریتم درخت تصمیم

دسترسی به این پارامترها از طریق تب mining models  امکان پذیر می‌باشد. با کلیک بر روی الگوریتم پنجره، properties  آن نمایش داده خواهد شد حال می‌توان به بخش Algorithm Parameters  رفت و پارامترها را مقداردهی کرد. 

Complexity_Penalty : که توضیح آن در بخش "آموزش بیش از اندازه" آورده شد.

Minimum_Support : جهت تعیین مینیمم اندازه هر نود به کار می‌رود. برای مثال اگر مقدار 20 را به آن بدهیم، آنگاه هر تقسیم بندی که منجر به تولید نودهای فرزندی با اندازه کمتر از 20 شود، انجام نمی‌گردد. اغلب در مواردی که مجموعه داده دارای حالات گوناگون زیادی است، می‌توان مقدار این متغیر را بالا برد تا از آموزش بیش از اندازه جلوگیری کرد. پیش فرض این پارامتر 10 می‌باشد.

Score_Method : این پارامتر مشخص می‌کند که از کدام روش برای محاسبه امتیاز جهت بخش بندی درخت استفاده کنیم. سه مقدار 1، 3 و 4 را می‌گیرد. 1 از امتیاز انتروپی استفاده می‌کند، 3 از بایزین k2 و 4 از بایزین Dirichlet equivalent .

Split_Method : سه مقدار 1 تا 3 را می‌گیرد. فرض کنید که وضعیت تحصیل در یک مجموعه داده سه حالت را دارد: دیپلم، لیسانس، فوق لیسانس. اگر مقدار 1 را برای این پارامتر تعیین نماییم آنگاه حالت دودویی برای تقسیم نودها درخت درنظر گرفته می‌شود. یعنی دو حالت دیپلم و غیر دیپلم. حال اگر مقدار 2 را نظر بگیریم آنگاه تقسیم نودها براساس تمامی حالات درنظر گرفته می‌شود؛ در اینجا سه تا. مقدار 3 که مقدار پیش فرض نیز می‌باشد، انتخاب حالت 1 یا 2 را به عهده الگوریتم می‌گذارد.

Maximum_Input_Attributes : ماکزیمم ورودی را می‌توان از این طریق تعیین کرد. اگر تعداد ورودی‌ها بیشتر از این مقدار باشد، آنگاه فقط ورودی‌های مهم درنظر گرفته شده و مابقی نادیده گرفته می‌شوند.


Linear Regression:

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

درکل رگرسیون شبیه به دسته بندی است با این تفاوت که رگرسیون می‌تواند ویژگی‌های پیوسته را پیش بینی کند. 
مطالب
نوشتن Middleware سفارشی در ASP.NET Core
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 3 - Middleware چیست؟» با اصول مقدماتی Middlewareها آشنا شدیم. همچنین در مطلب «آشنایی با OWIN و بررسی نقش آن در ASP.NET Core» یک مثال سفارشی از آن‌ها، بررسی شد. در اینجا می‌خواهیم نکات بیشتری را در مورد تهیه‌ی Middlewareهای سفارشی بررسی کنیم.


تفاوت بین متدهای app.Use  و  app.Run در چیست؟

Middlewareها به همان ترتیبی که در متد Configure کلاس آغازین برنامه معرفی می‌شوند، اجرا خواهند شد؛ اما نکته‌ی مهم اینجا است که middleware ایی که توسط متد app.Use تعریف می‌شود، می‌تواند middleware بعدی ثبت شده‌را، فراخوانی کند؛ اما app.Run خیر. برای درک بهتر این مفهوم، به مثال زیر دقت کنید:
using Microsoft.AspNetCore.Http;

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, before next()</div>");
 
            await next();
 
            await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, after next()</div>");
        });
 
        app.Run(async context =>
        {
            await context.Response.WriteAsync("<div>Inside middleware-2 defined using app.Run</div>");
        });
 
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, before next()</div>");
 
            await next();
 
            await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, after next()</div>");
        });
اگر در این حالت برنامه را اجرا کنیم، چنین خروجی را مشاهده خواهیم کرد:


همانطور که در تصویر نیز مشخص است، ابتدا کدهای پیش از فراخوانی دلیگیت next میان‌افزار اول اجرا شده‌است. سپس باتوجه به فراخوانی دلیگیت next، کدهای دومین میان‌افزار ثبت شده، فراخوانی گردیده‌است و سپس کدهای پس از فراخوانی دلیگیت next میان‌افزار اول، اجرا شده‌اند.
این دلیگیت در اصل یک چنین امضایی را دارد:
 public delegate Task RequestDelegate(HttpContext context);
در اینجا چون میان‌افزار دوم از نوع app.Run است و قابلیت فراخوانی دلیگیت next را ندارد، از نوع terminal یا خاتمه دهنده به‌شمار آمده و دیگر میان افزار بعدی ثبت شده، یعنی میان‌افزار سوم، اجرا نخواهد شد و کار پردازش برنامه در همین مرحله به پایان می‌رسد.

باید دقت داشت که فراخوانی دلیگیت next در میان‌افزارهای از نوع app.Use الزامی نبوده و اگر اینکار انجام نشود، بین app.Run و app.Use تفاوتی نخواهد بود و هر دو terminal به حساب می‌آیند.


تفاوت بین متدهایapp.Map  و  app.MapWhen در چیست؟

متد app.Map در صورت برآورده شدن شرطی، سبب اجرای میان‌افزاری مشخص می‌شود (امکان اجرای غیر خطی میان‌افزارها).
فرض کنید قطعه کد زیر را پس از اولین app.Use مثال فوق قرار داده‌ایم:
app.Map("/dnt", appBuilder =>
{
    appBuilder.Run(async context =>
    {
        await context.Response.WriteAsync(@"<div>Inside Map(/dnt) --> Run</div>");
    });
});
در این حالت اگر برنامه را اجرا کنیم، خروجی جدیدی را مشاهده نخواهیم کرد و خروجی حاصل دقیقا مانند تصویر مثال فوق است. اما اگر آدرس ویژه‌ی dnt/ درخواست شود (الگوی تطابق اولین پارامتر متد Map)، آنگاه میان افزار ثبت شده‌ی app.Run ویژه‌ی این حالت خاص، اجرا می‌شود:


در اینجا چون app.Run داخلی فراخوانی شده، از نوع terminal است، دیگر میان افزارهای پس از آن اجرا نشده‌اند. بدیهی است در اینجا نیز می‌توان به هر تعدادی که نیاز است میان افزارهای جدیدی را به appBuilder متد app.Map اضافه کرد.

پارامتر اول متد Map برای تطابق با الگوهایی خاص و مشخص، مناسب است. اما در اگر در اینجا نیاز به اطلاعات بیشتری از HttpContext جاری داشته باشیم، می‌توانیم از متد app.MapWhen استفاده کنیم که اولین پارامتر آن یک دلیگیت است که HttpContext را در اختیار استفاده کننده قرار می‌دهد و اگر در نهایت true را دریافت کند، سبب اجرای میان افزارهای قسمت appBuilder آن خواهد شد:
app.MapWhen(context =>
{
    return context.Request.Query.ContainsKey("dnt");
},
appBuilder =>
{
    appBuilder.Run(async context =>
    {
        await context.Response.WriteAsync(@"<div>Inside MapWhen(?dnt) --> Run</div>");
    });
});
در این مثال، شرط ارائه شده‌ی در پارامتر اول، اندکی پیچید‌ه‌تر است از حالت app.Map. در اینجا مشخص شده‌است که اگر آدرس دریافتی از کاربر، دارای کوئری استرینگی به نام dnt بود، آنگاه میان افزار(های) ارائه شده‌ی در قسمت appBuilder، اجرا شود. برای نمونه درخواست آدرس ذیل، سبب فراخوانی appBuilder.Run ذکر شده می‌شود:
 http://localhost:7742/?dnt=true



نظم بخشیدن به تعاریف میان‌افزارها

متدهای app.Run و app.Use و امثال آن‌ها برای تعریف سریع میان افزارها مناسب هستند. اما اگر بخواهیم کدهای کلاس آغازین برنامه را اندکی خلوت کرده و به تعاریف میان‌افزارها نظم ببخشیم، می‌توان کدهای آن‌ها را به کلاس‌هایی با امضایی خاص منتقل کرد:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public class MyMiddleware1
    {
        private readonly RequestDelegate _next;
 
        public MyMiddleware1(RequestDelegate next)
        {
            _next = next;
        }
 
        public async Task Invoke(HttpContext context)
        {
                    context.Response.ContentType = "text/html";
                    context.Response.StatusCode = 200;
            await context.Response.WriteAsync("<div>Hello from MyMiddleware1.</div>");
            await _next.Invoke(context);
            await context.Response.WriteAsync("<div>End of action.</div>");
        }
    } 
}
در اینجا نحوه‌ی تعریف یک کلاس میان‌افزار سفارشی را مشاهده می‌کنید. دو پارامتر context و next ایی را که در متد app.Use مشاهده کردید، دراینجا به نحو واضح‌تری مشخص شده‌اند. دلیگیت next اشاره کننده‌ی به اجرای میان افزار بعدی، در سازنده‌ی کلاس این میان افزار تزریق شده‌است. همچنین در اینجا می‌توان سرویس‌هایی را که به IoC Container توکار ASP.NET Core معرفی کرده‌ایم نیز تزریق کنیم و از این لحاظ محدودیتی ندارد (و همچنین امضای آن می‌تواند کاملا متغیر باشد). قسمت اصلی اجرایی این میان افزار، متد Invoke آن است که اطلاعات HttpContext جاری را در اختیار مصرف کننده قرار می‌دهد.
در اینجا نیز اگر دلیگیت next_ فراخوانی نشود، این میان‌افزار سبب خاتمه‌ی اجرای پردازش درخواست جاری می‌گردد.

 مرحله‌ی بعد، روش معرفی این میان‌افزار تعریف شده، به لیست میان‌افزارهای موجود است. برای این منظور می‌توان متد app.UseMiddleware را به صورت مستقیم در کلاس آغازین برنامه فراخوانی کرد و یا مرسوم است اگر کتابخانه‌ای را طراحی کرده‌اید، به نحو ذیل متد الحاقی خاصی را برای آن تدارک دید:
using Microsoft.AspNetCore.Builder;

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
    {
        app.UseMiddleware<MyMiddleware1>();
        return app;
    }
}
سپس مصرف کننده تنها باید این متد را در کلاس آغازین برنامه فراخوانی کند:
public void Configure(IApplicationBuilder app)
{
    app.UseMyMiddleware();
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 20 - بررسی تغییرات فیلترها
پیشنیازها

- فیلترها در MVC
- ASP.NET MVC #15


فیلترها در ASP.NET MVC، امکان اجرای کدهایی را پیش و یا پس از مرحله‌ی خاصی از طول اجرای pipeline آن فراهم می‌کنند. کلیات فیلترها در ASP.NET Core با نگارش‌های قبلی ASP.NET MVC (پیشنیازهای فوق) تفاوت چندانی را ندارد و بیشتر تغییراتی مانند نحوه‌ی معرفی سراسری آن‌ها، اکشن فیلترهای Async و یا تزریق وابستگی‌ها در آن‌ها، جدید هستند.


امکان تعریف فیلترهای Async در ASP.NET Core

حالت کلی تعریف یک فیلتر در ASP.NET MVC که در ASP.NET Core نیز همچنان معتبر است، پیاده سازی اینترفیس کلی IActionFilter می‌باشد که توسط آن می‌توان به مراحل پیش و پس از اجرای قطعه‌ای از کدهای برنامه دسترسی پیدا کرد:
namespace FiltersSample.Filters
{
    public class SampleActionFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            // انجام کاری پیش از اجرای اکشن متد
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // انجام کاری پس از اجرای اکشن متد
        }
    }
}
در اینجا اینترفیس IAsyncActionFilter نیز معرفی شده‌است که توسط آن می‌توان فراخوانی‌های غیرهمزمان و async را نیز مدیریت کرد:
namespace FiltersSample.Filters
{
    public class SampleAsyncActionFilter : IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(
            ActionExecutingContext context,
            ActionExecutionDelegate next)
        {
            // انجام کاری پیش از اجرای اکشن متد
            await next();
            // انجام کاری پس از اجرای اکشن متد
        }
    }
}
به کامنت‌های نوشته شده‌ی در بدنه‌ی متد OnActionExecutionAsync دقت کنید. در اینجا کدهای پیش از await next معادل OnActionExecuting و کدهای پس از await next معادل OnActionExecuted حالت همزمان و یا همان حالت متداول هستند. بنابراین جایی که اکشن متد اجرا می‌شود، همان await next است.

یک نکته: توصیه شده‌است که تنها یکی از حالت‌های همزمان و یا غیرهمزمان را پیاده سازی کنید و نه هر دوی آن‌ها را. اگر هر دوی این‌ها را در طی یک کلاس پیاده سازی کنید (تک کلاسی که هر دوی اینترفیس‌های IActionFilter و IAsyncActionFilter را با هم پیاده سازی می‌کند)، تنها نگارش Async آن توسط ASP.NET Core فراخوانی و استفاده خواهد شد. همچنین مهم نیست که اکشن متد شما Async هست یا خیر؛ برای هر دو حالت می‌توان از فیلترهای async نیز استفاده کرد.


ساده سازی تعریف فیلترها

اگر مدتی با ASP.NET MVC کار کرده باشید، می‌دانید که عموما کسی از این اینترفیس‌های کلی برای پیاده سازی فیلترها استفاده نمی‌کند. روش کار با ارث بری از یکی از فیلترهای از پیش تعریف شده‌ی ASP.NET MVC صورت می‌گیرد؛ از این جهت که این فیلترها که در اصل همین اینترفیس‌ها را پیاده سازی کرده‌اند، یک سری جزئیات توکار protected را نیز به همراه دارند که با ارث بری از آن‌ها می‌توان به امکانات بیشتری دسترسی پیدا کرد و کدهای ساده‌تر و کم حجم‌تری را تولید نمود:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute

برای مثال در اینجا فیلتری را مشاهده می‌کنید که با ارث بری از فیلتر توکار ResultFilterAttribute، سعی در تغییر Response برنامه و افزودن هدری به آن کرده‌است:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
    public class AddHeaderAttribute : ResultFilterAttribute
    {
        private readonly string _name;
        private readonly string _value;
        public AddHeaderAttribute(string name, string value)
        {
            _name = name;
            _value = value;
        }

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                _name, new string[] { _value });
            base.OnResultExecuting(context);
        }
    }
}
و برای استفاده‌ی از این فیلتر جدید خواهیم داشت:
[AddHeader("Author", "DNT")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("با فایرباگ هدر خروجی را بررسی کنید");
    }
}


نحوه‌ی تعریف میدان دید فیلترها

نحوه‌ی دید فیلترها در اینجا نیز همانند سابق، سه حالت را می‌تواند داشته باشد:
الف) اعمال شده‌ی به یک اکشن متد.
ب) اعمال شده‌ی به یک کنترلر که به تمام اکشن متدهای آن کنترلر اعمال خواهد شد.
ج) حالت تعریف سراسری و این مورد محل تعریف آن به کلاس آغازین برنامه منتقل شده‌است:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(SampleActionFilter)); // by type
        options.Filters.Add(new SampleGlobalActionFilter()); // an instance
    });
}
در اینجا دو روش معرفی فیلترهای سراسری را در متد ConfigureServices کلاس آغازین برنامه مشاهده می‌کنید:
الف) اگر توسط ارائه‌ی new ClassName معرفی شوند، یعنی وهله سازی را خودتان قرار است مدیریت کنید و در این حالت تزریق وابستگی‌هایی صورت نخواهند گرفت.
ب) اگر توسط typeof معرفی شوند، یعنی این وهله سازی توسط IoC Container توکار ASP.NET Core انجام خواهد شد و طول عمر آن Transient است. یعنی به ازای هربار نیاز به آن، یکبار وهله سازی خواهد شد.


ترتیب اجرای فیلترها

توسط خاصیت Order می‌توان ترتیب اجرای چندین فیلتر اجرا شده‌ی به یک اکشن متد را مشخص کرد. اگر این مقدار منفی وارد شود:
 [MyFilter(Name = "Method Level Attribute", Order=-1)]
این فیلتر پیش از فیلترهای سراسری و همچنین فیلترهای اعمال شده‌ی در سطح کلاس اجرا می‌شود.


تزریق وابستگی‌ها در فیلترها

فیلترهایی که به صورت ویژگی‌ها یا Attributes تعریف می‌شوند و قرار است به کنترلرها و یا اکشن متدها به صورت مستقیم اعمال شوند، نمی‌توانند دارای وابستگی‌های تزریق شده‌ی در سازنده‌ی خود باشند. این محدودیتی است که توسط زبان‌های برنامه نویسی اعمال می‌شود و نه ASP.NET Core. اگر ویژگی قرار است پارامتری در سازنده‌ی خود داشته باشد، هنگام تعریف و اعمال آن، این پارامترها باید مشخص بوده و تعریف شوند. به همین جهت آنچنان با تزریق وابستگی‌های از طریق سازنده‌ی کلاس قابل مدیریت نیستند. برای رفع این نقصیه، راه‌حل‌های متفاوتی در ASP.NET Core پیشنهاد و طراحی شده‌اند:
الف) استفاده‌ی از ServiceFilterAttribute
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
   return View();
}
ویژگی جدید ServiceFilter، نوع کلاس فیلتر را دریافت می‌کند و سپس هر زمانیکه نیاز به اجرای این فیلتر خاص بود، کار وهله سازی‌های وابستگی‌های آن، در پشت صحنه توسط IoC Container توکار ASP.NET Core انجام خواهد شد.
همچنین باید دقت داشت که در این حالت ثبت کلاس فیلتر در متد ConfigureServices کلاس آغازین برنامه الزامی است.
 services.AddScoped<AddHeaderFilterWithDi>();
در غیراینصورت استثنای ذیل را دریافت خواهید کرد:
 System.InvalidOperationException: No service for type 'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.

ب) استفاده از TypeFilterAttribute
[TypeFilter(typeof(AddHeaderAttribute),  Arguments = new object[] { "Author", "DNT" })]
public IActionResult Hi(string name)
{
   return Content($"Hi {name}");
}
فیلتر و ویژگی TypeFilter بسیار شبیه است به عملکرد ServiceFilter، با این تفاوت که:
- نیازی نیست تا وابستگی آن‌را در متد ConfigureServices ثبت کرد (هرچند وابستگی‌های خود را از DI Container دریافت می‌کنند).
- امکان دریافت پارامترهای اضافی سازنده‌ی کلاس مدنظر را نیز دارند.


یک مثال تکمیلی: لاگ کردن تمام استثناءهای مدیریت نشده‌ی یک برنامه‌ی ASP.NET Core 1.0

می‌توان با سفارشی سازی فیلتر توکار ExceptionFilterAttribute، امکان ثبت وقایع را توسط فریم ورک توکار Logging اضافه کرد:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public class CustomExceptionLoggingFilterAttribute : ExceptionFilterAttribute
    {
        private readonly ILogger<CustomExceptionLoggingFilterAttribute> _logger;
        public CustomExceptionLoggingFilterAttribute(ILogger<CustomExceptionLoggingFilterAttribute> logger)
        {
            _logger = logger;
        }
 
        public override void OnException(ExceptionContext context)
        {
            _logger.LogInformation($"OnException: {context.Exception}");
            base.OnException(context);
        }
    }
}
و برای ثبت سراسری آن در کلاس آغازین برنامه خواهیم داشت:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(CustomExceptionLoggingFilterAttribute));
در اینجا از typeof استفاده شده‌است تا کار تزریق وابستگی‌های این فیلتر به صورت خودکار انجام شود.
در ادامه با این فرض که پیشتر تنظیمات ثبت وقایع صورت گرفته‌است:
public void Configure(ILoggerFactory loggerFactory)
{
   loggerFactory.AddDebug(minLevel: LogLevel.Debug);
اکنون اگر یک چنین اکشن متدی فراخوانی شود:
public IActionResult GetData()
{
  throw new Exception("throwing an exception!");
}
در پنجره‌ی دیباگ ویژوال استودیو، این استثناء قابل مشاهده خواهد بود:

مطالب
ماندگاری با تاخیر در SQL Server 2014
به صورت پیش فرض SQL Server از روش  write-ahead log - WAL استفاده می‌کند. به این معنا که کلیه تغییرات، پیش از commit نهایی باید در لاگ فایل آن نوشته شوند. این مساله با تعداد بالای تراکنش‌ها تا حدودی بر روی سرعت سیستم می‌تواند تاثیرگذار باشد. برای بهبود این وضعیت، در SQL Server 2014 قابلیتی به نام delayed_durability اضافه شده‌است که با فعال سازی آن، کلیه اعمال مرتبط با لاگ‌های تراکنش‌ها به صورت غیرهمزمان انجام می‌شوند. به این ترتیب تراکنش‌ها زودتر از معمول به پایان خواهد رسید؛ با این فرض که نوشته شدن تغییرات در لاگ فایل‌ها، در آینده‌ای محتمل انجام خواهند شد. این مساله به معنای فدا کردن D در ACID است (Atomicity, Consistency, Isolation, Durability). البته باید دقت داشت که رسیدن به ACID کامل هزینه‌بر است و شاید خیلی از اوقات تمام اجزای آن نیازی نباشند یا حتی بتوان با اندکی تخفیف آن‌ها را اعمال کرد؛ مانند D به تاخیر افتاده.
برای اینکار SQL Server از یک بافر 60 کیلوبایتی برای ذخیره سازی اطلاعات لاگ‌هایی که قرار است به صورت غیرهمزمان با تراکنش‌ها نوشته شوند، استفاده می‌کند. هر زمان که این 60KB پر شد، آن‌را flush کرده و ثبت خواهد نمود. به این ترتیب به دو مزیت خواهیم رسید:
- پردازش تراکنش‌ها بدون منتظر شدن جهت commit نهایی در دیسک سخت ادامه خواهند یافت. صبر کمتر به معنای امکان پردازش تراکنش‌های بیشتری در یک سیستم پر ترافیک است.
- با توجه به بافری که از آن صحبت شد، اینبار اعمال Write به صورت یک سری batch اعمال می‌شوند که کارآیی و سرعت بیشتری نسبت به حالت تکی دارند.

اندکی تاریخچه
ایده یک چنین عملی 28 سال قبل توسط Hal Berenson ارائه شده‌است! اوراکل آن‌را در سال 2006 تحت عنوان Asynchronous Commit پیاده سازی کرد و مایکروسافت در سال 2014 آن‌را ارائه داده‌است.


فعال سازی ماندگاری غیرهمزمان در SQL Server

فعال سازی این قابلیت در سطح بانک اطلاعاتی، در سطح یک تراکنش مشخص و یا در سطح رویه‌های ذخیره شده کامپایل شده مخصوص OLTP درون حافظه‌ای، میسر است.
برای فعال سازی ماندگاری با تاخیر در سطح یک دیتابیس، خواهیم داشت:
 ALTER DATABASE dbname SET DELAYED_DURABILITY = DISABLED | ALLOWED | FORCED;


در اینجا اگر ALLOWED را انتخاب کنید، به این معنا است که لاگ کلیه تراکنش‌های مرتبط با این بانک اطلاعاتی به صورت غیرهمزمان نوشته می‌شوند. حالت FORCED نیز دقیقا به همین معنا است با این تفاوت که اگر حالت ALLOWED انتخاب شود، تراکنش‌های ماندگار (آن‌هایی که به صورت دستی DELAYED_DURABILITY را غیرفعال کرده‌اند)، سبب flush کلیه تراکنش‌هایی با ماندگاری به تاخیر افتاده خواهند شد و سپس اجرا می‌شوند. در حالت Forced تنظیم دسترسی DELAYED_DURABILITY = OFF در سطح تراکنش‌ها تاثیری نخواهد داشت؛ اما در حالت ALLOWED این مساله به صورت دستی در سطح یک تراکنش قابل لغو است.
البته باید توجه داشت، صرفنظر از این تنظیمات، یک سری از تراکنش‌ها همیشه ماندگار هستند و بدون تاخیر؛ مانند تراکنش‌های سیستمی، تراکنش‌های بین دو یا چند بانک اطلاعاتی و کلیه تراکنش‌هایی که با FileTable، Change Data Capture و Change Tracking سر و کار دارند.

در سطح تراکنش‌های می‌توان نوشت:
 COMMIT TRANSACTION WITH (DELAYED_DURABILITY = ON);
و یا در رویه‌های ذخیره شده کامپایل شده مخصوص OLTP درون حافظه‌ای خواهیم داشت:
 BEGIN ATOMIC WITH (DELAYED_DURABILITY = ON, ...)

سؤال: آیا فعال سازی DELAYED_DURABILITY بر روی مباحث locking و isolation levels تاثیر دارند؟
پاسخ: خیر. کلیه تنظیمات قفل گذاری‌ها همانند قبل و بر اساس isolation levels تعیین شده، رخ خواهند داد. تنها تفاوت در اینجا است که با فعال سازی DELAYED_DURABILITY، کار commit بدون صبر کردن برای پایان نوشته شدن اطلاعات در لاگ سیستم صورت می‌گیرد. به این ترتیب قفل‌های انجام شده زودتر آزاد خواهند شد.

سؤال: میزان از دست دادن اطلاعات احتمالی در این روش چقدر است؟
در صورتیکه سرور کرش کند یا ری‌استارت شود، حداکثر به اندازه‌ی 60KB اطلاعات را از دست خواهید داد (اندازه‌ی بافری که برای اینکار درنظر گرفته شده‌است). البته عنوان شده‌است که اگر ری‌استارت یا خاموشی سرور، از پیش تعیین شده باشد، ابتدا کلیه لاگ‌های flush نشده، ذخیره شده و سپس ادامه‌ی کار صورت خواهد گرفت؛ ولی زیاد به آن اطمینان نکنید. اما همواره با فراخوانی sys.sp_flush_log، می‌توان به صورت دستی بافر لاگ‌های سیستم را flush کرد.


یک آزمایش

در ادامه قصد داریم یک جدول جدید را در بانک اطلاعاتی آزمایشی testdb2 ایجاد کنیم. سپس یکبار تنظیم DELAYED_DURABILITY = FORCED را انجام داده و 10 هزار رکورد را ثبت می‌کنیم و بار دیگر DELAYED_DURABILITY = DISABLED را تنظیم کرده و همین عملیات را تکرار خواهیم کرد:
CREATE TABLE tblData(
    ID INT IDENTITY(1, 1),
    Data1 VARCHAR(50),
    Data2 INT
);
CREATE CLUSTERED INDEX PK_tblData ON tblData(ID);
CREATE NONCLUSTERED INDEX IX_tblData_Data2 ON tblData(Data2);
 
-------------------------

alter database testdb2 SET DELAYED_DURABILITY = FORCED;

-------------------------

SET NOCOUNT ON
Print 'DELAYED_DURABILITY = FORCED'
DECLARE @counter AS INT = 0
DECLARE @start datetime = getdate()
WHILE (@counter < 10000)
BEGIN
      INSERT INTO tblData (Data1, Data2) VALUES('My Data', @counter)
      SET @counter += 1
END
Print DATEDIFF(ms,@start,getdate());
GO

-------------------------

alter database testdb2 SET DELAYED_DURABILITY = DISABLED;
truncate table tblData;
-------------------------

SET NOCOUNT ON
Print 'DELAYED_DURABILITY = DISABLED'
DECLARE @counter AS INT = 0
DECLARE @start datetime = getdate()
WHILE (@counter < 10000)
BEGIN
      INSERT INTO tblData (Data1, Data2) VALUES('My Data', @counter)
      SET @counter += 1
END
Print DATEDIFF(ms,@start,getdate());
GO

-----------------------
با این خروجی:
 DELAYED_DURABILITY = FORCED
666
DELAYED_DURABILITY = DISABLED
2883
در این آزمایش، سرعت insertها در حالت DELAYED_DURABILITY = FORCED حدود 4 برابر است نسبت به حالت معمولی.


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

Control Transaction Durability
SQL Server 2014 Delayed Durability/Lazy Commit
Delayed Durability in SQL Server 2014 – Part 1
Is In-Memory OLTP Always a silver bullet for achieving better transactional speed
Delayed Durability in SQL Server 2014
مطالب
پردازش توالی توالی‌ها در Reactive extensions
به صورت پیش فرض، Rx هر بار تنها یک مقدار را بررسی می‌کند. اما گاهی از اوقات نیاز است تا در هربار، بیشتر از یک مقدار دریافت و پردازش شوند. برای این منظور Rx متدهای الحاقی ویژه‌ای را به نام‌های Buffer ،Scan و Window تدارک دیده‌است تا بتواند از یک توالی، چندین توالی را تولید کند (توالی توالی‌ها = Sequence of sequences).


متد Scan

فرض کنید قصد دارید تعدادی عدد را با هم جمع بزنید. برای اینکار عموما عدد اول با عدد دوم جمع زده شده و سپس حاصل آن با عدد سوم جمع زده خواهد شد و به همین ترتیب تا آخر توالی. کار متد Scan نیز دقیقا به همین نحو است. هربار که قرار است توالی پردازش شود، حاصل عملیات مرحله‌ی قبل را در اختیار مصرف کننده قرار می‌دهد.
در مثال ذیل، قصد داریم حاصل جمع اعداد موجود در آرایه‌ای را بدست بیاوریم:
var sequence = new[] { 12, 3, -4, 7 }.ToObservable();
var runningSum = sequence.Scan((accumulator, value) =>
{
    Console.WriteLine("accumulator {0}", accumulator);
    Console.WriteLine("value {0}", value);
    return accumulator + value;
});
runningSum.Subscribe(result => Console.WriteLine("result {0}\n", result));
با این خروجی
result 12

accumulator 12
value 3
result 15

accumulator 15
value -4
result 11

accumulator 11
value 7
result 18
در اولین بار اجرای متد Subscribe، کار مقدار دهی accumulator با اولین عنصر آرایه صورت می‌گیرد.
در دفعات بعدی، مقدار این accumulator با عدد جاری جمع زده شده و حاصل این عملیات در تکرار آتی، مجددا توسط accumulator قابل دسترسی خواهد بود.

یک نکته: اگر علاقمند باشیم که مقدار اولیه‌ی accumulator، اولین عنصر توالی نباشد، می‌توان آن‌را توسط پارامتر seed متد Scan مقدار دهی کرد:
 var runningSum = sequence.Scan(seed: 10, accumulator: (accumulator, value) =>


متد Buffer

متد بافر، کار تقسیم یک توالی را به توالی‌های کوچکتر، بر اساس زمان، یا تعداد عنصر مشخص شده، انجام می‌دهد. برای مثال در برنامه‌های دسکتاپ شاید نیازی نباشد تا به ازای هر عنصر توالی، یکبار رابط کاربری را به روز کرد. عموما بهتر است تا تعداد مشخصی از عناصر یکجا پردازش شده و نتیجه‌ی این پردازش به تدریج نمایش داده شود.
var sequence = Enumerable.Range(1, 200)
                 .ToObservable()
                 .Buffer(count: 10);

sequence.Subscribe(onNext: numbers =>
{
   Console.WriteLine(numbers.Sum());
});
در اینجا نحوه‌ی استفاده از متد بافر را به همراه مشخص کردن تعداد اعضای بافر ملاحظه می‌کنید. هربار که onNext متد Subscribe فراخوانی شود، 10 عنصر از توالی را در اختیار خواهیم داشت (بجای یک عنصر حالت متداول بافر نشده).
به این ترتیب می‌توان فشار حجم اطلاعات ورودی با فرکانس بالا را کنترل کرد و در نتیجه از منابع موجود بهتر استفاده نمود. برای مثال اگر می‌خواهید عملیات bulk insert را انجام دهید، می‌توان بر اساس یک batch size مشخص، گروه گروه اطلاعات را به بانک اطلاعاتی اضافه کرد تا فشار کار کاهش یابد.

همینکار را بر اساس زمان نیز می‌توان انجام داد:
 var sequence = Enumerable.Range(1, 200)
                   .ToObservable()
                   .Buffer(timeSpan: TimeSpan.FromSeconds(2));
در مثال فوق هر 2 ثانیه یکبار، مجموعه‌ای از عناصر به متد onNext ارسال خواهند شد.


متد Window

متد Window نیز دقیقا همان پارامترهای متد بافر را قبول می‌کند. با این تفاوت که هربار، یک توالی obsevable را به متد onNext ارسال می‌کند.
نوع numbers پارامتر onNext، در حین بکارگیری متد بافر در مثال‌های فوق، IList of int است. اما اگر متدهای Buffer را تبدیل به متد Window کنیم، اینبار نوع numbers، معادل IObservable of int خواهد شد.
 var sequence = Enumerable.Range(1, 200)
                           .ToObservable()
                           .Window(timeSpan: TimeSpan.FromSeconds(2));

sequence.Subscribe(onNext: numbers =>
{
       numbers.Subscribe(onNext: number => Console.WriteLine(number));
});


چه زمانی باید از Buffer استفاده کرد و چه زمانی از Window؟

در متد بافر، به ازای هر توالی که به پارامتر onNext ارسال می‌شود، یکبار وهله‌ی جدیدی از توالی مدنظر در حافظه ایجاد و ارسال خواهد شد. در متد Window صرفا اشاره‌گرهایی به این توالی را در اختیار داریم؛ بنابراین مصرف حافظه‌ی کمتری را شاهد خواهیم بود. متد Window بسیار مناسب است برای اعمال aggregation. مثلا اگر نیاز است جمع، میانگین، حداقل و حداکثر عناصر دریافتی محاسبه شوند، بهتر است از متد Window استفاده شود که نهایتا قابلیت استفاده از متدهای الحاقی Sum و Max و Min را به همراه دارد. با این تفاوت که حاصل این‌ها نیز یک IObservable است که باید Subscribe آن‌را برای دریافت نتیجه فراخوانی کرد:
 var sequence = Enumerable.Range(1, 200)
                          .ToObservable()
                          .Window(10);

sequence.Subscribe(onNext: numbers =>
{
     numbers.Sum().Subscribe(onNext: number => Console.WriteLine(number));
});
در این حالت متد Window، برخلاف متد Buffer، توالی numbers را هربار کش نمی‌کند و به این ترتیب می‌توان به مصرف حافظه‌ی کمتری رسید.


کاربردهای دنیای واقعی

در اینجا دو مثال از بکارگیری متد Buffer را جهت پردازش مجموعه‌های عظیمی از اطلاعات و نمایش همزمان آن‌ها در رابط کاربری ملاحظه می‌کنید.
مثال اول: فرض کنید قصد دارید تمام فایل‌های درایو C خود را توسط یک TreeView نمایش دهید. در این حالت نباید رابط کاربری برنامه در حالت هنگ به نظر برسد. همچنین به علت زیاد بودن تعداد فایل‌ها و نمایش همزمان آن‌ها در UI، نباید CPU Usage برنامه تا حدی باشد که در کار سایر برنامه‌ها اخلال ایجاد کند. در این مثال‌ها با استفاده از Rx و متد بافر آن، هربار مثلا 1000 آیتم را بافر کرده و سپس یکجا در TreeView نمایش می‌دهند. به این ترتیب دو شرط یاد شده محقق می‌شوند.
The Rx Framework By Example

مثال دوم: خواندن تعداد زیادی رکورد از بانک اطلاعاتی به همراه نمایش همزمان آن‌ها در UI بدون اخلالی در کار سیستم و همچنین هنگ کردن برنامه.
Using Reactive Extensions for Streaming Data from Database