مطالب
نحوه به تاخیر انداختن ارسال ایمیل‌ها در آوت لوک

مطلب امروز به کنترل شخصی مرتبط است. به درد همه می‌خوره! :)
چگونه ارسال ایمیلی را که ممکن است 5 دقیقه بعد از ارسال آن به شدت پشیمان شویم، کنترل کنیم؟!

برای به تاخیر انداختن تمامی ایمیل‌های ارسالی از طریق آوت لوک می‌توان به صورت زیر عمل کرد:

به منوی tools‌ گزینه rules and alerts مراجعه کنید.


در صفحه باز شده بر روی دکمه new rule کلیک کنید.
در پنجره بعدی گزینه Check messages after sending را انتخاب کرده و بر روی دکمه next کلیک کنید.


در صفحه بعد تنها بر روی گزینه next کلیک کنید (تا تنظیمات ما بر روی تمامی ایمیل‌های ارسالی اعمال شود).
سپس بر روی دکمه yes پیغام باز شده کلیک نمائید.
تنظیمات اصلی مطلب جاری مربوط به این صفحه است. در اینجا گزینه defer delivery by a number of minutes را تیک بزنید.




سپس بر روی لینک a number of کلیک کنید تا صفحه وارد کردن میزان زمان به تاخیر انداختن ارسال را بتوان وارد کرد. پس از وارد کردن یک عدد دلخواه و کلیک بر روی دکمه ok ، بر روی دکمه next کلیک نمائید.
در صفحه بعد نیز بر روی دکمه next کلیک کنید. (البته در اینجا می‌توان مشخص کرد که برای مثال اگر عنوان ویژه‌ای بکار برده شد یا به گروه خاصی ایمیل ارسال گردید، این محدودیت برداشته شود)
و در آخرین صفحه، نامی دلخواه را وارد کرده و بر روی دکمه‌ی خاتمه کلیک نمائید.
در صفحه‌ی اصلی rules & alerts نیز بر روی دکمه apply کلیک کنید، تا تنظیمات اعمال گردد.

از این پس هر ایمیل ارسالی شما مدتی در outbox معطل شده و سپس ارسال می‌گردد.
هنوز تا 5 دقیقه دیگر فرصت هست! با مراجعه به outbox می‌توان ایمیل مورد نظر را در صورت منصرف شدن حذف یا ویرایش کرد. (بنابر تجربه 3 دقیقه کافی است!)

مطلب ارسالی فوق برای آوت لوک 2007 تنظیم شد. اگر آوت لوک شما 2003 است لطفا به آدرس زیر مراجعه کرده و قسمت Delay delivery of all messages را مطالعه نمائید.



مطالب
نکات استفاده از افزونه‌ی Web Essentials جهت کار با تصاویر
در این مطلب نکات کار با تصاویر را توسط افزونه‌ی Web Esstentials بررسی می‌کنیم. این افزونه قابلیت‌های زیر را در کار با تصاویر در اختیار شما قرار می‌دهد:
بهینه‌سازی تصاویر
یکی از موارد مهمی که باید مورد توجه قرار بگیرد، استفاده از تصاویر کم حجم در وب‌سایت می‌باشد. روش‌های مختلفی جهت بهینه‌سازی تصاویر مورد استفاده در سایت وجود دارند، به طور مثال جهت بهینه‌سازی تصاویر PNG می‌توانید از ابزار PNGGauntlet استفاده کنید. همچنین اینجا نیز یک ابزار آنلاین موجود می‌باشد. افزونه‌ی Web Essentails این قابلیت را به آسانی در اختیار شما قرار می‌دهد؛ اینکار را می‌توانید توسط این افزونه به روش‌های زیر انجام دهید:
  • کلیک راست بر روی تصویر
برای اینکار بر روی فایلی که می‌خواهید optimize کنید، کلیک راست کرده و از منوی ظاهر شده گزینه Web Essentials و سپس Optimize Image را انتخاب کنید:

در قسمت status bar نیز می‌توانید نتیجه را مشاهده کنید:

  • انتخاب چندین تصویر
روال قبلی را می‌توانید برای چندین فایل انتخاب شده و یا یک پوشه تکرار کنید:

  • بهینه‌سازی تصاویر موجود در فایل‌های CSS
همچنین امکان بهینه‌سازی تصاویر داخل فایل‌های CSS نیز توسط این افزونه امکان پذیر است:

  • بهینه سازی تصاویر Base64 Encode
توسط این افزونه می‌توانیم تصاویر  Data Uri  را نیز بهینه سازی کنیم:

همانطور که در تصویر فوق مشاهده می‌کنید می‌توانیم تصاویری که به صورت Data Uri درون کد پیوست شده اند را با کلیک بر روی Save to file به صورت یک فایل ذخیره کنیم.

ایجاد تصاویر Sprite
یکی دیگر ار قابلیت‌های افزونه Web Essentials امکان تهیه تصاویر به صورت Sprite می باشد. برای اینکار کافی است به این صورت عمل کنید:

بعد از کلیک بر روی Create image sprite باید یک نام برای آن تعیین کنید و سپس بر روی کلید Save کلیک کنید. با اینکار یک فایل از نوع XML با پسوند sprite برای شما ساخته خواهد شد:
<?xml version="1.0" encoding="utf-8"?>
<sprite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://vswebessentials.com/schemas/v1/sprite.xsd">
  <settings>
    <!--Determines if the sprite image should be automatically optimized after creation/update.-->
    <optimize>true</optimize>
    <!--Determines the orientation of images to form this sprite. The value must be vertical or horizontal.-->
    <orientation>vertical</orientation>
    <!--File extension of sprite image.-->
    <outputType>png</outputType>
    <!--Determin whether to generate/re-generate this sprite on building the solution.-->
    <runOnBuild>false</runOnBuild>
    <!--Use full path to generate unique class or mixin name in CSS, LESS and SASS files. Consider disabling this if you want class names to be filename only.-->
    <fullPathForIdentifierName>true</fullPathForIdentifierName>
    <!--Use absolute path in the generated CSS-like files. By default, the URLs are relative to sprite image file (and the location of CSS, LESS and SCSS).-->
    <useAbsoluteUrl>false</useAbsoluteUrl>
    <!--Specifies a custom subfolder to save CSS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForCss />
    <!--Specifies a custom subfolder to save LESS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForLess />
    <!--Specifies a custom subfolder to save SCSS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForScss />
  </settings>
  <!--The order of the <file> elements determines the order of the images in the sprite.-->
  <files>
    <file>/Content/Images/01.png</file>
    <file>/Content/Images/02.png</file>
    <file>/Content/Images/03.png</file>
    <file>/Content/Images/04.png</file>
  </files>
</sprite>
یکی از زیر مجموعه‌های این فایل، تصویر نهایی می‌باشد، همچنین فایل‌های css, less, map و scss آن نیز تولید می‌شود:

به عنوان مثال فایل CSS تصویر فوق به صورت زیر می‌باشد:
/*
This is an example of how to use the image sprite in your own CSS files
*/
.Content-Images-01 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 0;
}
.Content-Images-02 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -32px;
}
.Content-Images-03 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -64px;
}
.Content-Images-04 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -96px;
}
هر کدام از کلاس‌های فوق به یک تصویر در فایل مربوطه توسط image position اشاره می‌کند. شما می‌توانید با انتساب هر کدام از کلاس‌های فوق به یک المنت از آن تصویر استفاده نمائید:
<div class="Content-Images-01"></div>
<div class="Content-Images-02"></div>
<div class="Content-Images-03"></div>
<div class="Content-Images-04"></div>

استفاده از تصاویر Data URIs
یکی دیگر از روش‌های کاهش درخواست‌های HTTP در یک سایت استفاده از Data URIs  می باشد، توسط این روش می‌توانید فایل هایتان را درون HTML و یا CSS قرار دهید یا به اصطلاح embed کنید. به طور مثال جهت استفاده از یک تصویر می‌توانید به راحتی با آدرس دهی تصویر درون تگ img، تصویر را درون صفحه نمایش دهید:
<img src="https://www.dntips.ir/images/logo.png" />

همین کار را می‌توانیم توسط Data URIs انجام دهیم:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC" />
در کد فوق تصویر موردنظر را درون HTML به صورت embed شده قرار داده ایم، در این حالت دیگری نیازی به رفت و برگشت به سرور جهت نمایش تصویر نیست.
سینتکس Data URIs
به طور مثال تگ زیر را در نظر داشته باشید:
<img src="data:image/png;base64,iVBOR..." />
مقدار ویژگی src شامل موارد زیر است:
data: نام schema
image/png: نوع محتوا(content type)
base64: نوع encoding استفاده شده برای encode کردن اطلاعات
iVBOR...: اطلاعات encode شده.
توسط افزونه Web Essentials به راحتی می‌توانید تصویر موردنظرتان را به صورت Data URI تهیه کنید:

مطالب
مثالی از الگوی Delegate Dictionary
این الگو چیز جدیدی نیست و قبلا تو سری مطالب «مروری بر کاربردهای Action و Func» دربارش مطلب نوشته شده و...
البته با توجه به جدید بودن این الگو اسم واحدی براش مشخص نشده ولی تو این مطلب «الگوی Delegate Dictionary» معرفی شده که بنظرم از بقیه بهتره.
به طور خلاصه این الگو میگه اگه قراره براساس شرایط (ورودی) خاصی کار خاصی انجام بشه بجای استفاده از IF و Switch از DictionaryوFunc یا Action استفاده کنیم.

برای مثال فرض کنید مدلی به شکل زیر داریم
public class Person
{
    public int Id { get; set; }
    public Gender Gender { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
قراره براساس جنسیت(شرایط) شخص اعتبارسنجی متفاوتی(کار خاص) رو انجام بدیدم.مثلا در اینجا قراره چک کنیم اگه شخص مرد بود اسم زنونه انتخاب نکرده باشه و...
خب روش معمول به این شکل میتونه باشه
switch (person.Gender)
{
    case Gender.Male:
        if (IsMale(person.FirstName))
        {
            //Isvalid
        }
        break;
    case Gender.Female:
        if (IsFemale(person.FirstName))
        {
            //Isvalid
        }
        break;
}
خب این روش خوب جواب میده ولی باید در حد توان استفاده از IF و Switch رو کم کرد.مثلا تو همین مثال ما اصل Open/Closed رو نقض کردیم فکر کنید قرار باشه اعتبارسنجی دیگه ای از همین دست به این کد(کلاس) اضافه بشه باید تغیرش بدیم پس این کد(کلاس) برای تغییر بسته نیست.در اینجور موارد «الگوی Delegate Dictionary» به کار ما میاد.
ما میایم توابع مورد نظرمون رو داخل یک Dictionary ذخیره میکنیم.
var genderFuncs = new Dictionary<Gender, Func<string, bool>>
                {
                    {Gender.Male , (x) => IsMale(x)},
                    {Gender.Female , (x) => IsFemale(x)}
                };
فرض کنید پیاده سازی توابع به شکل زیر باشه
public static bool IsMale(string name)
{
    //check...
    return true;
}
public static bool IsFemale(string name)
{
    //check...
    if (name == "Farzad")
    {
        return false;    
    }
    return true;
}
نحوه استفاده
var dummyPerson = new List<Person>
                {
                    new Person
                        {Id = 1, Gender = Gender.Male, FirstName = "Mohammad", LastName = "Saheb"},
                    new Person
                        {Id = 2, Gender = Gender.Female, FirstName = "Farzad", LastName = "Mojidi"}
                };

foreach (var person in dummyPerson)
{
    bool isValid = genderFuncs[person.Gender].Invoke(person.FirstName);          
}
با همین روش میشه قسمت آخر مقاله ی خوب آقای کیاست رو هم Refactor کرد.
var query = context.Students.AsQueryable();
  if (searchByName)
  {
      query= query.FindStudentsByName(name);
  }
  if (orderByAge)
  {
      query = query.OrderByAge();
  }
  if (paging)
  {
     query =  query.SkipAndTake(skip, take);
  }
  return query.ToList();
توابع رو داخل یک دیکشنری ذخیره میکنیم
var searchTypeFuncs = new Dictionary<SearchType, Func<IQueryable<Student>, string, IQueryable<Student>>>
                    {
                        {SearchType.FirstName, (x, y) => x.FindStudentsByName(y)},
                        {SearchType.LastName, (x, y) => x.FindStudentsByLastName(y)}
                    };
نحوه استفاده
public static IList<Student> SearchStudents(IQueryable<Student> students, SearchType type, string keyword)
{
    var result = searchTypeFuncs[type].Invoke(students, keyword);
    return result.ToList();
}
نظرات مطالب
OpenCVSharp #5
سلام؛ ممنون از مطلب خوبتون. من از کتابخانه‌های opncv و emgu برای خوندن تصویر از وب کم و پردازش تصویر استفاده میکنم. مشکل من سرعت پایین برنامه در رزولوشن بالای وب کم است هر چه رزولوشن بالاتر میره سرعت برنامه من کمتر میشه. مثلا تو یه سیستم corei7 با ram 8 gig من 61 درصد استفاده از cpu دارم و فیلم نمایش داده شده در برنامه از محیط واقعی عقب‌تر است. من در رویداد Application.Idle   فرم این کد را قرار دادم:
 public void Application_Idle(object sender, EventArgs e) 
        {
            if (_capture != null)
            {
                try
                {
                    frame = _capture.QueryFrame();
                    Pic.Image = frame.ToBitmap();
                    //frame.ToBitmap(dst: (Bitmap)Pic.Image);
                }
                catch (NullReferenceException excpt)
                {
                    MessageBox.Show(excpt.Message);// you can also show any suitable message
                }
            }
        }
شما فرمودید برای به حداقل رسانی به روز رسانی‌های بعدی picture box بهتر است از متد ToBitmap به شکل زیر کمک گرفت: 
iplImage.ToBitmap(dst: (Bitmap)pictureBox.Image);
ولی dst رو فرم نمیشناسه و اینکه آیا رویداد idle رویداد مناسبی برای این کار هست؟ نظرتون درباره سرعت پایین برنامه من چیه؟
نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت اول
سلام.
من یه وظیفه نوشتم که می‌یاد هر 1 دقیقه 1 بار یه متن رو در Table درج می‌کنه.
این کد کلاس
using Quartz;
using System.Data;
using System.Data.OleDb;
using System.Configuration;


namespace WebApplication1
{
    
    public class TestQuartzClass:IJob
    {
        
       
        public void Execute(IJobExecutionContext context)
        {
            //Page myPage = (Page)HttpContext.Current.Handler;
            //TextBox MyTextBox=(TextBox)myPage.FindControl("txt");
            
            string sql = "Insert into tbl_Test (Content) values (@Content)";
            ExecuteNoneQuery(System.Data.CommandType.Text, sql, new OleDbParameter[]{
                //new OleDbParameter("@Content", MyTextBox.Text)
                new OleDbParameter("@Content","Hello world!")
            });

        }

        public int ExecuteNoneQuery(CommandType commandType, string commandText, params OleDbParameter[] commandParameters)
        {
            using (OleDbConnection con = new OleDbConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
            {
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                cmd.CommandType = commandType;
                cmd.CommandText = commandText;
                cmd.Parameters.AddRange(commandParameters);
                con.Open();
                int retVal = cmd.ExecuteNonQuery();
                con.Close();
                return retVal;
            }
        }
    }


}



و این هم کد صفحه ای که با کلیک دکمه وظیفه شروع به کار می‌کنه

using System;
using Quartz;
using Quartz.Impl;

namespace WebApplication1
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        public static void ConfigureQuartzJobs()
        {
            // construct a scheduler factory
            ISchedulerFactory schedFact = new StdSchedulerFactory();

            // get a scheduler
            IScheduler sched = schedFact.GetScheduler();
            sched.Start();
            IJobDetail job = JobBuilder.Create<TestQuartzClass>()
                .WithIdentity("SendJob")
                .Build();
            var trigger = TriggerBuilder.Create()
                .WithIdentity("SendTrigger")
                .WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever())
                //.StartAt(startTime)
                .StartNow()
                .Build();

            sched.ScheduleJob(job, trigger);
        }

        protected void btn_Click(object sender, EventArgs e)
        {
            ConfigureQuartzJobs();
        }
    }
}

همونطور که می‌بینید متن رو به شکل زیر پاس دادم.
new OleDbParameter("@Content","Hello world!")
ولی من می‌خوام این متن رو از TextBox بگیرم.
و برای اینکه در کلاس بتونم به کنترلهای صفحه دسترسی داشته باشم کدهای زیر رو به متد Exceute اضافه کردم
Page myPage = (Page)HttpContext.Current.Handler;
            TextBox MyTextBox=(TextBox)myPage.FindControl("txt");

ولی به محض اینکه این کدها رو اضافه می‌کنم دیگه برنامه کار نمی‌کنه. 

متن داخل تکست باکس رو هم قصد داشتم به شکل زیر پاس بدم.
new OleDbParameter("@Content", MyTextBox.Text)

لطفاً راهنمایی کنید.
من یک نمونه هم به منظور تست آماده کردم که از لینک زیر می‌تونید دانلود کنید:
http://www.4shared.com/rar/1Fu_jpOOba/WebApplication1.html 
بازخوردهای دوره
صفحات مودال در بوت استرپ 3
 لطفا فایلی رو که پیوست کردم دانلود کنید ، در فایل پیوست شده دو تا مودال روی هم نمایش داده می‌شوند و تداخلی با هم ندارند .
برای نمایش مودال تو در تو فکر نکنم نیازی به این کتابخانه "jquery.bootstrap-modal-ajax-form.js  " باشد . کتابخانه جاوااسکریت  بوت استراپ کفایت میکند .
بازخوردهای دوره
صفحات مودال در بوت استرپ 3
فایل layout مثال پیوست شده را بررسی کنید. در آن لیست اسکریپت‌های مورد نیاز و همچنین ترتیب دقیق آ‌ن‌ها ذکر شده. برای مثال اول باید jQuery باشد و بعد اسکریپت jquery.validate و الی آخر. فایل‌های مربوطه هم در پوشه‌ی Scripts پروژه پیوست شده موجود است.
نظرات مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت چهارم - انجام اعمال async با Redux
شما دقیقا از مثال پیوست شده استفاده می‌کنید؟ چون در این مثال نکته‌ی «تنظیمات CORS مخصوص React در برنامه‌های ASP.NET Core 3x » لحاظ شده. تنها تصویری هم که در این مطلب ارسال شده و لیست مطالب را نشان می‌دهد، بر مبنای همین پروژه‌ی پیوست شده‌ی انتهای بحث هست.
مطالب
پروسیجرها و شنود پارامترها در SQL Server
در اس کیو ال سرور 2016، قابلیت غیر فعال نمودن parameter sniffing در سطح بانک اطلاعاتی مهیا شده است. اما چرا؟


قبل از پاسخگویی به سؤال بالا، به یک سری مقدمات نیاز است:

وقتی یک کوئری به اس کیو ال ارسال می‌شود، چه اتفاقی رخ می‌دهد؟
وقتی یک کوئری ارسال می‌شود، تعدادی از پروسس‌ها بر روی کوئری شروع به فعالیت‌هایی مانند مهیا نمودن داده‌های بازگشتی، یا ذخیره سازی و ... می‌کنند.
 پروسس‌ها به دو دسته زیر تقسیم می‌شوند:
  1. پروسس‌هایی که در relational engine رخ می‌دهند
  2. پروسس‌هایی که در storage engine رخ می‌دهند

در relational engine، هر کوئری pars شده و سپس بوسیله query optimizer پردازش و پلن اجرایی (execution plan) آن که بفرمت باینری است، ایجاد می‌شود و به storage engine ارسال می‌گردد. در storage engine پروسس‌هایی مانند قفل گذاری، نگهداری ایندکس‌ها و تراکنش‌ها رخ می‌دهد. هنگامیکه اس کیو ال سرور کوئری را دریافت می‌نمایند، آن را بلافاصله به relational engine ارسال می‌کند. سپس نحو (syntax) آن بررسی می‌شود؛ این عمل  query parsing نامیده می‌شود. خروجی عملیات پارسر، یک ساختار درختی (query tree) است. این ساختار درختی مشخص کننده مراحل لازم جهت اجرای کوئری ارائه شده می‌باشد.
اگر یک کوئری شامل DML نباشد (مانند ساخت جدول)، علمیات بهبود برروی آن صورت نخواهد گرفت. ولی در صورتیکه کوئری ارسالی، DML باشد، درخت اشاره شده در بالا به algebrizer فرستاده می‌شود که وظیفه آن تفسیر و بررسی کلیه نام اشیاء، جداول و ستون‌های اشاره شده در متن کوئری است. فرآیند algebrizer بسیار مهم و حیاتی است؛ بدلیل اینکه در کوئری ممکن است اشاره کننده‌هایی به اشیایی باشند که در بانک اطلاعاتی موجود نیست. خروجی algebrizer یک query processor tree باینری است که به بهبود دهنده کوئری ارسال می‌گردد. 

معرفی Query Optimizer (بهبود دهنده پرس و جو)

بهبود دهنده، بهترین مسیر اجرای کوئری را مشخص می‌کند. این بهبود دهنده است که مشخص می‌کند که اطلاعات بوسیله ایندکس دریافت شوند، یا اینکه از چه اتصالی استفاده شود و الی آخر. این تصمیمات براساس محاسبات هزینه‌های (میزان پردازش لازم cpu و I/O) پلن اجرایی صورت خواهد پذیرفت. بهمین دلیل به پلن cost-based نیز شناخته می‌شود.
هنگامیکه کوئری ساده‌ای مانند دریافت اطلاعات از یک جدول، که بر روی آن ایندکس گذاری انجام نشده‌است، ارسال شود، بهبود دهنده بجای مشخص نمودن یک پلن مناسب بهینه، از یک پلن ساده (trivial) استفاده می‌کند. ولی برعکس در صورتیکه کوئری trivial نباشد (یعنی مثلا کوئری به گونه‌ای باشد که از ایندکس‌ها به شکل صحیحی استفاده شده باشند)، بهبود دهنده یک پلن مناسب را براساس اطلاعات آماری مهیا شده در اس کیو ال سرور، تولید و انتخاب می‌نماید.

اطلاعات آماری از ستون‌ها و ایندکس‌ها جمع آوری می‌شود. این اطلاعات شامل نحوه توزیع داده، یکتایی و انتخاب شوندگی است. این اطلاعات توسط یک histogram ارائه می‌شود. اگر اطلاعات آماری برای یک ستون و یا ایندکس وجود داشته باشد، بهبود دهنده از آن‌ها برای محاسبات خود استفاده خواهد کرد. اطلاعات آماری بصورت خودکار برای تمام ایندکس‌ها و یا هر ستونی که بشود بر روی آن‌ها where یا join نوشت، فراهم خواهد شد.
بهبود دهنده با مقایسه پلن‌ها براساس بررسی تفاوت‌های انواع joinها، چیدمان مجدد ترتیب join و بررسی ایندکس‌های مختلف و سایر فعالیت‌های دیگر، پلن مناسب را انتخاب و از آن استفاده می‌کند. در طی هر کدام از فعالیت‌های اشاره شده، زمان اجرای آن‌ها نیز تخمین زده (estimated cost) خواهد شد و در پایان، زمان کل تخمینی بدست خواهد آمد و بهبود دهنده از این زمان برای انتخاب پلن مناسب بهره خواهد برد. باید توجه داشت که این زمان تقریبی است. زمانیکه بهبود دهنده پلن اجرایی انتخاب می‌کند، یک actual plan را ایجاد و در حافظه ذخیره می‌شود؛ بنام plan cache. البته درصورتیکه پلن مشابه و بهینه‌تری وجود نداشته باشد. 

استفاده مجدد از پلن ها

تولید پلن هزینه بر است. به‌همین دلیل اس کیوال سرور اقدام به ذخیره سازی و نگهداری آن‌ها می‌کند تا بتواند از آن‌ها مجددا استفاده نماید؛ البته تا جایی که مقدور باشد. هنگامیکه آن‌ها تولید می‌شوند، در قسمتی از حافظه بنام plan cache ذخیره می‌شوند. به این عمل procedure cache نیز گفته می‌شود.

هنگامیکه کوئری به سرور ارسال می‌شود، بوسیله بهبود دهنده، یک estimated plan ایجاد خواهد شد و قبل از اینکه به storage engine ارسال شود، بهبود دهنده estimated plan را با actual execution planهای موجود در plan cache مقایسه می‌کند. در صورتیکه یک actual plan را مطابق با estimated plan پیدا نماید، از آن مجدد استفاده خواهد کرد. این استفاده مجدد به عدم تحمیل سربار اضافه‌ای به سرور جهت کوئری‌های بزرگ و پیچیده که در زمان واحد، هزاران بار اجرا خواهند شد، منجر می‌شود.
هر پلن فقط یکبار در حافظه ذخیره خواهد شد. ولی در مواقعی با تشخیص بهبود دهنده و هزینه پلن، یک کوئری می‌تواند پلن دیگری نیز داشته باشد. بنابراین پلن دوم نیز با مجموعه عملیاتی متفاوت، جهت اجرای موازی (parallel execution) برای یک کوئری ایجاد و در حافظه ذخیره می‌شود.
پلن‌های اجرایی برای همیشه در حافظه باقی نخواهند ماند. پلن‌های اجرایی دارای طول عمری طبق فرمول حاصل ضرب هزینه، در تعداد دفعات می‌باشند. مثلاً پلنی با هزینه 10 و تعداد دفعات اجرای 5، طول عمر 50 را خواهد داشت. پروسس lazywriter که یک پروسس داخلی است وظیفه آزاد سازی تمام انواع کش‌ها، از جمله پلن کش را دارد. این پروسس در بازه‌های مشخص، تمام اشیاء درون حافظه را بررسی کرده و یک واحد از طول عمر آن‌ها می‌کاهد.
در موارد زیر، یک پلن از حافظه پاک خواهد شد:
1. به حافظه بیشتری نیاز باشد
2. طول عمر پلن صفر شده باشد 

حال فرض کنید شما یک پروسیجر یا یک کوئری پارامتری دارید (پارامتر ورودی: شناسه سفارش یا نال) که کلیه محصولات سفارش داده شده یا محصولات یک سفارش خاص را نمایش می‌دهد. هنگامی که SQL Server optimizer پلن این کوئری را ایجاد می‌کند و یا آن را کامپایل می‌کند، به پارامترهای ورودی این پروسیجر گوش می‌دهد (نال یا یک شناسه سفارش). optimizer بوسیله column statistics از تعداد رکوردهایی که بازگشت داده می‌شود، برآوردی می‌کند (مثلا 40 رکورد). سپس یک پلن مناسب را انتخاب می‌کند و آن را برای اجرا ارسال می‌کند و پلن را ذخیره می‌نماید.
جمله آخر، معمولا باعث ایجاد مشکل می‌شوند.
اگر optimizer تکست کوئری مشابهی را مشاهده نماید، ولی با پارامترهای متفاوت، به کش پلن مراجعه کرده و اگر در آن جا قرار داشت، از آن مجددا استفاده می‌نماید. این استفاده مجدد خوب است؛ اما  درصورتیکه پارامتر ارسالی نال باشد چه اتفاقی رخ می‌دهد؟ جدول سفارشات محصول بسیار حجیم است و متاسفانه از پلنی که برای بازگشت 40 رکورد قبلا ایجاد شده، برای بازگشت این حجم بالای از رکوردها استفاده می‌شود که این کشنده است.
هیچ تضمینی وجود ندارد که از وقوع این اتفاق جلوگیری نمایید؛ اما می‌توانید در هنگام توسعه، پروسیجر را شناسایی و نسبت به رفع آنها اقدام نمایید. ابتدا کش پلن را خالی نمایید و سپس پروسیجر را با مقادیر متفاوت، اجرا نمایید. در صورتیکه پلن‌های متفاوتی مشاهده نمودید، این یک علامت هشدار است و می‌بایست نسبت به رفع آن‌ها اقدام فوری نمایید. 
مطالب
ایجاد «خواص الحاقی»
حتما با متدهای الحاقی یا Extension methods آشنایی دارید؛ می‌توان به یک شیء، که حتی منبع آن در دسترس ما نیست، متدی را اضافه کرد. سؤال: در مورد خواص چطور؟ آیا می‌شود به وهله‌ای از یک شیء موجود از پیش طراحی شده، یک خاصیت جدید را اضافه کرد؟
احتمالا شاید عنوان کنید که با اشیاء dynamic می‌توان چنین کاری را انجام داد. اما سؤال در مورد اشیاء غیر dynamic است.
یا نمونه‌ی دیگر آن Attached Properties در برنامه‌های مبتنی بر Xaml هستند. می‌توان به یک شیء از پیش موجود Xaml، خاصیتی را افزود که البته پیاده سازی آن منحصر است به همان نوع برنامه‌ها.


راه حل پیشنهادی

یک Dictionary را ایجاد کنیم تا ارجاعی از اشیاء، به عنوان کلید، در آن ذخیره شده و سپس key/valueهایی به عنوان value هر شیء، در آن ذخیره شوند. این key/valueها همان خواص و مقادیر آن‌ها خواهند بود. هر چند این راه حل به خوبی کار می‌کند اما ... مشکل نشتی حافظه دارد.
شیء Dictionary یک ارجاع قوی را از اشیاء، درون خودش نگه داری می‌کند و تا زمانیکه در حافظه باقی است، سیستم GC مجوز رهاسازی منابع آن‌ها را نخواهد یافت؛ چون عموما این نوع Dictionaryها باید استاتیک تعریف شوند تا طول عمر آن‌ها با طول عمر برنامه یکی گردد. بنابراین اساسا اشیایی که به این نحو قرار است پردازش شوند، هیچگاه dispose نخواهند شد. راه حلی برای این مساله در دات نت 4 به صورت توکار به دات نت فریم ورک اضافه شده‌است؛ به نام ساختار داده‌ای ConditionalWeakTable.


معرفی ConditionalWeakTable

ConditionalWeakTable جزو ساختارهای داده‌ای کمتر شناخته شده‌ی دات نت است. این ساختار داده، اشاره‌گرهایی را به ارجاعات اشیاء، درون خود ذخیره می‌کند. بنابراین چون ارجاعاتی قوی را به اشیاء ایجاد نمی‌کند، مانع عملکرد GC نیز نشده و برنامه در دراز مدت دچار مشکل نشتی حافظه نخواهد شد. هدف اصلی آن ایجاد ارتباطی بین CLR و DLR است. توسط آن می‌توان به اشیاء دلخواه، خواصی را افزود. به علاوه طراحی آن به نحوی است که thread safe است و مباحث قفل گذاری بر روی اطلاعات، به صورت توکار در آن پیاده سازی شده‌است. کار DLR فراهم آوردن امکان پیاده سازی زبان‌های پویایی مانند Ruby و Python برفراز CLR است. در این نوع زبان‌ها می‌توان به وهله‌هایی از اشیاء موجود، خاصیت‌های جدیدی را متصل کرد.
به صورت خلاصه کار ConditionalWeakTable ایجاد نگاشتی است بین وهله‌هایی از اشیاء CLR (اشیایی غیرپویا) و خواصی که به آن‌ها می‌توان به صورت پویا انتساب داد. در کار GC اخلال ایجاد نمی‌کند و همچنین می‌توان به صورت همزمان از طریق تردهای مختلف، بدون مشکل با آن کار کرد.


پیاده سازی خواص الحاقی به کمک ConditionalWeakTable

در اینجا نحوه‌ی استفاده از ConditionalWeakTable را جهت اتصال خواصی جدید به وهله‌های موجود اشیاء مشاهده می‌کنید:
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace ConditionalWeakTableSamples
{
    public static class AttachedProperties
    {
        public static ConditionalWeakTable<object,
            Dictionary<string, object>> ObjectCache = new ConditionalWeakTable<object,
                Dictionary<string, object>>();

        public static void SetValue<T>(this T obj, string name, object value) where T : class
        {
            var properties = ObjectCache.GetOrCreateValue(obj);

            if (properties.ContainsKey(name))
                properties[name] = value;
            else
                properties.Add(name, value);
        }

        public static T GetValue<T>(this object obj, string name)
        {
            Dictionary<string, object> properties;
            if (ObjectCache.TryGetValue(obj, out properties) && properties.ContainsKey(name))
                return (T)properties[name];
            return default(T);
        }

        public static object GetValue(this object obj, string name)
        {
            return obj.GetValue<object>(name);
        }
    }
}
ObjectCache تعریف شده از نوع استاتیک است؛ بنابراین در طول عمر برنامه زنده نگه داشته خواهد شد، اما اشیایی که به آن منتسب می‌شوند، خیر. هرچند به ظاهر در متد GetOrCreateValue، یک وهله از شیءایی موجود را دریافت می‌کند، اما در پشت صحنه صرفا IntPtr یا اشاره‌گری به این شیء را ذخیره سازی خواهد کرد. به این ترتیب در کار GC اخلالی صورت نخواهد گرفت و شیء مورد نظر، تا پایان کار برنامه به اجبار زنده نگه داشته نخواهد شد.


کاربرد اول

اگر با ASP.NET کار کرده باشید حتما با IPrincipal آشنایی دارید. خواصی مانند Identity یک کاربر در آن ذخیره می‌شوند.
سؤال: چگونه می‌توان یک خاصیت جدید به نام مثلا Disclaimer را به وهله‌ای از این شیء افزود:
    public static class ISecurityPrincipalExtension
    {
        public static bool Disclaimer(this IPrincipal principal)
        {
            return principal.GetValue<bool>("Disclaimer");
        }

        public static void SetDisclaimer(this IPrincipal principal, bool value)
        {
            principal.SetValue("Disclaimer", value);
        }
    }
در اینجا مثالی را از کاربرد کلاس AttachedProperties فوق مشاهده می‌کنید. توسط متد SetDisclaimer یک خاصیت جدید به نام Disclaimer به وهله‌ای از شیءایی از نوع  IPrincipal  قابل اتصال است. سپس توسط متد  Disclaimer قابل دستیابی خواهد بود.

اگر صرفا قرار است یک خاصیت به شیءایی متصل شود، روش ذیل نیز قابل استفاده می‌باشد (بجای استفاده از دیکشنری از یک کلاس جهت تعریف خاصیت اضافی جدید استفاده شده‌است):
using System.Runtime.CompilerServices;

namespace ConditionalWeakTableSamples
{
    public static class PropertyExtensions
    {
        private class ExtraPropertyHolder
        {
            public bool IsDirty { get; set; }
        }

        private static readonly ConditionalWeakTable<object, ExtraPropertyHolder> _isDirtyTable
                = new ConditionalWeakTable<object, ExtraPropertyHolder>();

        public static bool IsDirty(this object @this)
        {
            return _isDirtyTable.GetOrCreateValue(@this).IsDirty;
        }

        public static void SetIsDirty(this object @this, bool isDirty)
        {
            _isDirtyTable.GetOrCreateValue(@this).IsDirty = isDirty;
        }
    }
}


کاربرد دوم

ایجاد Id منحصربفرد برای اشیاء برنامه.
فرض کنید در حال نوشتن یک Entity framework profiler هستید. طراحی فعلی سیستم Interception آن به نحو زیر است:
public void Closed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
{
}
سؤال: اینجا رویداد بسته شدن یک اتصال را دریافت می‌کنیم؛ اما ... دقیقا کدام اتصال؟ رویداد Opened را هم داریم اما چگونه این اشیاء را به هم مرتبط کنیم؟ شیء DbConnection دارای Id نیست. متد GetHashCode هم الزامی ندارد که اصلا پیاده سازی شده باشد یا حتی یک Id منحصربفرد را تولید کند. این متد با تغییر مقادیر خواص یک شیء می‌تواند مقادیر متفاوتی را ارائه دهد. در اینجا می‌خواهیم به ازای ارجاعی از یک شیء، یک Id منحصربفرد داشته باشیم تا بتوانیم تشخیص دهیم که این اتصال بسته شده، دقیقا کدام اتصال باز شده‌است؟
راه حل: خوب ... یک خاصیت Id را به اشیاء موجود متصل کنید!
using System;
using System.Runtime.CompilerServices;

namespace ConditionalWeakTableSamples
{
    public static class UniqueIdExtensions
    {
        static readonly ConditionalWeakTable<object, string> _idTable = 
                                    new ConditionalWeakTable<object, string>();

        public static string GetUniqueId(this object obj)
        {
            return _idTable.GetValue(obj, o => Guid.NewGuid().ToString());
        }

        public static string GetUniqueId(this object obj, string key)
        {
            return _idTable.GetValue(obj, o => key);
        }
    }
}
در اینجا مثالی دیگر از پیاده سازی و استفاده از ConditionalWeakTable را ملاحظه می‌کنید. اگر در کش آن ارجاعی به شیء مورد نظر وجود داشته باشد، مقدار Guid آن بازگشت داده می‌شود؛ اگر خیر، یک Guid به ارجاعی از شیء، انتساب داده شده و سپس بازگشت داده می‌شود. به عبارتی به صورت پویا یک خاصیت UniqueId به وهله‌هایی از اشیاء اضافه می‌شوند. به این ترتیب به سادگی می‌توان آن‌ها را ردیابی کرد و تشخیص داد که اگر این Guid پیشتر جایی به اتصال باز شده‌ای منتسب شده‌است، در چه زمانی و در کجا بسته شده است یا اصلا ... خیر. جایی بسته نشده‌است.


برای مطالعه بیشتر
The Conditional Weak Table: Enabling Dynamic Object Properties
How to create mixin using C# 4.0
Disclaimer Page using MVC
Extension Properties Revised
Easy Modeling
Providing unique ID on managed object using ConditionalWeakTable