Quantcast
Channel: ‫فید مطالب .NET Tips
Viewing all 1980 articles
Browse latest View live

‫کارهایی جهت بالابردن کارآیی Entity Framework #1

$
0
0
امروزه اهمیت استفاده از  Entity Framework بر هیچ کسی پوشیده نیست؛ اما در صورتی که به مفاهیم ابتدایی آن آشنایی نداشته باشید ممکن است در دام هایی بیفتید که استفاده از آن کم رنگ شود. در زیر به توصیه‌هایی جهت بالابردن کارآیی برنامه‌های مبتنی بر EF اشاره خواهیم کرد.
  • تنها دریافت رکوردهای مورد نیاز

EF راهی برای کار با اشیاء POCO، بدون آگاهی از مقادیرشان می‌باشد. اما هنگام فرآیند دریافت و یا به روزرسانی مقادیر این اشیاء از بانک اطلاعاتی، رفت و برگشت هایی انجام می‌شود که اطلاع از آنها بسیار حیاتی و ضروری است. به این فرآیند materiallization می‌گویند.
string city = "New York";
List<School> schools = db.Schools.ToList();
List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();

در کد بالا ابتدا کلیه ردیف‌های جدول از دیتابیس به حافظه منتقل می‌شود و سپس برروی آنها کوئری مورد نظر اعمال می‌گردد که بشدت می‌تواند برای یک برنامه - خصوصا برنامه وب - به‌دلیل دریافت کلیه‌ی ردیف‌های جدول بسیار مخرب باشد. کوئری فوق را می‌توان به صورت زیر اصلاح کرد:

List<School> newYorkSchools = db.Schools.Where(s => s.City == city).ToList();
یا
IQueryable<School> schools = db.Schools;
List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();
  • حداقل رفت و برگشت به دیتابیس

کد زیر را در نظر بگیرید:

string city = "New York";
List<School> schools = db.Schools.Where(s => s.City == city).ToList();
var sb = new StringBuilder();
foreach(var school in schools)
{
    sb.Append(school.Name);
    sb.Append(": ");
    sb.Append(school.Pupils.Count);
    sb.Append(Environment.NewLine);
}

هدف تکه کد بالا این است که تعداد دانش آموزان مدرسه‌های واقع در شهر New York را بدست آورد.

توجه داشته باشید:

    • یک مدرسه می‌تواند چندین دانش آموز داشته باشد (وجود رابطه یک به چند)
    • LazyLoading فعال است
    • تعداد مدرسه‌های شهر نیویورک 200 عدد می‌باشد

    اگر کوئری بالا را به‌وسیله‌ی یک پروفایلر بررسی نمایید، متوجه خواهید شد 1 + 200 رفت و برگشت به دیتابیس صورت گرفته است که به "N+1 select problem" معروف است. 1 مرتبه جهت دریافت لیست مدرسه‌های شهر نیویورک و 200 مرتبه جهت دریافت تعداد دانش آموزان هر مدرسه.

    بدلیل فعال بودن Lazy Loading، زمانیکه موجودیتی فراخوانی می‌شود، سایر موجودیت‌های وابسته به آن، زمانی از دیتابیس فراخوانی خواهند شد که به آن‌ها دسترسی پیدا کنید. در حلقه‌ی foreach هم به ازای هر مدرسه (200 مدرسه) شهر نیویورک یک رفت و برگشت انجام می‌شود.

      اما راه حل در این مورد خاص استفاده از Eager Loading است. خط دوم کد را بصورت زیر تغییر دهید:

      List<School> schools = db.Schools
          .Where(s => s.City == city)
          .Include(x => x.Pupils)
          .ToList();

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

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

      فرض کنید قصد دارید نام و نام خانوادگی دانش آموزان یک مدرسه را بدست آورید.

      int schoolId = 1;
      
      List<Pupil> pupils = db.Pupils
          .Where(p => p.SchoolId == schoolId)
          .ToList();
      
      foreach(var pupil in pupils)
      {
          textBox_Output.Text += pupil.FirstName + " " + pupil.LastName;
          textBox_Output.Text += Environment.NewLine;
      }
      کد بالا تمام ستون‌های یک جدول را همراه با ستون‌های نام و نام خانوادگی جدول مربوطه را از دیتابیس فراخوانی می‌کند که باعث بروز 2 مشکل زیر می‌گردد:
      1. انتقال اطلاعات بلا استفاده که ممکن است باعث کاهش کارآیی Sql Server I/O و شبکه و اشغال حافظه‌ی کلاینت گردد.
      2. کاهش کارآیی ایندکس گذاری. فرض کنید برروی جدول دانش آموزان ایندکسی شامل 2 ستون نام و نام خانوادگی تعریف کرده‌اید. با انتخاب تمام ستونهای جدول توسط خط دوم (select * from...) به کارآیی ایندکس گذاری برروی این جدول آسیب زده‌اید. توضیح بیشتر در اینجامطرح شده است.

      اما راه حل:

      var pupils = db.Pupils
          .Where(p => p.SchoolId == schoolId)
          .Select(x => new { x.FirstName, x.LastName })
          .ToList();
      • عدم تطابق نوع ستون با نوع خصیصه مدل

      فرض کنید نوع ستون جدول دانش آموزان (VARCHAR(20 است و خصیصه کدپستی مدل دانش آموز مانند زیر تعریف شده است:

      public string PostalZipCode { get; set; }

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

      string zipCode = "90210";
      var pupils = db.Pupils
          .Where(p => p.PostalZipCode == zipCode)
          .Select(x => new {x.FirstName, x.LastName})
          .ToList();
      کوئری بالا منجر به تولید SQL زیر می‌گردد: (نوع پارامتر ارسالی NVARCHAR است در حالی که ستون از نوع VARCHAR)

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

        همانطور که در شکل بالا مشخص است عملیات index scan از سایر عملیات‌ها پرهزینه‌تر است. حال به بررسی علت به‌وجود آمدن این عملیات پرهزینه خواهیم پرداخت.

        Index Scan زمانی رخ می‌دهد که اس کیو ال سرور مجبور است هر صفحه‌ی از ایندکس را بخواند و شرایط را (کدپستی برابر 90210) اعمال نماید و نتیجه را برگرداند. Index Scan بسیار هزینه بر است، چون اس کیو ال سرور، کل ایندکس را بررسی می‌نماید. نقطه‌ی مقابل و بهینه‌ی آن، Index Seek است که اس کیو ال سرور به صفحه‌ی مورد نظر ایندکسی که به شرایط نزدیک‌تر است، منتقل می‌گردد.

        خب چرا اس کیو ال سرور Index Scan را بجای Index Seek انتخاب کرده است؟!

        اشکالی در قسمت سمت چپ شکل بالا که به رنگ قرمز نمایش داده شده است، وجود دارد:

        Type conversion: Seek Plan for CONVERT_IMPLICIT(nvarchar(20), [Extent1].[PostalZipCode],0)=[@p__linq__0]
        [Extent1].[PostalZipCode]  بصورت غیر صریح به (NVARCHAR(20 تبدیل شده است. اما چرا؟
        پارامتر کوئری تولید شده‌ی توسط EF از نوع NVARCHAR است و تبدیل نوع NVARCHAR پارامتر کدپستی، که محدوده‌ی اطلاعات بیشتری (Unicode Strings) را نسبت به نوع VARCHAR ستون دارد، به‌دلیل از دست رفتن اطلاعات امکان پذیر نیست. به‌همین جهت برای مقایسه‌ی پارامتر کدپستی با ستون VARCHAR ، اس کیو ال سرور باید هر ردیف ایندکس را از VARCHAR به NVARCHAR تبدیل نماید که منجر به Index Scan می‌شود. اما راه حل بسیار ساده این است که فقط نوع خصیصه را با ستون جدول یکسان کنید.
        [Column(TypeName = "varchar")]
        public string PostalZipCode { get; set; }


          ‫برنامه نویسی اندروید با Xamarin.Android - قسمت سوم

          $
          0
          0
          در این مقاله می‌خواهیم یک لیست ساده را ایجاد کرده و داخل یک کنترل (View)، از نوع ListView قرار دهیم. همچنین با برخی از کنترل‌های پرکاربرد، برای چیدمان کنترل‌ها در اندروید آشنا می‌شویم.

          قبل از شروع به طراحی UI باید کمی با واحدهای اندازه گیری در اندروید آشنا شویم. بدانید و آگاه باشید که استفاده از واحد Pixel برای تعیین اندازه در اندروید کار بسیار اشتباهی است. طراح همیشه باید Density یا تراکم صفحه‌ی نمایش را در نظر بگیرد. تراکم صفحه‌ی نمایش به معنای تعداد پیکسل موجود در یک اینچ می‌باشد. اندازه‌ی 100 پیکسل در دستگاه‌های مختلف با (dpi(Dot Per Inchهای متفاوت به یک اندازه نیست.

          واحد dpi: اندروید واحد dpi را برای طراحی و چیدمان Layoutها معرفی کرده است. dpi مخفف Device Independent Pixel هست و معمولا بصورت dp نوشته می‌شود که یک واحد پیکسلی مجازی است و بر پایه‌ی یک صفحه نمایش با رزولوشن 160dpi طراحی شده‌است. به عبارت دیگر یک dp، یک پیکسل در یک صفحه‌ی نمایش با رزولوشن 160dpi می‌باشد. این واحد این اطمینان را به شما می‌دهد که یک View، در صفحه نمایش‌های با رزولوشن متفاوت، بطور مناسبی بزرگ یا کوچک می‌شود.

          واحد sp:مخفف Scale Independent Pixel است و شبیه dp عمل می‌کند؛ با این تفاوت که تنظیمات کاربر را (مثلا شخصی که بخاطر ضعف چشم اندازه‌ی قلم گوشی خود را بزرگ نموده) در محاسبات خود در نظر می‌گیرد. به دلیل آنکه از لحاظ زیبایی شناسی و همچنین چیدمان عناصر داخل UI زمانیکه از واحد اندازه گیری sp استفاده می‌کنیم ممکن است با مشکل مواجه شویم، بیشتر از dp استفاده می‌کنیم، مگر در بعضی مواقع آن هم برای مقداردهی به اندازه‌ی قلم!

          خوب! به سراغ فولدر Layout رفته و Main.axml را باز نمایید. به قسمت Source بروید.
          <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"><Button
                  android:id="@+id/MyButton"
                  android:layout_width="fill_parent"
                  android:layout_height="wrap_content"
                  android:text="@string/Hello" /></LinearLayout>
          در این سند axml یک LinearLayout مشاهده می‌نمایید. وقتی شما View را به LinearLayout اضافه می‌کنید، با توجه به اینکه orientation آن را vertical یا horizontal انتخاب کرده باشید، به صورت افقی و یا عمودی طرح بندی را انجام می‌دهد.

          layout_width و layout_height (مقداردهی آن‌ها الزامی است) ابعاد layout ما را مشخص می‌کنند. مقدار fill_parent دیگر منسوخ شده و به جای آن match_parent استفاده می‌شود و به معنای آن است که تمام فضای موجود در کنترل را اشغال کند. مقدار دیگری که می‌توان به آن نسبت داد (و در layout_height مربوط به Button مشاهده می‌نمایید)، wrap_content می‌باشد که اعلام می‌کند فقط به میزان مورد نیاز برای محتویات، کنترل والد را اشغال کند. البته با تغییر میزان محتویات، اندازه‌ی کنترل متغییر است. شما می‌توانید مقادیر عددی را هم با واحد dp یا حتی pixel (که اصلا توصیه نمی‌شد) جایگزین نمایید.

          در ادامه، کنترل (که در اندروید به آن View گفته می‌شود) Button را حذف نمایید و به جای آن یک ListView را قرار دهید و نامی را به آن نسبت دهید. ListView از کاربردی‌ترین و مهم‌ترین کنترل‌های اندروید می‌باشد. ListView شامل قسمت‌های زیر است:
          Rows:قسمت نمایش دهنده‌ی داده‌ها.
          Adapter: یک کلاس که وظیفه‌ی انقیاد منبع داده را به ListView، بر عهده دارد.
          Fast Scrolling: یک دسته(handle) که به کاربر اجازه می‌دهد تا در طول ListView حرکت کند.
          Section Index: یک view می‌باشد و جایگاه لیت را هنگام اسکرول مشخص میکند و معمولا در Contacts گوشی بصورت ابتدای حروف نام مخاطبین خود مشاهده کرده‌اید.
          Layout زیر را در نظر بگیرید:
          <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent"><ListView
                  android:background="#fff"
                  android:id="@+id/NameListView"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent" /></LinearLayout>  
          به MainActivity.cs بروید و کدهای مربوط به Button قبلی را که با ListView جایگزین کرده‌ایم، حذف نمایید. متد OnCreate به این صورت می‌باشد:
          protected override void OnCreate(Bundle bundle)
                  {
                      base.OnCreate(bundle);
                      SetContentView(Resource.Layout.Main);
          
                      List<string> namesList = new List<string>
                      {
                          "Mohammad","Fatemeh","Ali","Hasan","Husein","Mohsen","Mahdi",
                      };
                      var namesAdapter = new ArrayAdapter<string>
                          (this, Android.Resource.Layout.SimpleListItem1, namesList);
          
                      var listview = FindViewById<ListView>(Resource.Id.NameListView);
                      listview.Adapter = namesAdapter;
                  }
          همانطور که گفته شد SetContentView مشخص کننده‌ی layout مورد نظر ما برای نمایش می‌باشد. می‌توان بدون هیچ layout خاصی با کدهای سی شارپ، کنترل‌های مورد نظر را ایجاد کرد که کار زمانبری است؛ ولی بعضی مواقع مجبور به این کار هستیم.
          namesList یک لیست ساده از نوع string با مقدار دهی اولیه است.
          ArrayAdapter یک کلاس Adapter توکار می‌باشد که یک آرایه (یا لیست) را از نوع string، برای نمایش به ListView متصل می‌کند (bind). نوع جنریک آن یعنی <ArrayAdapter<T برای نوع‌های دیگر هم استفاده می‌شود. در واقع Adapter با دریافت یک لیست برای نمایش و یک Layout برای تعیین نوع نمایش، به ازای هر سطر از اطلاعات یک View را با اطلاعات آن سطر به سمت ListView ارسال می‌کند. در اینجا ما در سازنده‌ی ArrayAdapter با استفاده از Resourceهای توکار اندروید که از طریق Android.Resource به آن‌ها دسترسی داریم، یک layout ساده را شامل یک TextView(مانند label و یا textBlock)، به همراه namesList، برای Adapter ارسال کردیم.
          متد FindViewById با توجه به Layout معرفی شده‌ی به Activity، به دنبال View با Id مورد نظر می‌پردازد. مهم نیست که در Layoutهای جداگانه نام‌های یکسانی استفاده کنید. این متد در کلاس View قرار دارد و تمام کنترل(View)ها، فرزند آن می‌باشند. در اینجا از نوع جنریک آن استفاده شده که عمل تبدیل View به ListView را خود متد بر عهده بگیرد.
          در انتها Adapter مورد نظر به ویژگی Adpater کنترل ListView اضافه می‌شود.

          ListView کنترل بسیار منعطفی می‌باشد. برخی ویژگی‌ها آن را در زیر می‌توانید مشاهده بفرمایید:
          • android:dividerHeight                    // ارتفاع جداکننده‌ی سطرها
          • android:divider                            // رنگ جداکننده‌ی سطرها
          • android:layoutAnimation               // انیمیشن برای layoutها 
          • android:background                    // رنگ ضمینه را مشخص میکند. البته میتوانید یک style را به ان نسبت دهید

          خوب؛ حالا بیایید یک ListView را با ظاهر و Adapter سفارشی بسازیم.
          ابتدا باید یک Layout را طراحی کنیم تا به ازای هر سطر برای ListView ارسال شود. با استفاده از Add->New item یک Layout را به فولدر layout اضافه کنید.
          کد زیر را درون فایل axml مربوطه کپی کنید. 
          <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:padding="14dp"><TextView
                  android:text=""
                  android:gravity="center_vertical"
                  android:layout_width="wrap_content"
                  android:layout_height="match_parent"
                  android:id="@+id/idTextView" /><TextView
                  android:text=""
                  android:gravity="center_vertical"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:id="@+id/nameTextView"
                  android:layout_marginLeft="14dp" /></LinearLayout>
          کلاس زیر (یا هر کلاس دلخواه دیگری) را به عنوان مدل برنامه اضافه کنید.
          namespace DotSystem.ir.App1.Model
          {
              public class Person
              {
                  public int Id { get; set; }
                  public string PersonName { get; set; }
          
              }
          حالا باید Adapter خود را بسازیم. ابتدا کلاسی را با نام PersonAdapter به برنامه اضافه نمایید. این کلاس باید از کلاس BaseAdapter (نوع جنریک آن هم موجود می‌باشد) و یا فرزندان آن ArrayAdapter، CursorAdapter و ... ارث بری نماید. اگر مستقیما از BaseAdapter استفاده کنیم، به دلیل Abstract بودن تعدادی از متدها و Propertyها مجبور به override کردن آن‌ها می‌شویم. ما در اینجا از BaseAdapter استفاده می‌کنیم. کد زیر را در نظر بگیرید:
          namespace DotSystem.ir.App1.Adapters
          {
              public class PersonAdapter : BaseAdapter<Model.Person>
              {
                  public override Person this[int position]
                  {
                      get
                      {
                          throw new NotImplementedException();
                      }
                  }
          
                  public override int Count
                  {
                      get
                      {
                          throw new NotImplementedException();
                      }
                  }
          
                  public override long GetItemId(int position)
                  {
                      throw new NotImplementedException();
                  }
          
                  public override View GetView(int position, View convertView, ViewGroup parent)
                  {
                      throw new NotImplementedException();
                  }
              }
          }
          BaseAdapter شامل یک Indexer برای دسترسی آسان به Itemهای لیست، یک ویژگی برای برگرداندن تعداد آیتم‌ها، متدی برای برگرداندن Id هر آیتم و مهمترین بخش آن یعنی متد GetView که برای نمایش هر آیتمی یک بار اجرا می‌شود و Layout مورد نظر ما را با اطلاعات پر کرده و به سمت ListView می‌فرستد.

          در اینجا ما به چند فیلد داخل کلاس احتیاج داریم.
          • لیست اطلاعات مورد نظر.
          • Activity جاری که Adapter را استفاده می‌کند.
          بنابراین دو فیلد را به همراه متد سازنده، برای مقدار دهی آن‌ها اضافه کرده و کلاس بالا را نیز تکمیل می‌کنیم.
          namespace DotSystem.ir.App1.Adapters
          {
              public class PersonAdapter : BaseAdapter<Person>
              {
                  protected Activity _activity = null;
                  protected List<Person> _list = null;
                  public PersonAdapter(Activity activity, List<Person> list)
                  {
                      _activity = activity;
                      _list = list;
                  }
                  public override Person this[int position]
                  {
                      get
                      {
                          return _list[position];
                      }
                  }
          
                  public override int Count
                  {
                      get
                      {
                          return _list.Count;
                      }
                  }
          
                  public override long GetItemId(int position)
                  {
                      return _list[position].Id;
                  }
          
                  public override View GetView(int position, View convertView, ViewGroup parent)
                  {
                      throw new NotImplementedException();
                  }
              }
          }
          در این مرحله باید متد GetView را پیاده سازی کنیم. به پیاده سازی زیر دقت کنید:
          public override View GetView(int position, View convertView, ViewGroup parent)
                  {
                      if (convertView == null)
                          convertView = _activity.LayoutInflater
                              .Inflate(Resource.Layout.PersonListViewItemLayout, parent, false);
          
                      var idTextView = convertView.FindViewById<TextView>(Resource.Id.idTextView);
                      var nameTextView = convertView.FindViewById<TextView>(Resource.Id.NameListView);
          
                      var persion = _list[position];
          
                      idTextView.Text = persion.Id.ToString();
                      nameTextView.Text = persion.PersonName;
          
                      return convertView;
                  }
          در مرحله‌ی اول بررسی می‌کنیم که اگر convertView برابر با null بود، آن را مقدار دهی کند. این نکته بسیار مهم است، چرا که ListView برای کارآیی بهتر فقط آن آیتم هایی را که در دید کاربر باشد، با متد GetView لود میکند و دوباره با اسکرول لیست، عمل فراخوانی متد انجام می‌شود؛ البته اینبار بدون مقدار null برای convertView. بنابراین اگر دیدید که هنگام اسکرول لیست، آیتم‌ها جابجا شدند، این بخش از متد را دوباره بررسی نمایید.
          Inflate متدی است که Layout و نگه دارنده‌ی  layout را گرفته و آن را برای نمایش در Activity آماده می‌کند. سپس دو View را که در Layout ما وجود دارند، گرفته مقدار دهی می‌کنیم و در آخر هم convertView را برای نمایش به سمت ListView می‌فرستیم.
          حال متد OnCreate را به صورت زیر بازنویسی نموده و برنامه را اجرا می‌کنیم.
          protected override void OnCreate(Bundle bundle)
                  {
                      base.OnCreate(bundle);
                      SetContentView(Resource.Layout.Main);
          
                      List<Model.Person> personList = new List<Model.Person>
                      {
                          new Model.Person() {Id = 1, PersonName = "Mohammad", },
                          new Model.Person() {Id = 2, PersonName = "Ali", },
                          new Model.Person() {Id = 3, PersonName = "Fatemeh", },
                          new Model.Person() {Id = 4, PersonName = "hasan", },
                          new Model.Person() {Id = 5, PersonName = "Husein", },
                          new Model.Person() {Id = 6, PersonName = "Mohsen", },
                          new Model.Person() {Id = 14, PersonName = "Mahdi", },
                      };
                      var personAdapter = new Adapters.PersonAdapter(this, personList);
          
                      var listview = FindViewById<ListView>(Resource.Id.NameListView);
                      listview.Adapter = personAdapter;
                  }

          ‫توسعه سیستم مدیریت محتوای DNTCms - قسمت پنجم

          $
          0
          0
          در این قسمت به بررسی بخش Collections (امکان ساخت گروه‌های شخصی برای انتشار مطالب خود (توسط کاربران) با اعمال دسترسی‌های مختلف) ، بخش آگهی‌ها، سیستم لاگ عملیات کاربران و مدل‌های سیستمی می‌پردازیم.
          در مدل‌های سیستم، یک تغییر کلی به منظور نگهداری آخرین تغییر دهنده و آخرین تاریخ تغییر در رکورد‌ها، ایجاد شده است. کلاس پایه‌ی زیر به منظور کپسوله کردن یکسری خصوصیات تکراری در نظر گرفته شده است.
            public abstract class BaseEntity
              {
                  #region Properties
                  /// <summary>
                  /// gets or sets Identifier of this Entity
                  /// </summary>
                  public virtual long Id { get; set; }
                  /// <summary>
                  /// gets or sets date that this entity was created
                  /// </summary>
                  public virtual DateTime CreatedOn { get; set; }
                  /// <summary>
                  /// gets or sets Date that this entity was updated
                  /// </summary>
                  public virtual DateTime ModifiedOn { get; set; }
                  /// <summary>
                  /// indicate this entity is Locked for Modify
                  /// </summary>
                  public virtual bool ModifyLocked { get; set; }
                  /// <summary>
                  /// gets or sets date that this entity repoted last time
                  /// </summary>
                  public virtual DateTime? ReportedOn { get; set; }
                  /// <summary>
                  /// gets or sets counter for Content's report
                  /// </summary>
                  public virtual int ReportsCount { get; set; }
                  /// <summary>
                  /// gets or sets TimeStamp for prevent concurrency Problems
                  /// </summary>
                  public virtual byte[] RowVersion { get; set; }
                  #endregion
          
                  #region NavigationProperties
                  /// <summary>
                  /// gets ro sets User that Modify this entity
                  /// </summary>
                  public virtual User ModifiedBy { get; set; }
                  /// <summary>
                  /// gets ro sets Id of  User that modify this entity
                  /// </summary>
                  public virtual long? ModifiedById { get; set; }
                  /// <summary>
                  /// gets ro sets User that Create this entity
                  /// </summary>
                  public virtual User CreatedBy { get; set; }
                  /// <summary>
                  /// gets ro sets User that Create this entity
                  /// </summary>
                  public virtual long CreatedById { get; set; }
                  #endregion
              }
          با توجه به امکان تغییر نام کاربری توسط کاربر در سیستم، نگه داری صرفا نام کاربری آخرین تغییر دهنده، مفید نخواهد بود. شبیه به این کار را در سیستم Decisionنیز می‌توانید مشاهده کنید. خصوصیاتی که نیاز به توضیح دارند :
          • ReportedOn : نگهداری آخرین تاریخ اخطار 
          • ModifyLocked : به منظور ممانعت از ویرایش 
          • CreatedBy,CreatedById : به منظور ایجاد ارتباط یک به چند بین کاربر و سایر موجودیت‌ها (به عنوان ایجاد کننده)
          • ModifiedBy , ModifiedById : به منظور ایجاد ارتباط یک به چند بین کاربر و سایر موجودیت‌ها (به عنوان آخرین تغییر دهنده)

          مدل کلکسیون (کالکشن ،Collection)

          بخش Collections می‌تواند برای کاربران امکان انتشار مطالب گروهبندی شده را بر اساس موضوع‌های مختلف، مهیا کند. کاربران بعد از کسب امتیازات لازم با استفاده از مهیا کردن محتوای وبلاگ یا فعالیت‌های آنها در انجمن و ... می‌توانند دسترسی لازم را برای ساخت Collections‌‌های خود، داشته باشند.  
           /// <summary>
              /// Represents the Collection for group posts by topic 
              /// </summary>
              public class Collection : GuidBaseEntity
              {
                  #region Properties
                  /// <summary>
                  /// gets or sets name of collection
                  /// </summary>
                  public virtual string Name { get; set; }
                  /// <summary>
                  /// gets or sets Alternative SlugUrl
                  /// </summary>
                  public virtual string SlugUrl { get; set; }
                  /// <summary>
                  /// gets or sets some description of group
                  /// </summary>
                  public virtual string Description { get; set; }
                  /// <summary>
                  /// gets or sets description that indicate how to pay 
                  /// </summary>
                  public virtual string HowToPay { get; set; }
                  /// <summary>
                  /// gets or sets Visibility Type 
                  /// </summary>
                  public virtual CollectionVisibility Visibility { get; set; }
                  /// <summary>
                  /// gets or sets color of Collection's Cover
                  /// </summary>
                  public virtual string Color { get; set; }
                  /// <summary>
                  /// gets or sets Name of Image that used as Cover
                  /// </summary>
                  public virtual string Photo { get; set; }
                  /// <summary>
                  /// gets or sets name of tags seperated by comma that assosiated with this content fo increase performance
                  /// </summary>
                  public virtual string TagNames { get; set; }
                  /// <summary>
                  /// indicate this collection is active or not
                  /// </summary>
                  public virtual bool IsActive { get; set; }
                  #endregion
          
                  #region  NavigationProperties
                 
                  /// <summary>
                  /// get or set collection of attachments that attached in this group
                  /// </summary>
                  public virtual ICollection<CollectionAttachment> Attachments { get; set; }
                  /// <summary>
                  /// get or set tags of collection
                  /// </summary>
                  public virtual ICollection<Tag> Tags { get; set; }
                  /// <summary>
                  /// get or set List Of Posts that Associated with this Collection
                  /// </summary>
                  public virtual ICollection<CollectionPost> Posts { get; set; }
                  /// <summary>
                  /// get or set Users that they are Member of this collection if visibility is Custom
                  /// </summary>
                  public virtual ICollection<User> Memebers { get; set; }
                  #endregion
              }
             public enum  CollectionVisibility
              {
                  Friends,
                  OnlyMe,
                  Public,
                  NotFree,
                  Custom
              }
          مدل بالا نشان دهنده‌ی کلکسیون‌های ایجاد شده‌ی توسط کاربران می‌باشد. خصوصیاتی که نیاز به توضیح دارند:
          • Visibility : برای اعمال محدودیت دسترسی به یک کلکسیون در نظر گرفته شده است. از نوع، نوع داده‌ی شمارشی CollectionVisibility پیاده سازی شده‌ی دربالا، می‌باشد. حالت Custom برای زمانی است که نیاز است صرفا یک سری از کاربران بدون هزینه‌، دسترسی برای مطالعه‌ی این کلکسیون داشته باشند. حالت NotFree هم برای زمانی است که کاربرانی که هزینه‌ی مورد نظر را پرداخت کرده باشند، به عنوان عضو به این کلکسیون می‌توانند دسترسی داشته باشند.
          • Members : به منظور اعمال ارتباط چند به چند بین مدل کاربر و مدل کلکسیون، در نظر گرفته شده است و زمانی این لیست اعضا خالی نیست که حالت Visibility با NotFree یا Custom مقدار دهی شده باشد.
          • Tags : برای یافتن راحت‌تر کلکسیون‌های مورد نظر کاربران، یک ارتباط چند به چند بین کلکسیون‌ها و مخزن برچسب مطرح شده‌ی در مقاله اول، در نظر گرفته شده است. 
          • TagNames : برای افزایش کارآیی سیستم در نظر گرفته شده است و نام برچسب‌های در ارتباط با کلکسیون را در خود به صورت جدا شده با (,) نگهداری می‌کند.
          • Posts : لیست پست‌هایی است که توسط مدیر کلکسیون در آن ارسال می‌شود. لذا یک ارتباط یک به چند بین کلکسیون‌ها و CollectionPost در نظر گرفته شده است.
          • Attachments : لیست فایل‌هایی است که در کلکسیون بارگذاری شده‌اند (در ادامه پیاده سازی خواهد شد).
          • Owner , OwnerId : هر کلکسیون نیز یک صاحب خواهد داشت. بدین منظور یک ارتباط یک به چند بین کاربر و کلکسیون برقرار شده است.
          • HowToPay : توضیحاتی در مورد نحوه‌ی پرداخت هزینه (در صورتی که Visibility این کلکسیون NotFree باشد).

          مدل پست‌های کلکسیون ها

           public class CollectionPost : GuidBaseEntity
              {
                  #region Properties
                  /// <summary>
                  /// indicate this post should be pin
                  /// </summary>
                  public virtual bool IsPin { get; set; }
                  /// <summary>
                  /// gets or sets the blog pot body
                  /// </summary>
                  public virtual string Body { get; set; }
                  /// <summary>
                  /// gets or sets the content title
                  /// </summary>
                  public virtual string Title { get; set; }
                  /// <summary>
                  /// gets or sets value  indicating Custom Slug
                  /// </summary>
                  public virtual string SlugAltUrl { get; set; }
                  /// <summary>
                  /// gets or sets a value indicating whether the content comments are allowed 
                  /// </summary>
                  public virtual bool AllowComments { get; set; }
                  /// <summary>
                  /// indicate comments should be approved before display
                  /// </summary>
                  public virtual bool ModerateComments { get; set; }
                  /// <summary>
                  /// gets or sets viewed count 
                  /// </summary>
                  public virtual long ViewCount { get; set; }
                  /// <summary>
                  /// Gets or sets the total number of  approved comments
                  /// <remarks>The same as if we run Item.Comments.Count()
                  /// We use this property for performance optimization (no SQL command executed)
                  /// </remarks>
                  /// </summary>
                  public virtual int ApprovedCommentsCount { get; set; }
                  /// <summary>
                  /// Gets or sets the total number of  unapproved comments
                  /// <remarks>The same as if we run Item.Comments.Count()
                  /// We use this property for performance optimization (no SQL command executed)
                  /// </remarks>
                  /// </summary>
                  public virtual int UnApprovedCommentsCount { get; set; }
                  /// <summary>
                  /// gets or sets rating complex instance
                  /// </summary>
                  public virtual Rating Rating { get; set; }
                  /// <summary>
                  /// gets or sets information of User-Agent
                  /// </summary>
                  public virtual string Agent { get; set; }
                  /// <summary>
                  /// indicate users can share this post
                  /// </summary>
                  public virtual bool IsEnableForShare { get; set; }
                  #endregion
          
                  #region NavigationProperties
                  /// <summary>
                  /// get or set comments of this post
                  /// </summary>
                  public virtual ICollection<CollectionComment> Comments { get; set; }
                  /// <summary>
                  /// gets or sets collection that associated with this post
                  /// </summary>
                  public virtual Collection Collection { get; set; }
                  /// <summary>
                  /// gets or sets id of collection that associated with this post
                  /// </summary>
                  public virtual Guid CollectionId { get; set; }
                  #endregion
              }
          مدل بالا نشان دهنده‌ی پست‌های ارسالی در کلکسیون‌ها می‌باشد. خصوصیاتی که نیاز به توضیح دارند:
          • IsEnableForShare : اگر با مقدار True مقدار دهی شده باشد ، امکان به اشتراک گذاری آن درشبکه‌های اجتماعی وجود خواهد داشت. 
          • Comments : اگر مقدار AllowComments مربوط به پست ارسالی true باشد، در آن صورت امکان نظر دهی به پست  هم امکان پذیر خواهد بود. برای برقراری ارتباط یک به چند بین مدل پست کلکسیون و نظرات کلکسیون، این لیست در مدل بالا گنجانده شده است.
          • ModerateComments : اگر برای پست خاصی، با مقدار true مقدار دهی شده باشد، نظرات آن پست قبل از نمایش باید توسط مدیر آن کلکسیون تأیید شوند.
          • Author , AuthorId : در واقع ارسال کننده‌ی تمام پست‌ها، همان صاحب کلکسیون می‌باشد. ولی برای راحتی واکشی لیست پست‌های ارسالی کاربر مورد نظر یک ارتباط یک به چند بین کاربر و پست‌های ارسالی را در کلکسیون اعمال کرده‌ایم.
          • Collection , CollectionId : کلید خارجی ما در دیتابیس ایجاد شده خواهد بود که نشان دهنده‌ی ارتباط یک به چند بین کلکسیون و پست‌ها می‌باشد.
          • IsPin : اگر لازم است پستی به عنوان اولین پست در کلکسیون نمایش داده شود، این خصوصیت برای آن true خواهد بود.
          • ApprovedCommentsCount , UnApprovedCommentsCount: برای افزایش کارآیی سیستم در نظر گرفته شده است و هنگام درج نظر جدید یا حذف نظر، ویرایش خواهند شد. 

          مدل نظرات ارسالی در کلکسیون ها

           public class CollectionComment 
              {
                  #region Ctor
                  public CollectionComment()
                  {
                      Id = SequentialGuidGenerator.NewSequentialGuid();
                      CreatedOn = DateTime.Now;
          
                  }
                  #endregion
          
                  #region Properties
                  /// <summary>
                  /// get or set identifier of record
                  /// </summary>
                  public virtual Guid Id { get; set; }
                  /// <summary>
                  /// gets or sets date of creation 
                  /// </summary>
                  public virtual DateTime CreatedOn { get; set; }
                  /// <summary>
                  /// gets or sets body of blog post's comment
                  /// </summary>
                  public virtual string Body { get; set; }
                  /// <summary>
                  /// gets or sets body of blog post's comment
                  /// </summary>
                  public virtual Rating Rating { get; set; }
                  /// <summary>
                  /// gets or sets informations of agent
                  /// </summary>
                  public virtual string UserAgent { get; set; }
                  /// <summary>
                  /// indicate this comment is Approved 
                  /// </summary>
                  public virtual bool IsApproved { get; set; }
                  /// <summary>
                  /// gets or sets Ip Address of Creator
                  /// </summary>
                  public virtual string CreatorIp { get; set; }
                  /// <summary>
                  /// gets or sets datetime that is modified
                  /// </summary>
                  public virtual DateTime? ModifiedOn { get; set; }
                  /// <summary>
                  /// gets or sets counter for report this comment
                  /// </summary>
                  public virtual int ReportsCount { get; set; }
                  /// <summary>
                  /// indicate this entity is Locked for Modify
                  /// </summary>
                  public virtual bool ModifyLocked { get; set; }
                  /// <summary>
                  /// gets or sets date that this entity repoted last time
                  /// </summary>
                  public virtual DateTime? ReportedOn { get; set; }
                  #endregion
          
                  #region NavigationProperties
                  /// <summary>
                  /// gets or sets post that this comment added it
                  /// </summary>
                  public virtual CollectionPost Post { get; set; }
                  /// <summary>
                  /// gets or sets id of post that this comment added it
                  /// </summary>
                  public virtual Guid PostId { get; set; }
                  /// <summary>
                  /// get or set user that create this record
                  /// </summary>
                  public virtual User Creator { get; set; }
                  /// <summary>
                  /// get or set Id of user that create this record
                  /// </summary>
                  public virtual long CreatorId { get; set; }
                  /// <summary>
                  /// gets or sets CollectionComment's identifier for Replying and impelemention self referencing
                  /// </summary>
                  public virtual Guid? ReplyId { get; set; }
                  /// <summary>
                  /// gets or sets Collection's comment for Replying and impelemention self referencing
                  /// </summary>
                  public virtual CollectionComment Reply { get; set; }
                  /// <summary>
                  /// get or set collection of Collection's comment for Replying and impelemention self referencing
                  /// </summary>
                  public virtual ICollection<CollectionComment> Children { get; set; }
                  #endregion
              }

          مدل بالا نشان دهنده‌ی نظرات ارسالی برای پست‌های کلکسیون‌ها می‌باشد. صرفا کاربران عضو سیستم این اجازه را در صورتی خواهند داشت که برای پست مورد نظر خصوصیت AllowComments با مقدار true مقدار دهی شده باشد
          حالت درختی آن مشخص است. برای اعمال ارتباط یک به چند بین پست‌ها و نظرات، از CollectionPost و CollectionPostId استفاده خواهد شد.

          • IsApproved : برای زمانی استفاده خواهد شد که خصوصیت ModerateComments پست مورد نظر با مقدار true مقدار دهی شده باشد. 
          • ReportsCount : به مانند بخش‌های قبل، تعداد اخطار‌های داده شده‌ی برای یک نظر را نشان خواهد داد. 
          • Creator,CreatorId : ارسال کننده‌ی نظر می‌باشد و برای ایجاد ارتباط یک به چند بین کاربر و نظرات کلکسیون‌ها در نظر گرفته شده‌اند. 
          • ReportedOn : نگه داری آخرین تاریخ اخطار 
          • ModifyLocked : به منظور ممانعت از ویرایش

          مدل فایل‌های ضمیمه کلکسیون ها

          public class CollectionAttachment : BaseAttachment
              {
                  #region NavigationProperties
                  /// <summary>
                  /// gets or sets Collection  that this file attached 
                  /// </summary>
                  public virtual Collection Collection { get; set; }
                  /// <summary>
                  /// gets or sets Id of Collection  that this file attached
                  /// </summary>
                  public virtual Guid? CollectionId { get; set; }
                  #endregion
              }
          اگر یادتان باشد در مقاله‌ی دوم در مورد نحوه‌ی مدیریت فایل‌ها بحث شد و در نتیجه تصمیم گرفته شد که از ارث بری TPH استفاده کنیم. مدل بالا نیز یکی از SubClass‌های ما خواهد بود. با توجه به اینکه شاید Privacy فایل‌های ارسالی یک گروه مهم باشد (در صورت خصوصی بودن یا پولی بودن مطالعه‌ی کلکسیون)  نیاز شد مدل بالا را نیز داشته باشیم. برای اعمال ارتباط یک به چند بین مدل بالا و مدل کلکسیون، از خصوصیت‌های Colllection , CollectionId استفاده خواهیم کرد. دقت کنید که لازم است CollectionId به صورت نال پذیر درنظر گرفته شود.

          مدل آگهی ها

          /// <summary>
              /// Represents Announcement For Announcement Section
              /// </summary>
              public class Announcement : BaseContent
              {
                  #region Properties
                  /// <summary>
                  /// gets or sets Date that this Announcement will Expire
                  /// </summary>
                  public virtual DateTime? ExpireOn { get; set; }
                  /// <summary>
                  /// indicate this accouncement is approved by admin if announcementSetting.Moderate==true
                  /// </summary>
                  public virtual bool IsApproved { get; set; }
                  #endregion
          
                  #region NavigationProperties
                  /// <summary>
                  /// get or set Collection of Comments for this Announcement
                  /// </summary>
                  public virtual ICollection<AnnouncementComment> Comments { get; set; }
          
                  #endregion
              }
          مدل بالا نشان دهنده‌ی آگهی‌ها سیستم خواهد بود. همان طور که مشخص است، این مدل نیز از کلاس پایه BaseContent ارث بری کرده و علاوه بر آن یکسری خصوصیت دیگر را به شرح زیر دارد:
          • ExpireOn : زمان انقضای آگهی 
          • IsApproved : به منظور اعمال مدیریتی در نظر گرفته شده است
          • Comments : اگر امکان ارسال نظرات برای آگهی از بخش تنظیمات فعال باشد، این لیست نظرات ما را نگه داری خواهد کرد. لذا یک رابطه‌ی یک به چند بین نظرات و آگهی‌ها خواهد بود.

          مدل نظرات آگهی ها

          /// <summary>
              /// Repersents Comment For Announcement
              /// </summary>
              public class AnnouncementComment : BaseComment
              {
                  #region NavigationProperties
                  /// <summary>
                  /// gets or sets body of announcement's comment
                  /// </summary>
                  public virtual long? ReplyId { get; set; }
                  /// <summary>
                  /// gets or sets body of announcement's comment
                  /// </summary>
                  public virtual AnnouncementComment Reply { get; set; }
                  /// <summary>
                  /// gets or sets body of announcement's comment
                  /// </summary>
                  public virtual ICollection<AnnouncementComment> Children { get; set; }
                  /// <summary>
                  /// gets or sets announcement that this comment sent to it
                  /// </summary>
                  public virtual Announcement Announcement { get; set; }
                  /// <summary>
                  /// gets or sets announcement'Id that this comment sent to it
                  /// </summary>
                  public virtual long AnnouncementId { get; set; }
                  #endregion
              }
          مدل بالا  نشان دهنده‌ی نظرات ارسالی برای آگهی‌ها می‌باشد. از کلاس پایه‌ی مطرح شده‌ی در مقاله‌ی اولارث بری کرده و علاوه بر آن ساختار درختی آن مشخص است و همچنین برای برقراری ارتباط یک به چند با مدل آگهی‌ها، خصوصیات Announcement  , AnnouncementId در نظر گرفته شده‌اند.

          مدل سیستم لاگ عملیات کاربران

          /// <summary>
              /// Represent The Operation's log
              /// </summary>
              public class AuditLog
              {
                  #region Ctor
                  /// <summary>
                  /// 
                  /// </summary>
                  public AuditLog()
                  {
                      Id = SequentialGuidGenerator.NewSequentialGuid();
                      OperatedOn = DateTime.Now;
                  }
                  #endregion
          
                  #region Properties
                  /// <summary>
                  /// sets or gets identifier of AuditLog
                  /// </summary>
                  public virtual Guid Id { get; set; }
                  /// <summary>
                  /// sets or gets description of Log
                  /// </summary>
                  public virtual string Description { get; set; }
                  /// <summary>
                  /// sets or gets when log is operated
                  /// </summary>
                  public virtual DateTime OperatedOn { get; set; }
                  /// <summary>
                  /// sets or gets log's section
                  /// </summary>
                  public virtual AuditSection Section { get; set; }
                  #endregion
          
                  #region NavigationProperties
                  /// <summary>
                  /// sets or gets log's creator
                  /// </summary>
                  public virtual User OperatedBy { get; set; }
                  /// <summary>
                  /// sets or gets identifier of log's creator
                  /// </summary>
                  public virtual long OperatedById { get; set; }
                  #endregion
              }
          
            public enum AuditSection
              {
                  Blog,
                  News,
                  Forum,
                  ...
              }
          مدل بالا نگهدارنده زمان اکشن انجام شده، توسط کاربری که انجام شده و یه سری توضیحات کلی می‌باشد. به منظور اعمال ارتباط یک به چند مابین کاربر و مدل بالا، خصوصیات OperatedBy و OperatedById را در نظر گرفته‌ایم. خصوصیت Section که از نوع، نوع داده شمارشی AuditSection می‌باشد، می‌تواند برای جداسازی این لاگ‌های ذخیره شده، مفید باشد.

          مدل دامین‌های ممنوع

          در پنل مدیریت امکانی را خواهیم داشت تا یکسری از دامین‌های مد نظر را که نمی‌خواهیم در محتوای سیستم، آدرس صفحات آنها دیده شوند و همچنین برای عدم ارسال و دریافت پینگ بک‌ها لحاظ شوند، ثبت کنیم.
           /// <summary>
              /// Represents Domain that is banned
              /// </summary>
              public class BannedDomain
              {
                  #region Propertie
                  /// <summary>
                  /// gets or sets identifier of Domain
                  /// </summary>
                  public virtual Guid Id { get; set; }
                  /// <summary>
                  /// gets or sets DomainName
                  /// </summary>
                  public virtual string Name { get; set; }
                  /// <summary>
                  /// gets or sets Date that this record added
                  /// </summary>
                  public virtual DateTime BannedOn { get; set; }
                  #endregion
              }

          مدل کلمات ممنوع 

           /// <summary>
              /// Represents the banned words
              /// </summary>
              public class BannedWord
              {
                  #region Ctor
                  /// <summary>
                  /// Create one instance of <see cref="BannedWord"/>
                  /// </summary>
                  public BannedWord()
                  {
                      Id = SequentialGuidGenerator.NewSequentialGuid();
                  }
                  #endregion
          
                  #region Properties
                  /// <summary>
                  /// gets or sets identifier of BannedWord
                  /// </summary>
                  public Guid Id { get; set; }
                  /// <summary>
                  /// gets or sets Bad word
                  /// </summary>
                  public string BadWord { get; set; }
                  /// <summary>
                  /// gets or sets Good replaceword
                  /// </summary>
                  public string GoodWord { get; set; }
                  /// <summary>
                  /// indicating that this Word is spam
                  /// </summary>
                  public bool IsStopWord { get; set; }
                  #endregion
          
              }
          مدل بالا نشان دهنده‌ی کلماتی است که لازم است در متن مطالب سیستم حذف یا با کلمات جدید جایگزین شوند.
          • BadWord : کلمه مورد نظر که قرار است Ban شود.
          • IsStopWord : اگر لازم نیست جایگزینی برای کلمه استفاده شود و فقط لازم است حذف گردد، مقدار این خصوصیت true خواهد بود.
          • GoodWord : کلمه جایگزین 

          مدل تنظیمات سیستم

           /// <summary>
              /// Represent The CMS setting
              /// </summary>
              public class Setting
              {
                  #region Properties
                  /// <summary>
                  /// sets or gets name of setting
                  /// </summary>
                  public virtual string Name { get; set; }
                  /// <summary>
                  /// sets or gets value of setting
                  /// </summary>
                  public virtual string Value { get; set; }
                  /// <summary>
                  /// sets or gets Type of setting
                  /// </summary>
                  public virtual string Type { get; set; }
                  #endregion
              }
          مدل بالا کل تنظیمات سیستم را در قالب Key , Value نگهداری خواهد کرد. برای توضیحات این قسمت می‌توانید به مقاله‌ی "ذخیره تنظیمات متغیر مربوط به یک وب اپلیکیشن ASP.NET MVC با استفاده از EF" رجوع کنید.
          پ.ن:در مقاله‌ی بعد با پیاده سازی مدل‌های مدیریت کاربران، سیستم پیام رسانی، سیستم ترفیع رتبه و ارتباط دوستی، DomainClasses به اتمام خواهد رسید.

          نتیجه‌ی این قسمت 

          با توجه به این که تعداد مدل‌ها زیاد است و از طرفی حجم تصویر را کاهش داده‌ایم ، تصویر بدست آماده کمی افت کیفیت دارد؛ بنابراین بهتر است از فایل EDMX زیر استفاده کنید.

          ‫روش های مختلف پردازش یک رشته و تبدیل آن به نوع داده تاریخ

          $
          0
          0
          DateTime در طبقه بندی سی شارپ، جزء Strcut Type‌ها قرار می‌گیرد . عمدتا از DateTime برای مدیریت تاریخ، زمان و یا تاریخ-زمان استفاده می‌شود. خیلی از اوقات ما نیاز داریم تا رشته‌ای را به نوع تاریخ تبدیل کنیم تا بتوانیم عملیات مختلفی، همچون محاسبه‌ی اختلاف دو تاریخ، روز هفته، روز ماه و غیره را بدست آوریم. در دات نت متد‌های مختلفی وجود دارند که جداسازی تاریخ را از یک رشته برای ما فراهم می‌کنند:
          • Convert.ToDateTime()
          • DateTime.Parse()
          • DateTime.ParseExact()
          • DateTime.TryParse()
          • DateTime.TryParseExact()
          در این مطلب این متد‌ها و تفاوت آنها را بررسی می‌کنیم.

          تابع ()Convert.ToDateTime 
          این تابع یک رشته‌ی با فرمت مشخص را به تاریخ و زمان تبدیل می‌کند. overload‌ها مختلف این تابع را در بخش زیر مشاهده می‌کنید:
          • ToDateTime(string value)
          Value  : رشته‌ای از تاریخ و زمان است.
           • ToDateTime(string value,IFormatProvider provide)
          Value  : رشته‌ای از تاریخ و زمان است.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
          CultureInfo culture = new CultureInfo("en-US");    
          DateTime tempDate = Convert.ToDateTime("1/1/2010 12:10:15 PM", culture);
          در اینجا en-usاطلاعاتی را درباره‌ی فرهنگ کشور آمریکا، ارائه می‌دهد. لیست کامل فرهنگ‌های موجود در net. را می‌توانید در اینجامشاهده کنید.
          اگر رشته‌ی ما تهی (null) نباشد، بصورت درونی متد ()DateTime.Parse فراخوانی و نتیجه‌ی آن بازگردانده می‌شود. اما در صورتیکه رشته‌ی ارسالی ما تهی باشد، مقدار بازگردانده شده مقدار DateTime.MinValue که برابر با 0001/1/1 می‌باشد، بازگردانده می‌شود.
          نمونه‌ای از خروجی این تابع با ورودی‌های مختلف :
          string datestr = null;
          Console.WriteLine(Convert.ToDateTime(datestr));//0001-01-01T00:00:00
          datestr = "wrong string";
          Console.WriteLine(Convert.ToDateTime(datestr));
          //Unhandled Exception: System.FormatException: 
          //The string was not recognized as a valid DateTime. 
          //There is an unknown word starting at index 0.
          datestr = "Tue Dec 30,2015";
          Console.WriteLine(Convert.ToDateTime(datestr));
          //Unhandled Exception: System.FormatException: 
          //String was not recognized as a valid DateTime.

          تابع () DateTime.Parse :
          این تابع یک رشته‌ی با فرمت مشخص را به تاریخ و زمان تبدیل می‌کند. دو overload این تابع را در زیر می‌بینیم:
          • DateTime.Parse(string value)
          Value  : رشته‌ای از تاریخ و زمان می‌باشد.
          • DateTime.Parse(String value, IFormatProvider provider)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
          • DateTime.Parse(String value, IFormatProvider provider, DateTypeStyles styles)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند 
          Styles  : از طریق این پارامتر، تنظیمات قالب بندی رشته‌ی دریافتی برای سفارشی سازی کردن عملیات پردازش تعریف می‌شود. فرض کنید می‌خواهید کلیه‌ی فضاهای خالی (Space) را که قبل و بعد از رشته‌ی تاریخ هستند و نه در داخل رشته‌ی تاریخ، در زمان جداسازی و پردازش در نظر نگیرید. این پارامتر می‌تواند در اینجا به کمک ما بیاید (DateTimeStyles.AllowWhiteSpaces). مشاهده‌ی لیست کامل این خصوصیت از اینجا.
          اگر مقدار رشته تهی باشد، استثنای Null نمایش داده خواهد شد (ArgumentNullException ) و اگر فرمت تاریخ ورودی صحیح نباشد، با استثنای فرمت غیرمعتبر روبرو خواهیم شد (FormatException).
          string datestr = null;
          Console.WriteLine(DateTime.Parse(datestr));
          //// Exception: Argument null exception
          datestr = "wrong string";            
          Console.WriteLine(DateTime.Parse(datestr));
          //// Exception: The string was not recognized as a valid DateTime. 
          //// There is an unknown word starting at index 0.  
          datestr = "Tue Dec 30, 2015";
          //Unhandled Exception: System.FormatException: 
          //String was not recognized as a valid DateTime. 
          Console.WriteLine(DateTime.Parse(datestr));  

          تابع ()DateTime.ParsExact
          این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ ارسالی، دریافت و آن را تبدیل به نوع DateTime می‌کند. فرمت رشته‌ی ارسالی باید با فرمت استاندارد رشته‌ی تاریخ یکسان باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:
           • DateTime.ParseExact(string value, string format, IFormatProvider provider)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.

          • DateTime.ParseExact(string value, string format, IFormatProvider provider, DateTimeStyles style)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
          Styles  : از طریق این پارامتر تنظیمات قالب بندی رشته برای سفارشی کردن عملیات جداسازی و پردازش تعریف می‌شود.
          • DateTime.ParseExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style)
           
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند. تفاوت این حالت با حالت قبل این است که لیستی از فرمت‌ها را قبول می‌کند و حداقل باید یک فرمت با رشته‌ی ارسالی قابل انطباق باشد.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
          Styles : از طریق این پارامتر تنظیمات قالب بندی رشته برای سفارشی کردن عملیات جداسازی تعریف می‌شود. 
          اگر مقدار رشته تهی باشد، استثنای Null نمایش داده خواهد شد (ArgumentNullException) و اگر فرمت تاریخ ورودی صحیح نباشد، با استثنای فرمت غیرمعتبر روبرو خواهیم شد(FormatException).
          فرمت رشته‌ی تاریخ حتما باید با فرمت نوع تاریخ منطبق باشد. به همین منظور حالت‌های مختلفی را در آرایه می‌توان پیش بینی کرد. بطور مثال ممکن است رشته‌ی ما بصورت "2012-2-1" و یا "2012/2/1" باشد. بنابر این فرمت‌های "MM/dd/yyyy" و "MM-dd-yyyy" را از طریق آرایه ارسال می‌کنیم.
          برای درک بهتر این موضوع، کد‌های زیر را مشاهده کنید: 
          string datestr = null;
          CultureInfo provider = CultureInfo.InvariantCulture;
          Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
          //Unhandled Exception: System.ArgumentNullException: 
          //String reference not set to an instance of a String.
          datestr = "wrong date";
          Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
          //Unhandled Exception: System.FormatException: 
          //String was not recognized as a valid DateTime
          datestr = "Tue Dec 30, 2015";
          Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
          //Unhandled Exception: System.FormatException: 
          //String was not recognized as a valid DateTime.
          datestr = "10-22-2015";
          Console.WriteLine(DateTime.ParseExact(datestr, "MM-dd-yyyy", provider));
          //30/07/1394 12:00:00 ق.ظ
          datestr = "10-22-2015";
          Console.WriteLine(DateTime.ParseExact(datestr, new string[]
              {"MM-dd-yyyy", "MM/dd/yyyy", "MM.dd.yyyy"}, provider, DateTimeStyles.None));
          //30/07/1394 12:00:00 ق.ظ

          تابع ()DateTime.TryParse
          این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ مشخصی، دریافت و آن را تبدیل به نوع DateTime می‌کند و یک مقدار bool را برای اعلان این موضوع که عملیات تبدیل با موفقیت انجام شده است یا خیر، باز می‌گرداند. فرمت رشته‌ی ارسالی باید با فرمت رشته‌ی تاریخ یکسان باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:
           
          • DateTime.TryParse (String value, out DateTime result)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Result : مقدار تاریخ را بعد از جداسازی در خود ذخیره می‌کند.

          • DateTime.TryParse(String value, IFormatProvider provider, DateTimeStyles styles, out DateTime result)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
          Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).
          Result : مقدار تاریخ را بعد از جداسازی در خود ذخیره می‌کند.
          این تابع همیشه سعی می‌کند رشته‌ی تاریخ را جداسازی کند. در صورت موفقیت در عملیات جداسازی رشته، تاریخ معتبر را بر می‌گرداند و در غیر اینصورت میزان کوچکترین تاریخ یا همان MinValue را که قبلا در همین مطلب اشاره شد، باز می‌گرداند. در صورتی هم که رشته‌، null یا با فرمت رشته‌ای غیر معتبر باشد، همان مقدار DateTime.MinValue بازگردانده می‌شود. همیشه با کنترل مقدار بازگشتی صحت انجام عملیات را می‌توان متوجه شد (true موفقیت در عملیات جداسازی و false عدم موفقیت در عملیات جداسازی).
          نکته‌ی مهم عدم پرتاب استثتاء در صورت عدم موفقیت در عملیات جداسازی می‌باشد.
          string datestr = null;
                      DateTime temp;
                      Console.WriteLine(DateTime.TryParse(datestr, out temp));
                      Console.WriteLine(temp);
                      //False
                      //0001 - 01 - 01T00: 00:00
          
                      datestr = "wrong date";
                      Console.WriteLine(DateTime.TryParse(datestr, out temp));
                      Console.WriteLine(temp);
                      //False
                      //0001 - 01 - 01T00: 00:00
          
                      datestr = "Tue Dec 30, 2015";
                      Console.WriteLine(DateTime.TryParse(datestr, out temp));
                      Console.WriteLine(temp);
                      //False
                      //0001 - 01 - 01T00: 00:00

          تابع ()DateTime.TryParseExact
          این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ مشخصی دریافت و آن را تبدیل به نوع DateTime می‌کند. رشته‌ی ارسالی باید منطبق با فرمت تاریخ باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:

          • DateTime.ParseExact(string value, string format, IFormatProvider provider, DateTimeStyles style)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Format : فرمت مورد نظر را برای نمایش تاریخ بعد از تبدیل، مشخص می‌کند.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
          Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).

          • DateTime.ParseExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style)
          Value : رشته‌ای از تاریخ و زمان می‌باشد.
          Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند و می‌توان آرایه‌ای از فرمت‌ها را در اینجا ارسال کرد.
          Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
          Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).
          این تابع را در شرایط زیر، مقدار (MinValue( 1/1/0001 12:00:00 AM را باز می‌گرداند:
          • رشته null  باشد.
          • رشته خالی باشد "".
          • فرمت رشته‌ی تاریخ غلط باشد.
          • رشته با فرمت معرفی شده‌ی در provider، انطباق نداشته باشد.
          • تنها در صورتی که مقدار انتخابی DateTimeStyle معتبر نباشد، استثنایی رخ می‌دهد.
          همچنین همیشه پس از انجام عملیات، مقداری بولین را برای نمایش موفقیت یا عدم موفقیت جدا سازی، باز می‌گرداند.
            string datestr = null;
                      DateTime temp;
                      CultureInfo provider = CultureInfo.InvariantCulture;
                      Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
                      Console.WriteLine(temp);
                      //False
                      //1/1/0001 12:00:00 AM
          
                      datestr = "wrong date";
                      Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
                      Console.WriteLine(temp);
                      //False
                      //1/1/0001 12:00:00 AM
          
                      datestr = "Tue Dec 30, 2015";
                      Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
                      Console.WriteLine(temp);
                      //False
                      //1/1/0001 12:00:00 AM
          
                      datestr = "10‐22‐2015";
                      Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
                      Console.WriteLine(temp);
                      //False
                      //1/1/0001 12:00:00 AM
          
                      datestr = "10‐22‐2015";
                      Console.WriteLine(DateTime.TryParseExact(
                      datestr, "MM‐dd‐yyyy", provider, DateTimeStyles.None, out temp));
                      Console.WriteLine(temp);
                      //True
                      //30/07/1394 12:00:00 ق.ظ
          
                      datestr = "10‐12‐2015";
                      Console.WriteLine(DateTime.TryParseExact(datestr, new string[] { "MM/dd/yyyy", "MM‐dd‐yyyy", "MM.dd.yyyy" }, provider, DateTimeStyles.None, out temp));
                      Console.WriteLine(temp);
                      //True
                      //20/07/1394 12:00:00 ق.ظ


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

          تفاوت Parse و ConvertToDateTime:
          این دو متد شبیه به هم هستند، اما چند تفاوت کوچک با هم دارند:
          • اگر مقدار رشته‌ی ارسالی null باشد، متد Parse، یک استثناء را ارسال می‌کند و متد ConvertToDateTime مقدار MinValue را باز می‌گرداند.
          • در متد Parse می‌توان یک پارامتر اضافه‌تر نیز ارسال کرد که DateTimeStyle نامیده می‌شود. اما متد ConvertToDateTime این قابلیت را ندارد.
          • تابع ConvertToDateTime به‌صورت داخلی از متد DateTime.Parse به‌همراه فرهنگ جاری استفاده می‌کند.

          تفاوت تابع Parseو ParseExact:
          این دو تابع شبیه به هم هستند، اما می‌توان یک پارامتر اضافی format را نیز به تابع ParseExact ارسال کرد. این پارامتر به ما کمک می‌کند تا رشته‌ای را که فرمت متفاوتی از حالت معمولی دارد، به تاریخ تبدیل کنیم. مثلا اگر رشته‌ی "11232015" بدین شکل باشد، فرمت باید به شکل "MMddyyyy" تعریف شود.

          تفاوت تابع Parse و TryParse:
          این دو متد شبیه به هم هستند، با این تفاوت که تابع TryParse در صورت عدم موفقیت جداسازی رشته، استثنائی را ارسال نمی‌کند و همیشه مقدار MinValue را در صورت عدم موفقیت در تبدیل، باز می‌گرداند.

          تفاوت تابع TryParse و TryParseExact:
          هر دو تابع شبیه به هم هستند؛ بجز در پارامتر format. تابع TryParseExact از یک پارامتر اضافه‌ی format استفاده می‌کند. ولی تابع TryParse اگر نتواند فرمت مورد نیاز را فراهم کند، مقدار minValue را باز می‌گرداند.
           

          ‫ارسال عکس به stimulsoft و ایجاد گزارش

          $
          0
          0
          برنامه‌ی Stimulsoft designer را باز کرده و از قسمت سمت راست (Dictionary) بر روی Variable راست کلیک می‌کنیم. 


          سپس بر روی گزینه‌ی New Variable کلیک می‌کنیم:



          اکنون در قسمت Name، نام نمایشی را وارد می‌کنیم که با تبدیل کردن Alias نیز تغییر می‌کند و می‌تواند متفاوت باشد. در ادامه در قسمت Type ،Type را بر روی Image می‌گذاریم و سپس بر روی دکمه‌ی ok کلیک می‌کنیم. حال variable ایی را که ایجاد کرده‌ایم، بر روی صفحه می‌کشیم و در محل مورد نظر قرار می‌دهیم و پروژه را save می‌کنیم. تا اینجا توانسته‌ایم فایلی را به‌وسیله‌ی stimulsoft ایجاد کنیم که دارای یک مقدار variable هست.

          حال باید بتوانیم آن را در پروژه‌ی خود استفاده کنیم. جهت استفاده‌ی از آن‌، یک پروژه‌ی از نوع برنامه‌ی ویندوز (Windows Application) را ایجاد می‌کنیم و بر روی آن یک دکمه را جهت ارسال (چاپ گزارش) قرار می‌دهیم.

          سپس به قسمت #C رفته و یک متد را تحت عنوان imageToByteArray مانند کدهای زیر ایجاد می‌کنیم:

          public byte[] imageToByteArray(System.Drawing.Image imageIn)
          {
              MemoryStream ms =n ew MemoryStream();
              imageIn.Save(ms, imageIn.RawFormat);
              return ms.ToArray();
          }
          در ادامه در پشت دکمه‌ی چاپ، کدهای زیر را قرار می‌دهیم:
          var img = new System.Drawing.Bitmap(@"C:\\Users\\Ali\\Desktop\\multipage_tif_example.tif");
          byte[] array1 = imageToByteArray(img); 
          
          MemoryStream ms = new MemoryStream(array1);
          System.Drawing.Image image = System.Drawing.Image.FromStream(ms);
          
          StiReport rpt = new StiReport();
          rpt.Load(Application.StartupPath + "\\Report(image).mrt");
          rpt.Dictionary.Variables.Add("Image1", image);
          rpt.Compile();
          rpt.Show();
          که در اینجا آدرس فایل عکس به‌صورت دستی داده‌شده است:
          var img = new System.Drawing.Bitmap(@"C:\\Users\\Ali\\Desktop\\multipage_tif_example.tif");
          و مسیر فایل stimulsoft
            rpt.Load(Application.StartupPath + "\\Report(image).mrt");
          حال پروژه‌ی خود را اجرا کرده و نتیجه را می‌بینیم.

          باید دقت داشت جهت استفاده‌ی از برنامه Stimulsoft بایستی dll‌های مربوط به آن در پروژه قرارگرفته باشد و استفاده شده باشد.

          SASS #2

          $
          0
          0
          در قسمت قبل، روش‌های مختلف کامپایل فایل‌های SASS را بررسی کردیم. در ادامه می‌خواهیم با syntax آن بیشتر آشنا شویم.

          متغیرها (Variables)

          متغیرها در SASS با استفاده از $در ابتدای نام آن، به عنوان یک مقدار مورد استفاده‌ی در CSS تعریف می‌شوند. شما در SASS می‌توانید متغیرهایی را برای margin ،font-size و یا padding و غیره، تعریف کنید. استفاده از متغیرها این امکان را به شما می‌دهد که خیلی راحت‌تر از style‌های تعریف شده، مجدد استفاده کنید.

          شما 6 نوع مختلف متغیر را می‌توانید با استفاده از SASS بکار ببرید.

          • Strings (مثال: ;"myString: "your text here$ )
          • Numbers (مثال: ;myNum: 16px$)
          • Colors (مثال: ;myColor: aqua$)
          • Booleans (مثال: ;myBool: true$)
          • Lists (مثال: ;myItemList: 1px solid red$)
          • Nulls (مثال: ;myVar: null$)

          برای مثالی از استفاده‌ی از این متغیرها، یک فایل را با نام styles.scss ایجاد کرده و کدهای زیر را در آن وارد کنید:

          $myColor: #FFF726;
          $myBackColor: #2B14FF;
          $myString: "I Love ";
          $myFontSize: 13px;
          $myMargin: 0px auto;
          $myWidth: 300px;
          h1 {
             color: $myColor;
             margin: 0;
             padding: 0;
          }
          
          h1:before{
            content: $myString;
          }
          
          #container {
             width: $myWidth;
             margin: $myMargin;
             background-color:$myBackColor;
             text-align:center;
          }
          پس از کامپایل فایل SASS و اجرای کد بالا، مرورگر شما باید خروجی را به این صورت داشته باشد:


          ریاضی (Math)

          برخلاف SASS ،CSS به ما امکان استفاده از عبارات ریاضی را می‌دهد. عملگرهای جمع +، تفریق -، تقسیم /، ضرب *، باقیمانده %، مساوی ==، نامساوی =! را  SASS پشتیبانی می‌کند. در هنگام استفاده از عبارات ریاضی چند نکته وجود دارد که باید رعایت کنید:

          نکته1: چون علامت /در CSS به عنوان یک کوتاه کننده استفاده می‌شود مانند font: 14px/16px، در صورتیکه بخواهید عمل تقسیم را بر روی مقدار ثابتی انجام دهید باید آنها را درون پرانترقرار دهید.

          $fontDiff: (14px/16px);
          نکته2: شما نمی توانیدواحدهای مختلف را با هم استفاده کنید.
          $container-width: 100% - 20px;
          مثال بالا کار نمی‌کند، در صورتیکه نیاز به چنین محاسبه‌ای داشتید می‌توانید از تابع calcدر CSS استفاده کنید؛ چرا که نیاز به محاسبه‌ی در زمان اجرا را دارید.

          حال می‌خواهیم براساس عرض container، ستون‌های پویایی را ایجاد کنیم:
          $container-width: 100%;
          
          .container {
            width: $container-width;
          }
          
          .col-4 {
            width: $container-width / 4;
          }
          که پس از کامپایل به صورت زیر تبدیل می‌شود:
          .container {
             width: 100%;
          }
          
          .col-4 {
              width: 25%;
          }

          مشاهده‌ی پیاده سازی مثال بالا اینجا.

          توابع (Functions)

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

          برای نمونه برخی از توابع مربوط به کار با رنگ‌ها را توضیح می‌دهیم:

          • (darken ($color, $amount: این تابع برای تیره‌تر کردن یک کد رنگ می‌باشد. شما برای استفاده‌ی از این تابع باید دو مقدار را به آرگومان‌های ورودی این تابع که به ترتیب کد رنگ و میزان تیره‌تر شدن آن به صورت درصد از %0 تا %100 می‌باشند، ارسال کنید و خروجی آن کد رنگ تولید شده است. توجه داشته باشید نوع سیستم رنگی ارسال شده به عنوان پارامتر color$، خروجی این تابع نیز همان نوع می‌باشد.
          darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
          darken(#800, 20%) => #200
          • (lighten ($color, $amount: این تابع برای روشن‌تر کردن یک رنگ می‌باشد و دقیقا برعکس تابع darkenعمل می‌کند.
          lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
          lighten(#800, 20%) => #e00
          • (alpha ($color) / opacity($color: با استفاده از این دو تابع می‌توانید میزان شفافیت/کدری را مشخص کنید.
          • (mix ($color1, $color2, $weight:50% : با استفاده از این تابع می‌توانید دو رنگ را با هم ترکیب کنید. مقدار پیش فرض آرگومان weight$ برابر %50 می‌باشد و تعیین آن اختیاری است. محدوده‌ی پذیرش مقدار weight$ هرچه به %0 نزدیک‌تر باشد، باعث نزدیک‌تر بودن رنگ خروجی به رنگ دوم و هرچه به %100 نزدیکتر باشد رنگ خروجی به رنگ اول نزدیک‌تر می‌شود. توجه: میزان شفافیت/کدری را نیز می‌تواند تشخیص دهد.
          mix(#f00, #00f) => #7f007f
          mix(#f00, #00f, 25%) => #3f00bf
          mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)

          تو در تو (Nesting)

          SASS امکان تعریف استایل‌های تو در تو را به شما می‌دهد که باعث خواناتر شدن استایل‌های نوشته شده می‌شود. به عنوان مثال به کد CSS زیر توجه کنید:

          #container {
              width: 500px;
              margin: 0 auto;
          }
          
          #container p {
             font-family: Arial;
             font-size: 13px;
          }
          
          #container h1 {
             font-family: Tahoma;
             font-size: 15px;
          }
          
          #container h2 {
             font-family: Helvetica;
             font-size: 14px;
          }
          حال نسخه‌ی SASS مثال بالا می‌شود:
          $myFontsize1: 13px;
          $myFontsize2: 18px;
          $myFontsize3: 25px;
          $myWidth: 500px;
          $myMargin: 0px auto;
          
          #container {
              width: $myWidth;
              margin: $myMargin;
          
              p {
                  font-family: Arial;
                  font-size: $myFontsize1;
              }
          
              h1 {
                  font-family: Tahoma;
                  font-size: $myFontsize3;
              }
          
              h2 {
                  font-family: Helvetica;
                  font-size: $myFontsize2;
              }
          }
          توجه کنید تمام element هایی که درون container# قرار دارند، بدون ذکر نام container# نوشته شده‌اند و این کار سبب کمتر شدن کدهای نوشته شده و خواناتر شدن آن می‌شود.
          در صورتیکه نیاز به دسترسی به والد داشته باشید کافیست از علامت &استفاده کنید.
          a.myAnchor {
              color: blue;&:hover {
                  text-decoration: underline;
              }&:visited {
                  color: purple;
              }
          }
          خوب تا اینجا شما با روش نوشتن استایل‌های به صورت تو در تو آشنا شدید. حال اگر بخواهید کد‌های نوشته شده‌ی تو در تو را به صورت غیر تو در تو کامپایل کنید، باید از کلمه‌ی کلیدی at-root@قبل استایلی که می‌خواهید به صورت تو در تو تعریف نشود، استفاده کنید.
          .first-component {
              .text { font-size: 1.4em; }
              .button { font-size: 1.7em; }
          
              .second-component {
                  .text { font-size: 1.2em; }
                  .button { font-size: 1.4em; }
              }
          }
          مثال بالا در حالت تو در تو، پس از کامپایل به صورت زیر تبدیل می‌شود:
          .first-component .text {
            font-size: 1.4em;
          }
          .first-component .button {
            font-size: 1.7em;
          }
          .first-component .second-component .text {
            font-size: 1.2em;
          }
          .first-component .second-component .button {
            font-size: 1.4em;
          }
          حال با استفاده از at-root@ به صورت زیر می‌شود:
          .first-component .text {
            font-size: 1.4em;
          }
          .first-component .button {
            font-size: 1.7em;
          }
          .second-component .text {
            font-size: 1.2em;
          }
          .second-component .button {
            font-size: 1.4em;
          }

          ‫توسعه سیستم مدیریت محتوای DNTCms - قسمت ششم

          $
          0
          0
          در این قسمت مدل‌های باقی مانده‌ی از بخش‌هایی را که در مقاله اول مطرح شدند، به اتمام می‌رسانیم. همچنین با بازخوردهایی که در مقالات قبل گرفتیم، در این قسمت تغییرات ایجاد شده‌ی در مدل‌های قسمت‌های قبل را نیز مطرح خواهیم کرد.

          مدل‌های AuditLog (اصلاحیه)و ActivityLog

          باید توجه داشت که اگر سیستم AuditLog، جزئیات بیشتری را در در بر بگیرد، می‌توان از آن به عنوان History هم یاد کرد. در قسمت چهارم برای پست‌های انجمن یک جدول جدا هم به منظور ذخیره سازی تاریخچه‌ی تغییرات، در نظر گرفتیم. فرض کنید که یک سری از جداول دیگر هم نیازمند این امکان باشند! راه حل چیست؟
          1. استفاده از جداول جدا برای هر کدام از جداول به صورتیکه یک ارتباط یک به چند مابین آنها برقرار است. از این جداول تحت عنوان HistoryTable یاد می‌شود.
          2. استفاده از یک جدول برای نگهداری تاریخچه‌ی تغییرات جداولی که نیازمند این امکان هستند. 
          در زیر پیاده سازی از روش دوم رو مشاهده میکنید.
            /// <summary>
              /// Represent The Operation's log
              /// </summary>
              public class AuditLog
              {
                  #region Ctor
                  /// <summary>
                  /// Create One Instance Of <see cref="AuditLog"/>
                  /// </summary>
                  public AuditLog()
                  {
                      Id = SequentialGuidGenerator.NewSequentialGuid();
                      OperatedOn = DateTime.Now;
                  }
                  #endregion
          
                  #region Properties
                  /// <summary>
                  /// sets or gets identifier of AuditLog
                  /// </summary>
                  public virtual Guid Id { get; set; }
                  /// <summary>
                  /// gets or sets Type of  Modification(create,softDelet,Delete,update)
                  /// </summary>
                  public virtual AuditAction Action { get; set; }
                  /// <summary>
                  /// sets or gets description of Log
                  /// </summary>
                  public virtual string Description { get; set; }
                  /// <summary>
                  /// sets or gets when log is operated
                  /// </summary>
                  public virtual DateTime OperatedOn { get; set; }
                  /// <summary>
                  /// sets or gets Type Of Entity 
                  /// </summary>
                  public virtual string Entity { get; set; }
                  /// <summary>
                  /// gets or sets  Old value of  Properties before modification
                  /// </summary>
                  public virtual string OldValue { get; set; }
                  /// <summary>
                  /// gets or sets XML Base OldValue of Properties (NotMapped)
                  /// </summary>
                  public virtual XElement OldValueXml
                  {
                      get { return XElement.Parse(OldValue); }
                      set { OldValue = value.ToString(); }
                  }
                  /// <summary>
                  /// gets or sets new value of  Properties after modification
                  /// </summary>
                  public virtual string NewValue { get; set; }
                  /// <summary>
                  /// gets or sets XML Base NewValue of Properties (NotMapped)
                  /// </summary>
                  public virtual XElement NewValueXml
                  {
                      get { return XElement.Parse(NewValue); }
                      set { NewValue = value.ToString(); }
                  }
                  /// <summary>
                  /// gets or sets Identifier Of Entity
                  /// </summary>
                  public virtual string EntityId { get; set; }
                  /// <summary>
                  /// gets or sets user agent information
                  /// </summary>
                  public virtual string Agent { get; set; }
                  /// <summary>
                  /// gets or sets user's ip address
                  /// </summary>
                  public virtual string OperantIp { get; set; }
                  #endregion
          
                  #region NavigationProperties
                  /// <summary>
                  /// sets or gets log's creator
                  /// </summary>
                  public virtual User Operant { get; set; }
                  /// <summary>
                  /// sets or gets identifier of log's creator
                  /// </summary>
                  public virtual long OperantId { get; set; }
                  #endregion
              }
            public enum AuditAction
              {
                  Create,
                  Update,
                  Delete,
                  SoftDelete,
              }
          خصوصیاتی که نیاز به توضیح خواهند داشت:
          • Action : از نوع AdutiAction است و برای مشخص کردن نوع عملیاتی که انجام شده است، می‌باشد.
          • Description : اگر نیاز باشد توضیحاتی اضافی ثبت شوند، از این خصوصیت استفاده می‌شود.
          • Entity : مشخص کننده‌ی نام مدل خواهد بود. شاید بهتر بود از یک Enum استفاده می‌شد. ولی این سیستم به احتمال زیاد قرار است افزونه پذیر باشد و استفاده از Enum، یعنی محدودیت و این امکان وجود نخواهد داشت که سایر افزونه‌ها بتوانند از مدل بالا استفاده کنند. برا ی مثال BlogPost , NewsItem , ForumPost , ...
          • EntitytId : آی دی رکوردی است که تاریخچه‌ی آن ثبت شده است. از آنجائیکه بعضی از موجودیت‌ها دارای آی دی از نوع long و برخی دیگر Guid ، لذا ذخیره‌ی رشته‌ای آن مفید خواهد بود.
          • OldValue : در برگیرنده‌ی مقدار (قبل از اعمال تغییرات) خصوصیاتی است که لازم است از یک موجودیت مشخص، در قالب XML رشته‌ای ذخیره شوند.
          • NewValue : در برگیرنده‌ی مقدار (بعد از تغییرات) خصوصیاتی است که لازم است از یک موجودیت مشخص، در قالب XML رشته‌ای ذخیره شوند.
          • Operant, OperantId: برای برقراری ارتباط یک به چند مابین مدل کاربر و مدل بالا در نظر گرفته شده‌اند که به عنوان انجام دهنده‌ی این تغییرات بوده است.
          با استفاده از مدل بالا می‌توان متوجه شد که کاربر x چه خصوصیاتی از موجودیت y را تغییر داده است و این خصوصیات قبل از تغییر چه مقدارهایی داشته‌اند.
            /// <summary>
              /// Represents Activity Log record
              /// </summary>
              public class ActivityLog
              {
                  #region Ctor
                  /// <summary>
                  /// Create one instance of <see cref="ActivityLog"/>
                  /// </summary>
                  public ActivityLog()
                  {
                      Id = SequentialGuidGenerator.NewSequentialGuid();
                      OperatedOn=DateTime.Now;
                  }
                  #endregion
          
                  #region Properties
                  /// <summary>
                  /// gets or sets identifier 
                  /// </summary>
                  public virtual Guid Id { get; set; }
                  /// <summary>
                  /// gets or sets the comment of this activity
                  /// </summary>
                  public virtual string Comment { get; set; }
                  /// <summary>
                  /// gets or sets the date that this activity was done
                  /// </summary>
                  public virtual DateTime OperatedOn { get; set; }
                  /// <summary>
                  /// gets or sets the page url . 
                  /// </summary>
                  public virtual string Url { get; set; }
                  /// <summary>
                  /// gets or sets the title of page if Url is Not null
                  /// </summary>
                  public virtual string Title { get; set; }
                  /// <summary>
                  /// gets or sets user agent information
                  /// </summary>
                  public virtual string Agent { get; set; }
                  /// <summary>
                  /// gets or sets user's ip address
                  /// </summary>
                  public virtual string OperantIp { get; set; }
                  #endregion
          
                  #region NavigationProperties
                  /// <summary>
                  /// gets or sets the type of this activity
                  /// </summary>
                  public virtual ActivityLogType Type{ get; set; }
                  /// <summary>
                  /// gets or sets the  type's id of this activity
                  /// </summary>
                  public virtual Guid TypeId { get; set; }
                  /// <summary>
                  /// gets or sets User that done this activity
                  /// </summary>
                  public virtual User Operant { get; set; }
                  /// <summary>
                  /// gets or sets Id of User that done this activity
                  /// </summary>
                  public virtual long OperantId { get; set; }
                  #endregion
              }
          
             /// <summary>
              /// Represents Activity Log Type Record
              /// </summary>
              public class ActivityLogType
              {
                  #region Ctor
                  /// <summary>
                  /// Create one Instance of <see cref="ActivityLogType"/>
                  /// </summary>
                  public ActivityLogType()
                  {
                      Id = SequentialGuidGenerator.NewSequentialGuid();
                  }
                  #endregion
          
                  #region Properties
                  /// <summary>
                  /// gets or sets identifier 
                  /// </summary>
                  public virtual Guid Id { get; set; }
                  /// <summary>
                  /// gets or sets the system name
                  /// </summary>
                  public virtual string Name{ get; set; }
                  /// <summary>
                  /// gets or sets the display name
                  /// </summary>
                  public virtual string DisplayName { get; set; }
                  /// <summary>
                  /// gets or sets the description 
                  /// </summary>
                  public virtual string Description { get; set; }
                  /// <summary>
                  /// indicate this log type is enable for logging
                  /// </summary>
                  public virtual bool IsEnabled { get; set; }
                  #endregion
              }
          مدل‌های بالا هم برای ثبت لاگ فعالیت‌های کاربران در سیستم در نظر گرفته شده است . برای مثال اگر بخش آخرین تغییرات سایت جاریرا هم مشاهده کنید، یک همچین سیستمی را هم دارد. این لاگ‌ها برای ردیابی عملکرد کاربران در سیستم مفید خواهد بود.
          • Comment : توضیحات کوتاهی از اکشنی که کاربر انجام داده است.
          • Url : آدرس صفحه‌ای که این عملیات در آنجا انجام شده است. این خصوصیت نال‌پذیر می‌باشد.
          • Title : عنوان صفحه‌ای که این عملیات در آنجا انجام شده است؛ اگر Url نال نباشد.
          • Operant , OperantId : برای برقراری ارتباط یک به چند بین کاربر و مدل فعالیت‌ها در نظر گرفته شده‌اند.
          • Type : از نوع ActivityLogType پیاده سازی شده در بالا می‌باشد. با استفاده از مدل ActivityLogType می‌توان مثلا لاگ فعالیت مربوط به بخش اخبار را غیر فعال کند یا بالعکس و از این موارد.
          خصوصیت مدل ActivityLogType :
          • Name : نام سیستمی آن است. برای مثال : Login ، NewsComment ، NewsItem و ...
          • IsEnabled : نشان دهنده‌ی این است که این نوع لاگ فعال است یا خیر.

            کلاس پایه تمام مدل‌ها (اصلاحیه)

            /// <summary>
                /// Represents the  entity
                /// </summary>
                /// <typeparam name="TForeignKey">type of user's Id that can be long or long? </typeparam>
                public abstract class Entity<TForeignKey>
                {
                    #region Properties
                    /// <summary>
                    /// gets or sets date that this entity was created
                    /// </summary>
                    public virtual DateTime CreatedOn { get; set; }
                    /// <summary>
                    /// gets or sets Date that this entity was updated
                    /// </summary>
                    public virtual DateTime ModifiedOn { get; set; }
                    /// <summary>
                    /// gets or sets IP Address of Creator
                    /// </summary>
                    public virtual string CreatorIp { get; set; }
                    /// <summary>
                    /// gets or set IP Address of Modifier
                    /// </summary>
                    public virtual string ModifierIp { get; set; }
                    /// <summary>
                    /// indicate this entity is Locked for Modify
                    /// </summary>
                    public virtual bool ModifyLocked { get; set; }
                    /// <summary>
                    /// indicate this entity is deleted softly
                    /// </summary>
                    public virtual bool IsDeleted { get; set; }
                    /// <summary>
                    /// gets or sets information of user agent of modifier
                    /// </summary>
                    public virtual string ModifierAgent { get; set; }
                    /// <summary>
                    /// gets or sets information of user agent of Creator
                    /// </summary>
                    public virtual string CreatorAgent { get; set; }
                    /// <summary>
                    /// gets or sets date that this entity repoted last time
                    /// </summary>
                    public virtual DateTime? ReportedOn { get; set; }
                    /// <summary>
                    /// gets or sets counter for Content's report
                    /// </summary>
                    public virtual int ReportsCount { get; set; }
                    /// <summary>
                    /// gets or sets count of Modification Default is 1
                    /// </summary>
                    public virtual int Version { get; set; }
                    /// <summary>
                    /// gets or sets action (create,update,softDelete) 
                    /// </summary>
                    public virtual AuditAction Action { get; set; }
                    /// <summary>
                    /// gets or sets TimeStamp for prevent concurrency Problems
                    /// </summary>
                    public virtual byte[] RowVersion { get; set; }
                    #endregion
            
                    #region NavigationProperties
                    /// <summary>
                    /// gets ro sets User that Modify this entity
                    /// </summary>
                    public virtual User ModifiedBy { get; set; }
                    /// <summary>
                    /// gets ro sets Id of  User that modify this entity
                    /// </summary>
                    public virtual TForeignKey ModifiedById { get; set; }
                    /// <summary>
                    /// gets ro sets User that Create this entity
                    /// </summary>
                    public virtual User CreatedBy { get; set; }
                    /// <summary>
                    /// gets ro sets User that Create this entity
                    /// </summary>
                    public virtual TForeignKey CreatedById { get; set; }
                    #endregion
                }
            
              /// <summary>
                /// Represents the base Entity
                /// </summary>
                /// <typeparam name="TKey">type of Id</typeparam>
                /// <typeparam name="TForeignKey">type of User's Id that can be long or long?</typeparam>
                public abstract class BaseEntity<TKey,TForeignKey> : Entity<TForeignKey>
                {
                    #region Properties
                    /// <summary>
                    /// gets or sets Identifier of this Entity
                    /// </summary>
                    public virtual TKey Id { get; set; }
                    #endregion
                }
            از دو کلاس معرفی شده‌ی در بالا برای کپسوله کردن یکسری خصوصیات تکراری استفاده شده است. البته با بهبودهایی نسبت به مقاله‌ی قبل که با مشاهده‌ی خصوصیات آنها قابل فهم خواهد بود. در برخی از مدل‌ها، برای مثال نظرات وبلاگ امکان ارسال نظر برای افراد Anonymous هم وجود داشت؛ لذا CreatedById امکان نال بودن را هم داشت. به همین دلیل برای کاهش کدها، کلاس‌های بالا را به صورت جنریک تعریف کردیم. فایل EDMX نهایی که در انتهای مقاله ضمیمه شده، برای درک تغییرات اعمال شده مفید خواهد.

            مدل سیستم آگاه سازی

            /// <summary>
                /// Represents the Notification Record
                /// </summary>
                public class Notification
                {
                    #region Ctor
                    /// <summary>
                    /// create one instance of <see cref="Notification"/>
                    /// </summary>
                    public Notification()
                    {
                        Id = SequentialGuidGenerator.NewSequentialGuid();
                        ReceivedOn = DateTime.Now;
                    }
                    #endregion
            
                    #region Properties
                    /// <summary>
                    /// gets or sets identifier
                    /// </summary>
                    public virtual Guid Id { get; set; }
                    /// <summary>
                    /// indicate that this notification is read by owner
                    /// </summary>
                    public virtual bool IsRead { get; set; }
                    /// <summary>
                    /// gets or sets notification's text body
                    /// </summary>
                    public virtual string Message { get; set; }
                    /// <summary>
                    /// gets or sets page url that this notification is related with it
                    /// </summary>
                    public virtual string Url { get; set; }
                    /// <summary>
                    /// gets or sets date that this Notification Received
                    /// </summary>
                    public virtual DateTime ReceivedOn { get; set; }
                    /// <summary>
                    /// gets or sets the type of notification
                    /// </summary>
                    public virtual NotificationType Type { get; set; }
                    #endregion
            
                    #region NavigationProperties
                    /// <summary>
                    /// gets or sets the id of user that is owner of this notification
                    /// </summary>
                    public virtual long OwnerId { get; set; }
                    /// <summary>
                    /// gets or sets the user that is owner of this notification
                    /// </summary>
                    public virtual User Owner { get; set; }
                    #endregion
                }
            
                public enum  NotificationType
                {
                    NewConversation,
                    NewConversationReply,
                    ...
                }
            در این سیستم برای اطلاع رسانی کاربر، علاوه بر ارسال ایمیل، بحث اطلاع رسانی RealTime را هم خواهیم داشت. اطلاع رسانی‌هایی که توسط کاربر خوانده نشده باشند، در جدول حاصل از مدل Notification ذخیره خواهند شد. خصوصیاتی که نیاز به توضیح دارند:
            • IsRead : مشخص کننده‌ی این است که یک اطلاع رسانی خوانده شده است یا خیر. در آخر هر روز اطلاع رسانی‌هایی که دارای خصوصیت IsRead با مقدار true هستند، حذف خواهند شد.
            • Url : برای مواردی که لازم است کاربر با کلیک بر روی آن به صفحه‌ی خاصی هدایت شود.
            • OwnerId, Owner : برای برقراری ارتباط یک به چند بین کاربر و مدل Notification در نظر گرفته شده‌اند.
            • Type : از نوع NotificationType و مشخص کننده‌ی نوع اطلاع رسانی می‌باشد .

              مدل Observation

               public class Observation
                  {
                      #region Ctor
                      /// <summary>
                      /// create one instance of <see cref="Observation"/>
                      /// </summary>
                      public Observation()
                      {
                          LastObservedOn = DateTime.Now;
                          Id = SequentialGuidGenerator.NewSequentialGuid();
                      }
                      #endregion
              
                      #region Properties
                      public virtual Guid Id { get; set; }
                      /// <summary>
                      /// gets or sets datetime of last visit 
                      /// </summary>
                      public virtual DateTime LastObservedOn { get; set; }
                      /// <summary>
                      /// gets or sets Id Of section That user is  observing the entity
                      /// </summary>
                      public virtual string SectionId { get; set; }
                      /// <summary>
                      /// gets or sets  section That user is  observing in it
                      /// </summary>
                      public virtual string Section { get; set; }
                      #endregion
              
                      #region NavigationProperites
                      /// <summary>
                      /// gets or sets user that observed the entity
                      /// </summary>
                      public virtual User Observer { get; set; }
                      /// <summary>
                      /// gets or sets identifier of user that observed the entity
                      /// </summary>
                      public virtual long ObserverId { get; set; }
                      #endregion
                  }
              این مدل برای ایجاد امکانی به منظور واکشی لیست افردای که در حال مشاهده‌ی یک بخش خاص هستند، مفید است. فرض کنید در یک انجمن قصد دارید لیست افردای را که در حال مشاهده‌ی آن هستند، در پایین صفحه نمایش دهید. برای تاپیک‌ها هم همین امکان لازم است. لذا مدل بالا مختص مدل خاصی نیست و برای هر بخشی می‌توان از آن استفاده کرد. 
              فرض کنیم کاربری قصد هدایت به یک تاپیک را دارد. لذا هنگام هدایت شدن لازم است رکوردی در جدول حاصل از مدل بالا ثبت شود که کاربر x در بخش Topic، در تاریخ d، تاپیک به شماره‌ی y را مشاهده کرد. در صفحه‌ی مشاهده‌ی تاپیک می‌توان لیست افرادی را که قبل از مدت زمان مشخصی تاپیک را مشاهده کرده اند، نمایش داد. این رکورد‌ها را هم با تعریف یک Task می‌توان در بازه‌های زمانی مشخصی حذف کرد.

              مدل گروه‌های کاربری

               /// <summary>
                  /// Represents the  role record
                  /// </summary>
                  public class Role : IdentityRole<long, UserRole>
                  {
                      #region Properties
                      /// <summary>
                      /// gets or sets the displayName
                      /// </summary>
                      public virtual string DisplayName { get; set; }
                      /// <summary>
                      /// Indicating This Role is SystemRole or Not
                      /// </summary>
                      public virtual bool IsSystemRole { get; set; }
                      /// <summary>
                      ///indicating all users of this role are banned
                      /// </summary>
                      public virtual bool IsBanned { get; set; }
                      /// <summary>
                      /// gets or sets Description of Role
                      /// </summary>
                      public virtual string Description { get; set; }
                      /// <summary>
                      /// gets or sets comma seperated permissions list
                      /// </summary>
                      public virtual string Permissions { get; set; }
                      #endregion
                  }
              مدل بالا مشخص کننده‌ی گروه‌های کاربری سیستم می‌باشند. خصوصیت Permissions، نام تمام دسترسی‌هایی را که یک گروه کاربری می‌تواند داشته باشد، در قالب یک رشته‌ی به صورت جداشده‌ی از هم با یک کاما، نگهداری می‌کند. خصوصیات:
              • IsSystemRole : مشخص کننده‌ی سیستمی بودن یک گروه کاربری می‌باشد. یکسری از گروه‌های کاربری سیستمی هستند و تغییر و حذف آن‌ها امکان پذیر نیست. برای مثال: Administrators
              • IsBanned : مشخص کننده‌ی غیر فعال بودن کاربران نسبت داده شده به این گروه کاربری می‌باشد.
               این بخش می‌تواند پیاده سازی‌های متفاوتی برای اعمال دسترسی داینامیک داشته باشد که به شرح زیر می‌باشد:
              1. استفاده از یک جدول جدا برای نگهداری دسترسی‌ها و ایجاد ارتباط چند به چند مابین این جدول و جدول گروه‌های کاربری. اگر سیستم به صورت افزونه پذیر باشد، این مورد می‌تواند مفید باشد؛ چرا که هر افزونه با خود یکسری دسترسی‌ها را هم خواهد آورد. ولی در غیر این صورت می‌تواند کارآیی پایینی داشته باشد.  در این مورد اگر سیستم خیلی بزرگ باشد، حتی می‌توان جداول گروه کاربری و دسترسی‌ها را هم به صورت خود ارجاع دهنده در نظر گرفت. برای مثال پروژه‌ی "Iris Membership برای احراز هویت کاربران در ASP.NET MVC به صورت پویا"  را مشاهده کنید.
              2. استفاده از یک جدول جدا برای نگهداری دسترسی‌ها و ایجاد ارتباط یک به چند مابین آن و جدول گروه کاربری به طوری که هر گروه کاربری چندین دسترسی می‌تواند داشته باشد. این روش می‌تواند از نظر کارآیی بهتر از روش اول باشد؛ چرا که یکی از جوین‌های مورد نیاز کم می‌شوند. 
              3. استفاده از یک فیلد رشته ای در جدول گروه‌های کاربری.

                مدل صفحات داینامیک

                /// <summary>
                    /// represents one custom page
                    /// </summary>
                    public class Page : BaseEntity<long, long>
                    {
                        #region Properties
                        /// <summary>
                        /// gets or sets the blog pot body
                        /// </summary>
                        public virtual string Body { get; set; }
                        /// <summary>
                        /// gets or sets the content title
                        /// </summary>
                        public virtual string Title { get; set; }
                        /// <summary>
                        /// gets or sets value  indicating Custom Slug
                        /// </summary>
                        public virtual string SlugUrl { get; set; }
                        /// <summary>
                        /// gets or sets meta title for seo
                        /// </summary>
                        public virtual string MetaTitle { get; set; }
                        /// <summary>
                        /// gets or sets meta keywords for seo
                        /// </summary>
                        public virtual string MetaKeywords { get; set; }
                        /// <summary>
                        /// gets or sets meta description of the content
                        /// </summary>
                        public virtual string MetaDescription { get; set; }
                        /// <summary>
                        /// gets or sets 
                        /// </summary>
                        public virtual string FocusKeyword { get; set; }
                        /// <summary>
                        /// gets or sets value indicating whether the content use CanonicalUrl
                        /// </summary>
                        public virtual bool UseCanonicalUrl { get; set; }
                        /// <summary>
                        /// gets or sets CanonicalUrl That the Post Point to it
                        /// </summary>
                        public virtual string CanonicalUrl { get; set; }
                        /// <summary>
                        /// gets or sets value indicating whether the content user no Follow for Seo
                        /// </summary>
                        public virtual bool UseNoFollow { get; set; }
                        /// <summary>
                        /// gets or sets value indicating whether the content user no Index for Seo
                        /// </summary>
                        public virtual bool UseNoIndex { get; set; }
                        /// <summary>
                        /// gets or sets value indicating whether the content in sitemap
                        /// </summary>
                        public virtual bool IsInSitemap { get; set; }
                        /// <summary>
                        /// gets or sets title for snippet
                        /// </summary>
                        public string SocialSnippetTitle { get; set; }
                        /// <summary>
                        /// gets or sets description for snippet
                        /// </summary>
                        public string SocialSnippetDescription { get; set; }
                        /// <summary>
                        /// gets or sets section's type that this page show on
                        /// </summary>
                        public virtual ShowPageSection Section { get; set; }
                        /// <summary>
                        /// indicate this page has not any body
                        /// </summary>
                        public virtual bool IsCategory { get; set; }
                        /// <summary>
                        /// gets or sets order for display forum
                        /// </summary>
                        public virtual int DisplayOrder { get; set; }
                
                        #endregion
                
                        #region NavigationProeprties
                        /// <summary>
                        /// gets or sets Parent of this page
                        /// </summary>
                        public virtual Page Parent { get; set; }
                        /// <summary>
                        /// gets or sets parent'id of this page
                        /// </summary>
                        public virtual long? ParentId { get; set; }
                        /// <summary>
                        /// get or set collection of page that they are children of this page
                        /// </summary>
                        public virtual ICollection<Page> Children { get; set; }
                        #endregion
                    }
                
                  public enum ShowPageSection
                    {
                        Menu,
                        Footer,
                        SideBar
                    }
                مدل بالا مشخص کننده‌ی صفحاتی است که مدیر می‌تواند در پنل مدیریتی آنها را برای استفاده‌های خاصی تعریف کند. حالت درختی آن مشخص است. یکسری از خصوصیات مربوط به محتوای صفحه و همچنین تنظیمات سئو برای آن در نظر گرفته شده است که بیشتر آنها در مقالات قبل توضیح داده شده‌اند. خصوصیت Section از نوع ShowPageSection و برای مشخص کردن امکان نمایش صفحه‌ی مورد نظر در نظر گرفته شده‌است. همچنین این مدل بالا از کلاس پایه‌ی مطرح شده‌ی در اول مقاله، ارث بری کرده است که امکان ردیابی تغییرات آن را مهیا می‌کند.
                  خوب! حجم مقاله زیاد شده است و تا اینجا کافی خواهد بود ؛ بر خلاف تصور بنده، یک مقاله‌ی دیگر نیز برای اتمام بحث لازم میباشد.

                نتیجه‌ی تا این قسمت

                ‫مقدمه‌ای بر LINQ بخش اول

                $
                0
                0
                کلمه‌ی LINQ مخفف Language Integrated Query یا زبان پرس و جوی یکپارچه می‌باشد. LINQ برای اولین بار در ویژوال استودیوی 2008 و دات نت فریم ورک 3.5 برای پرکردن خلع بین دنیای اشیاء برنامه نویسی (Object Oriented World) و دنیای داده‌ها (Data World) ارائه شد.

                چرا LINQ؟ 
                در نگاهی کلی، مزایایی که از طریق LINQ حاصل می‌شوند عبارتند از:
                • کاهش حجم کدنویسی 
                • درک بهتر از عملکرد کد‌های نوشته شده
                • پس از یادگیری اصول LINQ به راحتی می‌توانید از این اصول پرس و جو نویسی برای کار بر روی مجموعه داده‌های مختلف استفاده کنید
                • کنترل صحت کدهای پرس و جو‌ها در زمان کامپایل ( Compile-Time Type Checking )

                اجزای سازنده‌ی LINQ
                دو جزء اصلی سازنده‌ی LINQ عبارت است از:
                • Elements عناصر
                • Sequences توالی‌ها
                توالی‌ها می‌توانند لیستی از اطلاعات مختلف باشند. هر آیتم در لیست را عنصر می‌گوییم. توالی نمونه‌ای از یک کلاس است که اینترفیس <IEnumarable<T را پیاده سازی کرده باشد. این اینترفیس تضمین می‌کند که توالی قابلیت پیمایش عناصر را دارد.
                به آرایه‌ی تعریف شده‌ی زیر دقت کنید:
                int[] fibonacci = {0, 1, 1, 2, 3, 5};
                متغیر fibonacci در اینجا نشان دهنده‌ی توالی و هر یک از اعداد آرایه، یک عنصر محسوب می‌شوند.
                توالی می‌تواند درون حافظه‌ای باشد (In Memory Object) که به آن Local Sequence می‌گویند و یا می‌تواند یک بانک اطلاعاتی SQL Server باشد که به آن Remote Sequence می‌گویند.
                در حالت Remote باید اینترفیس <IQuerable<T پیاده سازی شده باشد.
                پرس و جو هایی را که بر روی توالی‌های محلی اجرا می‌شوند، اصطلاحا Local Query و یا LINQ-To-Object  نیز می‌نامند.
                عملگرهای پرس و جوی  زیادی به شکل متد الحاقی در کلاس System.Linq.Enumerable طراحی شده‌اند. این مجموعه از عملگرهای پرس جو را اصطلاحا Standard Query Operator می‌گویند.
                نکته‌ی مهم این است که عملگرهای پرس و جو تغییری را در توالی ورودی نمی‌دهند و نتیجه‌ی خروجی یک مجموعه جدید و یا یک مقدار عددی می‌باشد.

                توالی خروجی و مقدار بازگشتی Scalar
                در بخش قبل گفتیم که خروجی یک پرس و جو می‌تواند یک مجموعه و یا یک مقدار عددی باشد. در مثال زیر عملگر Count را بر روی مجموعه‌ی fibonacci  اعمال کردیم و عددی که نشان دهنده‌ی تعداد عناصر مجموعه است، بعنوان خروجی بازگردانده شده است.
                int[] fibonacci = { 0, 1, 1, 2, 3, 5 };
                int numberOfElements = fibonacci.Count();
                Console.WriteLine($"{numberOfElements}");
                IEnumerable<int> distinictNumbers = fibonacci.Distinct();
                Console.WriteLine("Elements in output sequence:");
                foreach (var number in distinictNumbers)
                {
                    Console.WriteLine(number);
                }
                در کد بالا توسط تابع Distinct، عناصر یکتا را از توالی ورودی استخراج کرده و بازگردانده‌ایم.
                خروجی برنامه‌ی فوق به شکل زیر است :
                6
                Elements in output sequence:
                0
                1
                2
                3
                5

                مفهوم Deffer Execution  (
                اجرای به تاخیر افتاده )
                عمده‌ی عملگر‌های پرس و جو بلافاصله پس از ایجاد، اجرا نمی‌شوند. این عملگرها در طول اجرای برنامه اجرا خواهند شد (اجرای با تاخیر). به همین خاطر می‌توان بعد از ساخت پرس و جو  تغییرات دلخواهی را به توالی ورودی اعمال کرد.
                در کد زیر  قبل از اجرای پرس و جو ، توالی ورودی ویرایش شده :
                int[] fibonacci = { 0, 1, 1, 2, 3, 5 };
                // ایجاد پرس و جو 
                IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2);
                // در این مرحله پرس و جو ایجاد شده ولی هنوز اجرا نشده است
                // تغییر عنصر اول توالی
                fibonacci[0] = 99;
                // حرکت بر روی عناصر توالی باعث اجرای پرس و جو می‌شود
                foreach (var number in numbersGreaterThanTwoQuery)
                {
                   Console.WriteLine(number);
                }
                پرس و جو تا زمان اجرای حلقه‌ی Foreach اجرا نخواهد شد. خروجی مثال بالا به شکل زیر است :
                99
                3
                5

                به غیر از بعضی از عملگرها مثل Count,Min,Last سایر عملگر‌ها بصورت اجرای با تاخیر عمل می‌کنند. عملگری مثل Count باعث اجرای فوری پرس و جو می‌شود.
                تعدادی عملگر تبدیل (Conversion Operator) هم وجود دارد که باعث می‌شوند پرس و جو بلافاصله اجرا شود :
                • ToList
                • ToArray
                • ToLookup
                • ToDictionary
                عملگر‌های فوق پس از اجرا، خروجی را در یک ساختمان داده‌ی جدید باز می‌گردانند.
                در کد زیر اصلاح توالی متغیر Fibonacci بعد از اجرای تابع ToArray صورت گرفته است.
                int[] fibonacci = { 0, 1, 1, 2, 3, 5 };
                // ساخت پرس و جو
                IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2) .ToArray();
                // در این مرحله به خاطر عملگر استفاده شده پرس و جو اجرا می‌شود
                // تغییر اولین عنصر توالی
                fibonacci[0] = 99;
                // حرکت بر روی نتیجه
                foreach (var number in numbersGreaterThanTwoQuery)
                {
                   Console.WriteLine(number);
                }
                خروجی مثال بالا:
                3
                5
                همانطور که می‌بینید عدد 99 در خروجی مشاهده نمی‌شود. علت فراخوانی عملگر ToArray است که بلافاصله باعث اجرای پرس و جو شده و خروجی را باز می‌گرداند . به همین خاطر تغییر عنصر اول توالی ورودی، تاثیری بر روی نتیجه‌ی خروجی ندارد. 

                ‫آموزش LINQ بخش دوم

                $
                0
                0
                سبک‌های مختلف نوشتن Query در LINQ
                تعریف Query:
                عبارتی که اطلاعات را از منبع داده، بازیابی می‌کند، پرس و جو یا Query می‌گوییم. بطور کلی عملیات پرس و جو شامل سه بخش زیر می‌شود:
                1- مشخص کردن منبع داده
                2- ایجاد پرس و جو (Query)
                3- اجرای پرس و جو
                // The Three Parts of a LINQ Query:
                //  1. منبع داده
                int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
                // 2. ایجاد پرس و جو
                // numQuery is an IEnumerable<int>
                var numQuery =
                    from num in numbers
                    where (num % 2) == 0
                    select num;
                // 3. اجرای پرس و جو
                foreach (int num in numQuery)
                {
                    Console.Write("{0,1} ", num);
                }
                شکل زیر توصیفی از کد‌های بالا می‌باشد :
                 
                دو سبک برای نوشتن عبارت‌های جستجو در LINQ وجود دارند :
                 1- Fluent Style 
                 2- Query Expression Style یا Query Syntax
                سبک Fluent از متد‌های الحاقی برای عملیات پرس و جو استفاده می‌کند. در کلیه‌ی کدهای بخش اولاین سری آموزشی از سبک Fluent استفاده شده است.
                در کلاس‌های زیر متد‌های استاتیک مختلفی برای عملیات بر روی توالی‌ها ارائه شده‌اند:
                 • System.Linq.Enumerable
                 • System.Linq.Queryable
                 • System.Linq.ParallelEnumarable
                بطور کلی هر نمونه‌ای که اینترفیس <IEnumerable<Tsource را پیاده سازی کرده باشد می‌تواند از این متدهای الحاقی استفاده کند.
                عملگرهای جستجو به دو صورت تکی و زنجیره‌ای برای ایجاد پرس و جو‌های پیچیده مورد استفاده قرار می‌گیرند.

                پرس و جوی‌های زنجیره‌ای
                در ابتدا کلاسی به نام Ingredient را به شکل زیر تعریف می‌کنیم (این کلاس نشان دهنده‌ی نام مواد غذایی و کالری آنهاست):
                class Ingredient
                {
                    public string Name { get; set; }
                    public int Calories { get; set; }
                }
                لیستی از مواد غذایی را ایجاد می‌کنیم:
                Ingredient[] ingredients =
                {
                   new Ingredient {Name = "Suger", Calories = 500},
                   new Ingredient {Name = "Egg", Calories = 100 },
                   new Ingredient {Name = "Milk", Calories = 150 },
                   new Ingredient {Name = "Flour", Calories = 50 },
                   new Ingredient {Name = "Butter", Calories = 200 }
                };
                حال می‌خواهیم بصورت زنجیره‌ای از عملگر‌های پرس و جوی Where,OrderBy,Select استفاده کنیم:
                Ingredient[] ingredients =
                {
                   new Ingredient {Name = "Suger", Calories = 500},
                   new Ingredient {Name = "Egg", Calories = 100 },
                   new Ingredient {Name = "Milk", Calories = 150 },
                   new Ingredient {Name = "Flour", Calories = 50 },
                   new Ingredient {Name = "Butter", Calories = 200 }
                };
                
                IEnumerable<string> highCalories =
                ingredients.Where(x => x.Calories >= 150)
                  .OrderBy(x => x.Name)
                  .Select(x => x.Name);
                
                foreach (var item in highCalories)
                {
                   Console.WriteLine(item);
                }
                خروجی کد بالا به شکل زیر است :
                Butter
                Milk
                Suger
                نمودار زیر نحوه‌ی عملکرد عملگرهای پرس و جو را نشان می‌دهد. هر عملگر بر روی توالی خروجی عملگر قبلی کار می‌کند. توجه کنید که توالی ورودی از نوع <IEnumerable<Ingredient می‌باشد و توالی خروجی تولید شده از نوع <IEnumerable<string است.
                در این مثال عملگر‌های پرس و جو بر روی توالی ورودی عمل می‌کنند تا به دستور Select برسند. دستور Select هر عنصر را به یک رشته تبدیل می‌کند. این عملیات را Projection می‌گویند.











                عبارت Lambda نوشته شده‌ی در بخش Select مشخص می‌کند که خروجی بر اساس چه خصوصیتی از توالی ورودی باشد. در اینجا نام عناصر به صورت رشته در خروجی ظاهر می‌شوند.


                سبک Query Expression (عبارت‌های پرس و جو) 

                Query Expression یک گرامر زیبا و روان برای نوشتن پرس و جو‌ها را ارائه می‌دهد. در مثال زیر از سبک Query Expression استفاده کرده‌ایم:

                Ingredient[] ingredients =
                {
                    new Ingredient {Name = "Suger", Calories = 500},
                    new Ingredient {Name = "Egg", Calories = 100},
                    new Ingredient {Name = "Milk", Calories = 150},
                    new Ingredient {Name = "Flour", Calories = 50},
                    new Ingredient {Name = "Butter", Calories = 200}
                };
                
                IEnumerable<string> highCalories =
                    from i in ingredients
                    where i.Calories >= 150
                    orderby i.Name
                    select i.Name;
                
                foreach (var item in highCalories)
                {
                    Console.WriteLine(item);
                }

                خروجی کد بالا با خروجی کد به سبک Fluent  یکسان است:

                Butter
                Milk
                Suger

                همانطور که می‌بینید ترتیب عملیات همانند روش قبل است. عبارت‌های پرس و جوی (from,where,orderby,select) به ترتیب با اصلاح توالی ورودی و تحویل آن به عبارت جستجوی بعدی کار را انجام می‌دهند.

                عبارت جستجوی بالا با کلمه‌ی کلیدی from آغاز شده است. هدف from دو چیز است:

                1- مشخص کردن توالی ورودی (منبع داده)

                2- معرفی متغیر Range  (مشخص کردن عنصر مورد نظر در منبع داده)

                متغیر Range همچون متغیر شمارنده در حلقه هاست. 


                در ادامه این سری آموزشی درباره متغیر Range بصورت کاملتری بحث خواهیم کرد.   

                ‫آموزش LINQ بخش سوم

                $
                0
                0
                در ادامه سری آموزشی LINQ  به بررسی متغیرهای Range می‌پردازیم:
                4 عنصر یک عبارت پرس و جو عبارتند از:
                • علملگرهای LINQ
                • کلمات کلیدی Keyword
                • متغیر‌های Range

                Range Variable : متغیر تعریف شده‌ی در یک محدوده خاص.

                  عبارت پرس و جوی زیر را در نظر بگیرد:
                var query = from word in list
                where word.StartsWith("a")
                select word;
                در این پرس و جو (from,in,where,select) کلمات کلیدی محسوب می‌شوند. البته where و select می‌توانند به عنوان عملگر محسوب شوند.
                شناسه‌ی list یک متغیر محلی است و تنها موردی که باقی می‌ماند شناسه‌ی word است که به آن متغیر Range می‌گوییم. متغیر‌های Range همانند متغیر‌های مرسوم مورد استفاده‌ی در برنامه‌ها هستند که بصورت فقط خواندنی مهیا شده اند. با این اوصاف متغیر‌های Range در ابتدا کمی عجیب به نظر می‌رسند. به این علت که در وسط عبارت پرس و جو معرفی می‌شوند و نیازی به تعریف شدن به روش مرسوم به شکل زیر را ندارند:
                 String word;
                در این حالت معرفی Word از طریق عبارت from انجام می‌شود. کامپایلر نوع داده‌ی متغیر را از طریق فرآیندی به نام Type Inference مشخص می‌کند. در مثال بالا کامپایلر تشخیص می‌دهد که Word از نوع string  است؛ به این علت که جدا شده از <list<string  می‌باشد.

                متغیر word  در دو حالت ممکن است قابل دسترس نباشد :
                • پایان پرس و جو
                • مواجه شدن با کلمه کلیدی into . این کلمه‌ی کلیدی برای اتصال دو Query استفاده می‌شود.

                نکته
                :در بعضی مواقع باید وضعیت متغیر Range را صریحا مشخص کنیم؛ بطور مثال کد زیر با خطا مواجه خواهد شد:
                object[] ints = new object[] { 1, 2, 3 };
                           var query = from num in ints
                                        where num < 3
                                        select num;
                در زمان پردازش دستور Where، کامپایلر نمی‌تواند عملگر مقایسه‌ای را برای یک نوع int و یک نوع object اجرا کند. برای حل این مشکل بصورت صریح (Explicit) نوع متغیر Range را مشخص می‌کنیم:
                var query = from int num in ints
                where num < 3
                select num;
                همانطور که می‌بینید در این حالت به کامپایلر اعلام می‌کنیم که num از نوع int می‌باشد و cast کردن با موفقیت انجام می‌شود و خروجی همان چیزی است که ما انتظار داریم.

                تذکر : بهتر است از تعریف صریح متغیر Range پرهیز کنیم؛ مگر در شرایطی مثل کد بالا .

                قطعه کد زیر به‌راحتی کامپایل می‌شود و نیازی به اعلان صریح نوع متغیر range نیست. زیر از طریق مکانیزیم Type Inference نوع متغیر مشخص شده است.
                List<string> list = new List<string> {"LINQ","Query","adventure"};
                var query = from string word in list
                where word.Contains("r")
                orderby word ascending
                select word;
                اعلان صریح متغیر Range باعث می‌شود که پشت پرده، عملیات <Cast<T اتفاق بیافتد و در مواقع غیر ضروری مثل کد فوق ممکن است کارآیی را کاهش دهد. یکی از نقاطی که در صورت پایین بودن کارآیی دستورات LINQ باید بررسی شود همین مورد CAST است. البته تنها استثنایی که در این مورد وجود دارد، توالی‌های غیر جنریک هستند (non generic Enumerable). در این حالت باید از Cast استفاده کرد.
                متغیر Range در محدوده‌ی مورد استفاده باید از یک شناسه‌ی یکتا برخوردار باشد.
                 string word="test";
                List<string> list = new List<string> {"LINQ","Query","adventure"};
                var query = from string word in list
                where word.Contains("r")
                orderby word ascending
                select word;
                همانطور که مشاهده می‌کنید کامپایلر خطای تعریف دو شناسه‌ی یکتا را در یک محدوده، اعلام می‌کند.

                تا اینجا از طریق کلمه‌ی کلیدی from، متغیری را تعریف کردیم. با استفاده از کلمات کلیدی let ،into و join  نیز می‌توان متغیر‌های Range تعریف کرد.

                عبارت let

                کلمه‌ی کلیدی let این امکان را فراهم می‌کند تا یک متغیر Range جدید را ایجاد کرده و در عبارت‌های بعدی از آن استفاده کنیم. در کد زیر از طریق کلمه‌ی کلیدی let، یک متغیر Range جدید را بنام IsDairy تعریف می‌کنیم که از نوع bool  می‌باشد:
                 Ingredient[] ingredients =
                {
                   new Ingredient {Name = "Sugar", Calories = 500},
                   new Ingredient {Name = "Egg", Calories = 100},
                   new Ingredient {Name = "Milk", Calories = 150},
                   new Ingredient {Name = "Flour", Calories = 50},
                   new Ingredient {Name = "Butter", Calories = 200}
                };
                
                IEnumerable<Ingredient> highCalDairyQuery =
                from i in ingredients
                let isDairy = i.Name == "Milk" || i.Name == "Butter"
                where i.Calories >= 150 && isDairy
                select i;
                
                foreach (var ingredient in highCalDairyQuery)
                {
                   Console.WriteLine(ingredient.Name);
                }
                  متغیر isDairy در عبارت (clause) بعدی که where باشد، مورد استفاده قرار گرفته است. توجه داشته باشید که متغیر i  تعریف شده‌ی در ابتدای پرس و جو، در بخش Select قابل دسترسی است.دستور let باعث از دسترس خارج شدن متغیر در بخش بعدی نمی‌شود.

                در کد زیر قصد داریم عملیات‌های زیر را بر روی توالی ورودی اعمال کنیم:
                1- جدا کردن عناصر توالی ورودی بر اساس جدا کننده‌ی "  ," 
                2- تبدیل همه‌ی حروف عناصر توالی ایجاد شده به حروف بزرگ
                3- جدا کردن عناصر توالی حاصل از مرحله‌ی 2، به شرط برابر بودن با MILK,BUTTER,CHEESE
                4- نمایش توالی ایجاد شده
                string[] csvRecipes = { "milk,sugar,eggs", "flour,BUTTER,eggs", "vanilla,ChEEsE,oats" };
                var dairyQuery = from csvRecipe in csvRecipes
                let ingredients = csvRecipe.Split(',')
                from ingredient in ingredients
                let uppercaseIngredient = ingredient.ToUpper()
                where
                  uppercaseIngredient == "MILK" ||
                  uppercaseIngredient == "BUTTER" ||
                  uppercaseIngredient == "CHEESE"
                select uppercaseIngredient;
                
                foreach (var dairyIngredient in dairyQuery)
                {
                   Console.WriteLine($"{dairyIngredient} is dairy");
                }
                همانطور که مشاهده می‌کنید متغیر ایجاد شده‌ی توسط let می‌تواند یک مقدار عددی (مثال قبل) و یا یک مجموعه را در خود ذخیره کند (مثال فوق).

                 عبارت Into 
                متغیر جدیدی که توسط این دستور ایجاد می‌شود، می‌تواند نتیجه‌ی حاصل از دستور Select را در خود ذخیره کند. در کد زیر یک نوع بی‌نام ایجاد کرده و در ادامه‌ی پرس و جو از آن استفاده می‌کنیم:
                 Ingredient[] ingredients =
                {
                   new Ingredient {Name = "Sugar", Calories = 500},
                   new Ingredient {Name = "Egg", Calories = 100},
                   new Ingredient {Name = "Milk", Calories = 150},
                   new Ingredient {Name = "Flour", Calories = 50},
                   new Ingredient {Name = "Butter", Calories = 200}
                };
                
                IEnumerable<Ingredient> highCalDairyQuery =
                from i in ingredients
                select new //نوع بی نام
                {
                  OriginalIngredient = i,
                  IsDairy = i.Name == "Milk" || i.Name == "Butter",
                  IsHighCalorie = i.Calories >= 150
                }
                into temp
                where temp.IsDairy && temp.IsHighCalorie
                select temp.OriginalIngredient;
                
                foreach (var ingredient in highCalDairyQuery)
                {
                   Console.WriteLine(ingredient.Name);
                }
                نکته‌ای که در ابتدای این بحث اشاره شد، در این مثال خود را نشان می‌دهد و آن هم عدم دسترسی به متغیر iدر بخش پایانی پرس و جو (select  نهایی) می‌باشد.

                در ادامه‌ی این سری آموزشی، به بررسی عبارت join  می‌پردازیم. 

                ‫کارهایی جهت بالابردن کارآیی Entity Framework #2

                $
                0
                0

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

                عدم استفاده از کوئری‌های کلی

                فرض کنید در یک فرم جستجو، 4 تکست باکس FirstName, LastName, City و PostalZipCode برای عملیات جستجو در نظر گرفته شده است و کاربر می‌تواند بر اساس آنها جستجو را انجام دهد.

                var searchModel = new Pupil
                {
                    FirstName = "Ben",
                    LastName = null,
                    City = null,
                    PostalZipCode = null
                };
                
                List<Pupil> pupils = db.Pupils.Where(p =>
                        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)&& (searchModel.LastName == null || p.LastName == searchModel.LastName)&& (searchModel.City == null || p.LastName == searchModel.City)&& (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
                    .Take(100)
                    .ToList()
                در کد بالا کاربر فقط فیلد نام را برای جستجو انتخاب کرده است و سایر فیلدها را خالی در نظر گرفته است و کوئری بالا صادر شده است و دستورات زیر به اس کیو ال ارسال شده‌اند:
                 USE [EFSchoolSystem]
                DECLARE @p__linq__0 NVarChar(4000) SET @p__linq__0 = 'Ben'
                DECLARE @p__linq__1 NVarChar(4000) SET @p__linq__1 = 'Ben'
                DECLARE @p__linq__2 NVarChar(4000) SET @p__linq__2 = ''
                DECLARE @p__linq__3 NVarChar(4000) SET @p__linq__3 = ''
                DECLARE @p__linq__4 NVarChar(4000) SET @p__linq__4 = ''
                DECLARE @p__linq__5 NVarChar(4000) SET @p__linq__5 = ''
                DECLARE @p__linq__6 NVarChar(4000) SET @p__linq__6 = ''
                DECLARE @p__linq__7 NVarChar(4000) SET @p__linq__7 = ''
                
                -- Executed query
                SELECT TOP (100)
                        [Extent1].[PupilId] AS [PupilId] ,
                        [Extent1].[FirstName] AS [FirstName] ,
                        [Extent1].[LastName] AS [LastName] ,
                        [Extent1].[Address1] AS [Address1] ,
                        [Extent1].[Adderss2] AS [Adderss2] ,
                        [Extent1].[PostalZipCode] AS [PostalZipCode] ,
                        [Extent1].[City] AS [City] ,
                        [Extent1].[PhoneNumber] AS [PhoneNumber] ,
                        [Extent1].[SchoolId] AS [SchoolId] ,
                        [Extent1].[Picture] AS [Picture]
                FROM    [dbo].[Pupils] AS [Extent1]
                WHERE   (@p__linq__0 IS NULL OR [Extent1].[FirstName] = @p__linq__1)
                        AND (@p__linq__2 IS NULL OR [Extent1].[LastName] = @p__linq__3)
                        AND (@p__linq__4 IS NULL OR [Extent1].[LastName] = @p__linq__5)
                        AND (@p__linq__6 IS NULL OR [Extent1].[PostalZipCode] = @p__linq__7)
                هنگامیکه کوئری، سمت اس کیو ال سرور اجرا می‌شود، در صورتیکه پلن مربوط به آن وجود نداشته باشد، اس کیو ال سرور اقدام به ایجاد پلن، براساس پارامترهای ورودی می‌کند و از این به بعد، کوئری‌هایی یکسان، با پارامترهای متفاوت، از پلن کش شده استفاده خواهند کرد و از ایجاد پلن دیگری بدلیل هزینه بر بودن آن جلوگیری می‌شود.
                مشکلی که در این دسته از کوئری‌های عمومی ایجاد می‌گردد آن است که ممکن است پلنی که برای یک گروه از پارامترهای ورودی مناسب باشد (جستجو بر اساس نام) برای سایر پارامترهای ورودی نامناسب باشد. تصور کنید تمام دانش آموزان، در شهر نیویورک یا بوستون زندگی می‌کنند. بنابراین این ستون از تنوع انتخاب کمتری برخوردار است در مقایسه با ستون نام خانوادگی و فرض کنید پلن، براساس پارامتر شهر ایجاد شده است. بنابراین ایجاد این پلن برای سایر پارامترها از کارآیی کافی برخوردار نیست. این مشکل با نام Bad Parameter Sniffingشناخته می‌شود و درباره‌ی Parameter Sniffingدر اینجابه تفصیل اشاره شده است.
                این مشکل زمانی بیشتر مشکل ساز خواهد شد که 99 درصد دانش آموزان در شهر نیویورک و فقط 1 درصد آن‌ها در شهر بوستون زندگی می‌کنند و پلن ایجاد شده بر اساس پارامتر شهر بوستون باشد.

                راه حل اول:
                برای حل این مشکل تنها یک راه حل خاص وجود ندارد و باید براساس شرایط برنامه، کوئری از حالت عمومی خارج گردد؛ مانند زیر:
                 if (searchModel.City == null)
                {
                    pupils = db.Pupils.Where(p =>
                        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)&& (searchModel.LastName == null || p.LastName == searchModel.LastName)&& (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
                        .Take(100)
                        .ToList();
                }
                else
                {
                    pupils = db.Pupils.Where(p =>
                        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)&& (searchModel.LastName == null || p.LastName == searchModel.LastName)&& (searchModel.City == null || p.LastName == searchModel.City)&& (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
                        .Take(100)
                        .ToList();
                }

                راه حل دوم:

                کامپایل مجدد پلن در اجرای هر کوئری، اما این راه حل سرباری را تحمیل می‌کند. بدین منظور مترجم زیر را ایجاد کنید:
                public class RecompileDbCommandInterceptor : IDbCommandInterceptor
                {
                    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
                    {
                        if(!command.CommandText.EndsWith(" option(recompile)"))
                        {
                            command.CommandText += " option(recompile)";
                        }
                    }
                    //and implement other interface members
                }
                و مانند زیر استفاده کنید:
                 var interceptor = new RecompileDbCommandInterceptor();
                DbInterception.Add(interceptor);
                var pupils = db.Pupils.Where(p => p.City = city).ToList();
                DbInterception.Remove(interceptor);

                راه حل سوم:

                استفاده از اجرای به تعویق افتاده به شکل زیر است:

                var result = db.Pupils.AsQueryable();
                
                if(searchModel.FirstName != null )
                     result = result.Where(p => p.FirstName == searchModel.FirstName);
                
                if(searchModel.LastName != null )
                     result = result.Where(p => p.LastName == searchModel.LastName);
                
                if(searchModel.PostalZipCode != null )
                     result = result.Where(p => p.PostalZipCode == searchModel.PostalZipCode);
                
                if(searchModel.City != null )
                     result = result.Where(p => p.City == searchModel.City);

                افزونگی کشِ پلن

                استفاده‌ی مجدد از پلن بدلیل عدم ایجاد مجدد آن در زمان اجرای هر کوئری، بسیار خوب است. برای استفاده‌ی مجدد از پلن، باید دستورات ارسالی یکسان باشند؛ مانند کوئری‌های پارامتریک. در EF هنگامیکه از متغیرها استفاده کنید، کوئری‌ها پارامتریک تولید می‌کند؛ اما یک استثناء وجود دارد: ()Skip و ()Take

                2 متد فوق بیشتر جهت صفحه بندی استفاده می‌شوند:

                var schools = db.Schools
                    .OrderBy(s => s.PostalZipCode)
                    .Skip(model.Page * model.ResultsPerPage)
                    .Take(model.ResultsPerPage)
                    .ToList();
                فرض کنید مقدار ResultsPerPage برابر 100 باشد و مقدار Page برابر 417. کوئری ارسالی به شکل زیر خواهد بود:

                حال اگر قصد دریافت اطلاعات صفحه‌ی 500 را داشته باشید، مقادیر کوئری بعدی بترتیب 100 و 50000 خواهد بود و بجای مقادیر تصویر بالا 100 و 50000 قرار داده می‌شوند و کوئری متفاوتی با پلن متفاوتی ایجاد خواهد شد و اس کیو ال پلن کوئری قبلی را مورد استفاده قرار نخواهد داد و با اجرای کوئری دوم، پلن متفاوتی ایجاد خواهد کرد که این باعث ایجاد افزونگی پلن‌ها خواهد شد و همانگونه که قبلا اشاره شد ایجاد پلن جدید هزینه بر است.

                نکته:جهت مشاهده پلن‌های کش شده در اس کیو ال، دستور زیر اجرا کنید:
                SELECT  text, query_plan
                FROM    sys.dm_exec_cached_plans
                        CROSS APPLY sys.dm_exec_query_plan(plan_handle)
                        CROSS APPLY sys.dm_exec_sql_text(plan_handle)
                درصورتیکه کوئری دوم را برای صفحه 500 اجرا کنید، مشاهده خواهید کرد پلن کش شده‌ی دیگری ایجاد شده است. این افزونگی، نامناسب است به دو دلیل:
                1. صدمه به کارآیی؛ هربار EF یک کوئریو اس کیو ال یک پلنجدید را ایجاد می‌کنند.
                2. افزایش اشغال حافظه؛ کش شدن کوئری‌ها توسط EF سمت کلاینت و کش شدن پلن‌ها در اس کیو ال سرور (کش بی رویه‌ی پلن‌ها باعث حذف سایر پلن‌های مورد استفاده بدلیل محدودیت حافظه می‌شود که امکان بروز اختلال در کارآیی را به‌همراه خواهد داشت.)


                علت بروز مشکل:

                هنگامیکه یک مقدار int، به متدهای ()Skip و ()Take ارسال می‌شود، EF نمی‌تواند تشخیص دهد این مقدار ارسالی ثابت (absolute) مانند (100)Take است یا توسط یک متغیر مانند (متغیر)Take تولید شده است. به همین خاطر EF مقدار ارسال شده را پارامتریک در نظر نمی‌گیرد.


                راه حل:

                در EF6 پیاده سازی دیگری برای متدهای ()Skip و ()Take ارائه شده است که برای حل مشکل فوق می‌توان به کار گرفت، این پیاده سازی امکان دریافت lambada بجای int را دارد که باعث ایجاد کوئری‌های پارامتریک خواهد شد.

                int resultsToSkip = model.Page * model.ResultsPerPage;
                var schools = db.Schools
                    .OrderBy(s => s.PostalZipCode)
                    .Skip(() => resultsToSkip) //must pre-calculate this value
                    .Take(() => model.ResultsPerPage)
                    .ToList();
                اکنون کوئری ارسال شده به اس کیو ال به صورت زیر خواهد بود:

                همانطور که مشاهده می‌کنید این بار  EF کوئری پارامتریک ایجاد و ارسال کرده است.

                  ‫کارهایی جهت بالابردن کارآیی Entity Framework #3

                  $
                  0
                  0

                  در قسمت‌های قبلی (^و ^) راهکارهایی جهت بالا بردن کارآیی، ارائه شد. در ادامه، به آخرین قسمت این سری اشاره خواهم کرد.

                  فراخوانی متد شناسایی تغییرات

                  یادآوری:قبل از هر چیز با توجه به این مقالهدانستن این نکته الزامی است که فراخوانی برخی متدها مانند DbSet.Addسبب فراخوانی DataContext.ChangeTracker.DetectChangesخواهند شد.

                  فرض کنید قصد افزودن 2000 موجودیت دانش آموز را دارید:

                  for (int i = 0; i < 2000; i++)
                  {
                      Pupil pupil = GetNewPupil();
                      db.Pupils.Add(pupil);
                  }
                  db.SaveChanges();
                  در کد بالا بدلیل فراخوانی متد DbSet.Add و به دنبال آن فراخوانی متد DetectChanges در هر بار اجرای حلقه (2000بار) مدت زمان اجرای کد بالا بسیار زیاد است و اگر به پروفایلر نگاهی بیاندازید، اشغال CPU توسط کوئری بالا، بیش از حد متعارف است.

                  اگر به تصویر بالا دقت کنید بیش از 34 ثانیه (خط 193 قسمت سوم شکل) جهت افزودن 2000 موجودیت به کانتکست سپری شده است. در حالی که درج این 2000 موجودیت کمی بیش از 1 ثانیه (خط 195 قسمت سوم شکل) که 379 میلی ثانیه (قسمت دوم شکل) آن مربوط به اجرای کوئری اختصاص یافته  طول کشیده است.
                  بیشترین زمان صرف شده‌ی برای درج 2000 موجودیت، در کد برنامه سپری شده است که با بررسی بیشتر در پروفایلر، متوجه زمان بر بودن فراخوانی متد ()DetectChanges که در فضای نام Data.Entity.Core وجود دارد خواهید شد. این متد 2000 بار به تعداد موجودیت هایی که قصد داریم به بانک اطلاعاتی اضافه نماییم، فراخوانی شده است.

                  همانطور که در شکل بالا مشخص است همان 34 ثانیه جهت ردیابی تغییرات صرف شده است. EF ردیابی تغییرات را بصورت پیش فرض هر زمانی که قصد افزودن یا ویرایش موجودیتی را داشته باشید، انجام خواهد داد و هر چه موجودیت‌های بیشتری را بخواهید ویرایش یا اضافه نمایید، این زمان نیز بیشتر خواهد شد. در حقیقت زمان لازم برای الگوریتم ردیابی تغییرات بصورت نمایی با رشد موجودیت‌ها افزوده می‌شود. به عبارت دیگر اگر این تعداد موجودیت‌ها را به 4000 عدد برسانید، مدت زمان لازم بیش از 2 برابر افزوده خواهد شد.

                  راه حل اول:
                  استفاده از متد ()AddRange ارائه شده در EF6 که جهت درج دسته‌ای (Bulk Insert) ارائه شده است:
                  var list = new List<Pupil>();
                  
                  for (int i = 0; i < 2000; i++)
                  {
                      Pupil pupil = GetNewPupil();
                      list.Add(pupil);
                  }
                  
                  db.Pupils.AddRange(list);
                  db.SaveChanges();

                  راه حل دوم:

                  در سناریوهای پیچیده، مانند درج دسته‌ای چندین موجودیت، شاید مجبور به خاموش نمودن این قابلیت شوید:
                  db.Configuration.AutoDetectChangesEnabled = false;
                  توجه داشته باشید اگر قصد دارید از امکان ردیابی تغییرات مجددا بهره‌مند شوید، باید این قابلیت را نیز فعال نمایید. با خاموش نمودن ردیابی تغییرات بار دیگر کوئری ابتدایی را اجرا نمایید. مدت زمان لازم جهت افزودن 2000 موجودیت به کانتکست از بیش از 34 ثانیه به 85 میلی ثانیه کاهش یافته است؛ ولی اعمال تغییرات به بانک اطلاعاتی همانند مرتبه اول بیش از 1 ثانیه طول خواهد کشید.


                  ردیابی تغییرات

                  هنگامی که موجودیتی را از بانک اطلاعاتی دریافت نمایید، می‌توانید آن را ویرایش نمایید و مجددا به بانک اطلاعاتی اعمال نمایید. چون EF اطلاعی از قصد شما برای موجودیت نمی‌داند، مجبور است تغییرات شما را زیر نظر بگیرد که این زیر نظر گرفتن، هزینه و سربار دارد و این سربار و هزینه برای داده‌های زیاد، بیشتر خواهد شد. بنابراین اگر قصد دارید اطلاعاتی فقط خواندنی را از بانک اطلاعاتی دریافت نمایید، بهتر است صراحتا به EF بگویید این موجودیت را تحت ردیابی قرار ندهد.
                  string city = "New York";
                  
                  List<School> schools = db.Schools
                      .AsNoTracking()
                      .Where(s => s.City == city)
                      .Take(100)
                      .ToList();

                  استفاده از متد AsNoTracking  در کد بالا سبب خواهد شد 100 مدرسه که در شهر نیویورک وجود دارد توسط EF، بدون تحت نظر گرفتن آن‌ها از بانک اطلاعاتی دریافت شوند و سرباری نیز تحمیل نشود.

                  ویوهای از قبل کامپایل شده

                  معمولا، هنگامی که از EF برای اولین بار استفاده می‌نمایید، ویوهایی ایجاد می‌گردد که برای ایجاد کوئری‌ها مورد استفاده قرار می‌گیرند. این ویوها در طول حیات برنامه فقط یکبار ایجاد می‌شوند. ولی همین یکبار هم زمانبر هستند. خوشبختانه راه‌هایی وجود دارد که ایجاد این ویوها را در زمان runtime انجام نداد و آن راه، استفاده از ویوهای از پیش کامپایل شده است. یکی از راههای ایجاد این ویوها استفاده از Entity Framework Power Toolsاست. بعد از نصب اکستنشن، بر روی فایل کانتکست راست کلیک کرده و سپس گزینه‌ی Generate Views را از منوی Entity Framework انتخاب کنید.

                  توجه داشته باشید که هر تغییری را بعد از ایجاد این ویوها بر روی کانتکست اعمال نمایید، باید آن‌ها را مجددا تولید کنید. برای آشنایی بیشتر با این ویوها به این لینکمراجعه کنید. هم چنین پکیج نیوگتی بنام EFInteractiveViewsنیز برای این منظور تهیه و توزیع شده است.


                  حذف کوئری‌های ابتدایی غیر ضروری

                  در هنگام شروع به کار با EF، چندین کوئری آغازین بر روی دیتابیس اجرا می‌شوند. یکی از کوئری‌های آغازین جهت تشخیص نسخه‌ی دیتابیس است که همانطور در تصویر زیر مشاهده می‌کنید، در حدود چند میلی ثانیه می‌باشد.

                  با توجه به توضیحات، در صورتیکه اطلاعی از نسخه‌ی دیتابیس دارید، می‌توانید این کوئری ابتدایی را تحریف نمایید. برای اینکار می‌توان توسط متد ()ResolveManifestTokenکلاسی که اینترفیس IManifestTokenResolverرا پیاده سازی کرده است، نسخه‌ی دیتابیس را برگردانیم و از یک رفت و برگشت به دیتابیس جلوگیری نماییم.

                   public class CustomManifestTokenResolver : IManifestTokenResolver
                  {
                      public string ResolveManifestToken(DbConnection connection)
                      {
                          return "2014";
                      }
                  }
                  و توسط کلاسی که از کلاس DbConfiguration ارث بری کرده است آن را استفاده نماییم.
                   public class CustomDbConfiguration : DbConfiguration
                  {
                      public CustomDbConfiguration()
                      {
                          SetManifestTokenResolver(new CustomManifestTokenResolver());
                      }
                  }


                  تخریب کانتکست

                  تخریب و از بین بردن کانتکست هنگامی که به آن نیاز نداریم بسیار ضروری است. یکی از روشهای اصولی برای Disposing کانتکست، محصور کردن آن بوسیله دستور Using است (البته فرض بر این است که قرار نیست از الگوهای اشاره شده استفاده نماییم). در صورت عدم تخریب صحیح کانتکست باید منتظر آسیب جدی به کارایی Garbage Collector جهت آزاد سازی منابع مورد استفاده کانتکست و هم چنین باز نمودن اتصالات جدید به دیتابیس باشید.


                  پاسخگویی به چندین درخواست بر روی یک کانکشن

                  EF از قابلیتی بنام Multiple Result Sets می‌تواند بهره ببرد که این قابلیت باعث می‌شود بر روی یک کانکشن ایجاد شده، یک یا چند درخواست از دیتابیس ارسال و یا دریافت شود که سبب کاهش تعداد رفت و برگشت به دیتابیس می‌شود. کاربرد دیگر این قابلیت، زمانی است که تاخیر زیادی (latency) بین اپلیکیشن و دیتابیس وجود دارد.

                  برای فعالسازی کافی است مقدار زیر را در کانکشن استرینگ اضافه نمایید:

                  MultipleActiveResultSets=True;


                  استفاده از متدهای ناهمگام

                  در C#5 و EF6 پشتیبانی خوبی از متدهای ناهمگامفراهم شده است و اکثر متدهایی مانند ToListAsync, CountAsync, FirstAsync, SaveChangesAsync و غیره که باعث رفت و برگشت به دیتابیس می‌شوند امکان پشتیبانی ناهمگام را نیز دارند. البته این قابلیت برای برنامه‌های یک درخواست در یک زمان شاید مفید نباشد؛ ولی برای برنامه‌های وبی برعکس. در برنامه وب جهت پشتیبانی از بارگذاری همزمان (concurrent) قابلیت ناهمگام (Async) سبب خواهد شد منابع تا زمان اجرای کوئری به ThreadPool بازگردانده شود و برای سرویس دهی مهیا باشند که باعث افزایش scalability خواهد شد.

                  بررسی و آزمایش با داده‌های واقعی

                  در اکثر مواقع کارآیی با حجیم شدن داده‌ها کاهش پیدا می‌کند (البته در صورت عدم رعایت اصول استاندارد). بنابراین بررسی کارآیی در محیط هایی با حجم داده‌های بالا ضروری است. هیچ چیز بدتر از آن نیست که همه چیز در محیط توسعه خوب و بی نقص باشد ولی در محیط عملیاتی به شکست بیانجامد. به همین جهت سعی کنید از ابزارهای تولید داده (^و ^و ^) برای ایجاد داده‌های آزمایشی استفاده نمایید. سپس کارآیی کوئری خود را مورد بررسی و آزمایش قرار دهید.

                  ‫آموزش LINQ بخش چهارم

                  $
                  0
                  0
                   بررسی عبارت Join
                  عبارت Join برای ایجاد ارتباط بین عناصر دو توالی استفاده می‌شود. پیش نیاز این عملیات وجود یک مقدار در عناصر توالی ورودی است که بتوان از طریق آن برابری دو عنصر را اهراز کرد.
                  عبارت join، دو توالی را به عنوان ورودی دریافت می‌کند. هر عنصر در توالی اول باید یک خصوصیت داشته باشد که بتوان آن را با یک خصوصیت از عناصر توالی دوم مقایسه کرد. عبارت Join، مقادیر مشخص شده را از طریق کلمه‌ی کلیدی equals مقایسه می‌کند. باید توجه داشت که عملیات join  برای بررسی برابری دو مقدار، در دو توالی مختلف است. از این رو به آن equal-joins می‌گویند.
                  فرم خروجی عبارت join  بر اساس نوع عملیات join ای است که ما انجام می‌دهیم.

                  انواع JOIN :
                  • Inner JOIN
                  • Group JOIN
                  • Left JOIN

                  کلاس‌های زیر را در نظر بگیرید:
                  /// <summary>
                  /// دستور العمل
                  /// </summary>
                  class Recipe
                  {
                    public int Id { get; set; }
                    public string Name { get; set; }
                  }
                  
                  /// <summary>
                  /// بازخورد
                  /// </summary>
                  class Review
                  {
                    public int RecipeId { get; set; }
                    public string ReviewText { get; set; }
                  }
                  بر اساس مدل‌های تعریف شده، هر Recipe ممکن است 0، 1 یا تعداد نامحدودی Review داشته باشد. همانطور که می‌بینید هیچ رابطه‌ی مستقیمی این دو مدل با هم ندارند. در کلاس Review یک خصوصیت وجود دارد که ID مدل Recipe را در خود نگه می‌دارد.

                  Inner Join
                   این دستور عنصری از  توالی اول را که متناظر با آن عنصری در توالی دوم وجود داشته باشد، به خروجی می‌برد.
                  مثال:

                  Recipe[] recipes =
                  {
                     new Recipe {Id = 1, Name = "Mashed Potato"},
                     new Recipe {Id = 2, Name = "Crispy Duck"},
                     new Recipe {Id = 3, Name = "Sachertorte"}
                  };
                  
                  Review[] reviews =
                  {
                     new Review {RecipeId = 1, ReviewText = "Tasty!"},
                     new Review {RecipeId = 1, ReviewText = "Not nice :("},
                     new Review {RecipeId = 1, ReviewText = "Pretty good"},
                     new Review {RecipeId = 2, ReviewText = "Too hard"},
                     new Review {RecipeId = 2, ReviewText = "Loved it"}
                  };
                  
                  var query = from recipe in recipes
                  join review in reviews on recipe.Id equals review.RecipeId
                  select new //anonymous type
                  {
                     RecipeName = recipe.Name,
                     RecipeReview = review.ReviewText
                  };
                  
                  foreach (var item in query)
                  {
                     Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}");
                  }
                  در مثال فوق، در ابتدا دو توالی تعریف شد. recipes که تعدادی دستور العمل و reviews که مجموعه‌ای از بازخوردها را در خود نگه می‌دارند.
                  پرس و جو با عبارت from که اشاره‌گری به توالی اول است آغاز و با معرفی یک متغیر Range به نام Recipe ادامه پیدا می‌کند. سپس دستور Join نوشته شده است. مجددا متغیر range  جدیدی به نام review معرفی شده که به عناصر توالی دوم اشاره خواهد کرد. در ادامه کلمه‌ی کلیدی on اجازه می‌دهد که عناصر یکسانی در توالی اول را با عناصر یکسانی در توالی دوم مشخص کنیم. کلمه‌ی کلیدی equal هم برای مقایسه‌ی برابری دو مقدار استفاده شده است. عبارت join  بر اساس مقدار id  در مجموعه recipes و همچنین Recipeid در مجموعه‌ی reviews خروجی را مهیا خواهد کرد.
                  خروج مثال فوق به شکل زیر است :
                  Mashed Potato-Tasty!
                  Mashed Potato-Not nice :(
                  Mashed Potato-Pretty good
                  Crispy Duck-Too hard
                  Crispy Duck-Loved it
                  همانطور که مشاهده می‌کنید کلیه دستورالعمل‌هایی که بازخوردی برای آنها وجود دارد، در خروجی وجود دارند.

                  Group Join
                  بکارگیری into به همراه join، دستور Group Join را می‌سازد.
                  این دستور  خروجی حاصل از join  را گروه بندی خواهد کرد. Group join یک توالی سلسله مراتبی را تولید می‌کند که در آن یک عنصر از توالی اول با یک یا چند عنصر در توالی دوم در ارتباط است.
                  مثال:
                  var query = from recipe in recipes
                  join review in reviews on recipe.Id equals review.RecipeId
                  into reviewGroup
                  select new //anonymous type
                  {
                     RecipeName = recipe.Name,
                     Reviews = reviewGroup//collection of related reviews
                  };
                  
                  foreach (var item in query)
                  {
                     Console.WriteLine($"Review for {item.RecipeName}");
                     foreach (var review in item.Reviews)
                     {
                         Console.WriteLine($"-{review.ReviewText}");
                     }
                  }
                  در این مثال از 2 توالی تعریف شده‌ی در مثال قبل استفاده کرده‌ایم.
                  متغیر reviewGroup توالی حاصل از اجرای join  را نمایش می‌دهد. برای ایجاد توالی خروجی، نتیجه به یک نوع بی نام، بازتاب شده است. هر عنصر در نوع بی نام یک گروه را نشان می‌دهد. نوع بی نام شامل دو خصوصیت RecipeName که مقدار آن از توالی اول می‌آید و Reviews که حاصل خروجی Join است می‌باشد.
                  خروجی مثال بالا به شکل زیر است:
                  Review for Mashed Potato
                  -Tasty!
                  -Not nice :(
                  -Pretty good
                  Review for Crispy Duck
                  -Too hard
                  -Loved it
                  Review for Sachertorte
                  همانطور که می‌بینید اینبار عنصر Sachertorte در خروجی ظاهر شده است. اگر عنصری در توالی سمت چپ وجود داشته باشد که عنصر و یا عناصری در توالی سمت راست با آن متناظر نباشند، آنگاه دستور join،  یک لیست خالی را برای این عنصر تخصیص می‌دهد.

                  Left outer join
                  برای آنکه بتوان یک خروجی غیر سلسله مراتبی و اصطلاحا flat را ایجاد کرد که مانند خروجی مثال group join  باشد (نمایش عنصر Sachertorte) می‌توان از عملگر  ()DefaultIfEmpty به‌همراه یک عبارت from اضافی و معرفی یک متغیر Range استفاده کرد. در صورت نبودن عنصر متناظر در توالی دوم این متغیر range بصورت null تنظیم می‌شود.
                  مثال :
                  در این مثال از مجموعه‌های تعریف شده‌ی در بخش اول مطلب استفاده کرده‌ایم:
                  var query = from recipe in recipes
                  join review in reviews on recipe.Id equals review.RecipeId
                  into reviewGroup
                  from rg in reviewGroup.DefaultIfEmpty()
                  select new //anonymous type
                  {
                     RecipeName = recipe.Name,
                     //RecipeReview = rg.ReviewText SystemNullException
                     RecipeReview = (rg == null ? "n/a" : rg.ReviewText)
                  };
                  
                  foreach (var item in query)
                  {
                     Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}");
                  }
                  در بخشی از کد بالا به خاطر وجود حالت null، در خروجی استثنائی رخ خواهد داد. به همین خاطر قبل از تخصیص، مقدار را کنترل کرده‌ایم.
                  خروجی مثال فوق به شکل زیر است:
                  Mashed Potato-Tasty!
                  Mashed Potato-Not nice :(
                  Mashed Potato-Pretty good
                  Crispy Duck-Too hard
                  Crispy Duck-Loved it
                  Sachertorte-n/a
                  همانطور که مشاهده می‌کنید، خروجی به صورت غیر سلسله مراتبی و flat می‌باشد. از طریق دستور select و جایگزین کردن مقدار N/A به جای null، عنصر Sachertorte را در خروجی مهیا کرده‌ایم .
                  یک راه جایگزین برای کنترل مقدار null در دستور select بازسازی متد DefaultIfEmpty به‌صورتی است که به جای ایجاد مقدار null، یک نمونه‌ی جدید از مدل review را با مقدار پیش فرض N/A ایجاد کند.
                  مثال:
                  var query = from recipe in recipes
                  join review in reviews on recipe.Id equals review.RecipeId
                  into reviewGroup
                  from rg in reviewGroup.DefaultIfEmpty(new Review { ReviewText = "N/A" })
                  select new //anonymous type
                  {
                     RecipeName = recipe.Name,
                     RecipeReview = rg.ReviewText
                  };
                  
                  foreach (var item in query)
                  {
                     Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}");
                  }
                  خروجی مثال بالا :
                   Mashed Potato-Tasty!
                  Mashed Potato-Not nice :(
                  Mashed Potato-Pretty good
                  Crispy Duck-Too hard
                  Crispy Duck-Loved it
                  Sachertorte-N/A 

                  ‫بررسی مفاهیم متغیرهای Value Type و Reference Type در سی شارپ

                  $
                  0
                  0
                  نوع داده(Data Type) ، متغیر‌ها(Variables) ، انواع مقداری(Value Type) ، انواع ارجاعی(Reference Type)

                  مقدمه :
                  نوع داده‌ها، اجزای اصلی سازنده‌ی یک زبان برنامه نویسی و شبیه قواعد هر زبانی هستند.
                  مفاهیمی که در این مطلب بررسی خواهد شد :
                   • Data Type نوع داده
                   • Variables  متغیرها
                   • Naming Convention قرارداد‌های نامگذاری
                   • Value Type/Reference Type انواع مقداری و ارجاعی
                   • Stack/heap memory  حافظه پشته و هرم

                  نوع داده

                  در دنیای واقعی، برای نگهداری مواد مختلف، ظروف مختلفی با اندازه‌های مختلفی طراحی شده است. در دنیای برنامه نویسی، به تناسب اطلاعاتی که می‌خواهیم در حافظه ذخیره کنیم، باید نوع ظرف ذخیره سازی را انتخاب کنیم. نوع ظرف ذخیره سازی را در دنیای برنامه نویسی، نوع داده‌ها مشخص می‌کنند.
                  در دات نت، همه‌ی نوع داده‌ها (Data Type) بصورت مستقیم و یا غیر مستقیم، از کلاس System.Object مشتق شده‌اند.


                  متغیرها

                  متغیر‌ها برای ذخیره‌ی مقادیر (اطلاعات)، استفاده می‌شوند. به این مثال دقت کنید: ما یک کیف داریم که در آن یک کتاب قرار دارد. در اینجا کیف نقش متغیر و کتاب نقش مقدار (value) را ایفا می‌کند. اندازه‌ی کیف همان نوع داده (Data Type) در دنیای برنامه نویسی می‌باشد.


                  چک کردن سایز نوع داده (Data Type)

                  ما نیازی به حفظ کردن اندازه‌ی نوع داده‌ها نداریم. در سی شارپ متدی به نام () sizeof مهیا شده است که با چک کردن نوع داده، اندازه‌ی آن را بر حسب بایت نمایش می‌دهد.
                  به مثال زیر دقت کنید:
                  Console.WriteLine(sizeof(int));
                  Console.WriteLine(sizeof(char));
                  Console.WriteLine(sizeof(bool));
                  Console.WriteLine(sizeof(decimal));
                  Console.WriteLine(sizeof(float));
                  خروجی کد‌های بالا :
                   4
                  2
                  1
                  16
                  4

                  نکته : متد sizeof فقط برای نمایش اندازه‌ی نوع داده‌های مقداری (value type) می‌تواند مورد استفاده قرار گیرد.


                  چک کردن نوع داده

                  ما می‌توانیم نوع داده‌ها را برای بدست آوردن کلاسی که به آن تعلق دارند، چک کنیم.
                  مثال :
                   int a = 23;
                  float b = 3.14f;
                  Console.WriteLine(a.GetType());
                  Console.WriteLine(b.GetType());
                  خروجی کد‌های بالا : 
                  System.Int32
                  System.Single

                  چک کردن نوع داده‌ی دو شیء

                  فرض کنید 2 شیء را با نام‌های obj1 و obj2 داریم که هر دو از نوع long هستند. برای اینکه این مقایسه را انجام دهیم، از متد Object.RefrenceEqual می‌توان استفاده کرد.
                  مثال :
                  long obj1 = 356;
                  long obj2 = 54;
                  float obj3 = 234;
                  Console.WriteLine(object.ReferenceEquals(obj1.GetType(), obj2.GetType()));
                  Console.WriteLine(object.ReferenceEquals(obj1.GetType(), obj3.GetType()));
                  خروجی کد‌های بالا : 
                  True
                  False

                  تعریف یک متغیر ومقدار دهی به آن
                  سی شارپ یک زبان strongly typed است (البته با در نظر نگرفتن نوع dynamic آن). به این معنا که کلیه‌ی متغیر‌ها، قبل از استفاده باید تعریف و مقدار دهی شوند و بعد از تعریف متغیر، نمی‌توان نوع آن را تغییر داد. رفتار یک متغیر بر اساس نوع انتخابی ما مشخص می‌شود. بطور مثال با انتخاب نوع int تنها می‌توان اعداد صحیح را ذخیره و نگهداری کرد و برای تغییر رفتار متغیر‌ها باید آنها را تبدیل کنیم.

                  تعریف یک متغیر
                  برای استفاده از یک متغیر ابتداباید آن را تعریف کنیم :
                  //<data type> <variable name>;
                  Int a;

                  مقداردهی اولیه یک متغیر

                  مقدار دهی اولیه‌ی یک متغیر با استفاده از عملگر = و نوشتن مقدار مورد نظر برای ذخیره کردن در متغیر، در سمت راست عملگر اتفاق خواهد افتاد.
                  //<data type> <variable name>=value;
                  Int a=23;
                  Int a;//declare تعریف
                  a=23;//مقدار دهی اولیه initializing
                  Int a=23;//تعریف و مقدار دهی در یک خط
                  Int a,b,c=23;//تعریف چند متغیر و مقدار دهی در یک خط


                  قرار دا‌دهای نام گذاری متغیر‌ها :

                  در دنیای برنامه نویسی دو نوع قرار داد نام گذاری بسیار متداول وجود دارند:
                   1-  camelCase : در این قرار داد، حرف اول کلمه‌ی اول، بصورت کوچک و حرف اول از کلمه‌ی دوم، بصورت بزرگ نوشته خواهد شد. برای مثال: firstName,lastName
                   2- PascalCase : در این قرار داد حروف ابتدایی دو کلمه‌ی مجاور، بصورت بزرگ نوشته خواهند شد: FirstName,LastName

                  چند نکته :
                   • نامگذاری متغیر‌ها را می‌توانید با علامت _ و یا @ شروع کنید.
                   • کلمات کلیدی (key word) سی شارپ نمی‌توانند به عنوان نام متغیر مورد استفاده قرار بگیرند (مگر آنکه با @ شروع شوند).
                   • در بین نام متغیر نباید فضای خالی وجود داشته باشد. کاراکتر‌های سازنده‌ی متغیر می‌توانند اعداد، حروف و زیر خط باشند.
                  لیستی از نام گذاری‌های مجاز:
                   int abc;
                  long _abcd;
                  float @abcd;
                  bool main_button;
                  decimal piValue;
                  string firstName;
                  string first_name;
                  bool button55_on;
                  لیستی از نام گذاری‌های غیر مجاز
                  long _a.5bc5d;
                  float @ab cd;
                  decimal pi@Value;
                  //استفاده از کلمات کلیدی سی شارپ که کامپایلر آنها را مجاز نمی‌داند
                  bool class;
                  string namespace;
                  string string;
                  int static;
                  برای مطالعه‌ی کاملتر کلمات کلیدی سی شارپ می‌توانید اینجارا مطالعه کنید.


                  در ادامه کمی در مورد نوع داده‌ها بحث خواهیم کرد.
                  در سی شارپ دو مدل نوع داده وجود دارد:
                   • انواع مقداری Value Type
                   • انواع ارجاعی یا اشاره‌ای Reference Type

                  انواع مقداری (Value Type) :
                   • انواع مقداری مستقیما حاوی داده‌ها هستند. اگر یک متغیر از نوع مقداری را به یک متغیر دیگر تخصیص دهید، مقدار آنها مستقیما کپی می‌شوند؛ برعکس نوع‌های اشاره‌ای که با نخصیص یک متغیر به یک متغیر دیگر، تنها اشاره‌گر به مقدار شیء کپی خواهد شد و نه خود شیء.
                   • کلیه نوع‌های مقداری از کلاس ValueType مشتق شده‌اند.
                   • در فضای stack  به آنها حافظه تخصیص داده می‌شود.
                   • نمی‌توانند مقدار null  بپذیرند. البته با قابلیت nullabletype امکان تخصیص مقدار null به نوع داده‌های مقداری نیز مهیا شده است.
                   • همه نوع‌های داده‌های مقداری، یک سازنده پیش فرض دارند که به صورت ضمنی کار مقدار دهی اولیه برای آنها را انجام می‌دهد. برای مطالعه بیشتر درباره مقادیر پیش فرض به اینجامراجعه کنید.

                  انواع مقداری به دو دسته‌ی اصلی تقسیم می‌شود :
                   • Structs
                   • Enumerations

                  طبقه بندی Structs به صورت زیر است :
                   • Numeric Type
                  * Integral Type : sbyte,short,ushort,int,uint,long,ulong,char
                  * Floating-Point Types : float,double
                  * Decimal : decimal
                   •  Bool دو مقدار true و false
                   • User Defined Struct


                  نوع داده نال (تهی) پذیر (nullable Type) و چگونگی تعریف آن

                  در ابتدای معرفی نوع داده‌های مقداری گفتیم همیشه باید وضعیت متغیر مشخص و مقدار دهی اولیه‌ی آن یا به صورت ضمنی و یا آشکار انجام شود. هیچ یک از نوع داده‌های مقداری نمی‌توانند بصورت null تعریف شوند. برای تبدیل یک نوع داده مقداری به صورتی که قابلیت ذخیره‌ی مقدار null را داشته باشد، بعد از نوشتن نوع داده، علامت سوال ؟ قرار می‌دهیم.
                  < data type >? < variable name >= null; //syntax

                  int? a = null; //assigning null
                  int? b = 55; //assigning null and a value
                  var? c = 55 //it will give error

                  نکته :  var نمی‌تواند بصورت nullable تعریف شود.

                  برای چک کردن مقدار در انواع تهی پذیر (nullable) دو خصوصیت وجود دارد:
                   • HasValue
                  اگر مقداری در متغیر وجود داشته باشد ارزش true  بازگردانده می‌شود؛ در غیر اینصورت ارزش false
                   • Value
                  مقدار واقعی متغیر را باز می‌گرداند.

                  مثال :
                   int? a = null;
                  int? b = 22;
                  Console.WriteLine(a.HasValue);
                  //------------
                  Console.WriteLine(b.HasValue);
                  Console.WriteLine(b.Value);
                  خروجی کد بالا :
                   False
                  True
                  22

                  انواع ارجاعی Reference Type

                  انواع ارجاعی مستقیما حاوی اطلاعات نیستند و ارجاعی هستند به آدرسی از حافظه که حاوی اطلاعات واقعی است. به بیانی دیگر، اشاره‌گری به آدرسی از حافظه هستند.
                   • انواع ارجاعی بصورت غیر مستقیم حاوی داده‌ها هستند.
                   • در بخشی از حافظه که به آن heap می‌گوییم، به آنها فضا اختصاص داده می‌شود.
                   • می‌توانند بصورت null (بدون مقدار) باشند.

                  انواع ارجاعی نیز به دو دسته‌ی کلی تقسیم می‌شوند :

                   • انواع از پیش تعریف شده
                    Object,string,dynamic
                   • انواع تعریف شده توسط کاربر
                          class,interface,delegate

                  نکته : آدرس مکانی از حافظه که داده‌ها در آن قرار دارند، در بخش پشته یا Stack ذخیره می‌شود و داد‌ه‌ها در فضای heap ذخیره می‌شوند.
                  مثال :
                   test obj; //allocating reference on stack
                  obj= new test(55);//allocating object on heap

                  نکته : دو متغیر از نوع ارجاعی می‌توانند به یک آدرس از حافظه اشاره کنند. در شکل زیر این موضوع نشان داده شده است.

                   
                  در شکل زیر طبقه بندی نوع داده‌ها در سی شارپ نشان داده شده است :


                  • عملیات کپی در نوع داده مقداری
                  وقتی از یک متغیر مقداری را به یک متغیر دیگر تخصیص می‌دهیم، یک کپی جدید از آن در فضای stack  ایجاد می‌شود. بدین معنی که محتوای دو متغیر یکسان هستند، ولی در دو بخش مجزای در حافظه‌ی Stack قرار دارند. به همین خاطر تغییر  محتوای یک متغیر، محتوای متغیر دیگر را تغییر نمی‌دهد.
                  مثال :
                   int a = 55;//declare a and initialize
                  int copya = a;//copya contains the copy of value a
                  دیاگرام حافظه کد بالا :

                   

                  • عملیات کپی، در نوع داده‌ی ارجاعی
                  وقتی یک متغیر از نوع ارجاعی را به یک متغیر دیگر تخصیص می‌دهیم، دو اشاره‌گر در فضای Stack ایجاد می‌شود که به یک مقدار واحد در حافظه‌ی heap اشاره می‌کنند. آدرس‌های ذخیره شده‌ی در stack  یکسان هستند.
                  مثال : در اینجا فرض بر این است کهtest یک کلاس تعریف شده‌ی توسط کاربر می‌باشد.
                  test obj;
                  obj=new test(23);
                  test objCopy;
                  objCopy = obj;

                  دیاگرام حافظه‌ی قطعه کد بالا به شکل زیر است :



                  تخصیص حافظه در بخش Stack  و Heap به متغیر‌ها

                  سیستم عامل و net CLR. حافظه را به دو بخش stack و heap تقسیم بندی می‌کنند.
                  زمانی که یک متد را فراخوانی می‌کنیم، در بخش پشته به پارامتر‌های متد فضا تخصیص داده می‌شود و بعد از پایان کار متد، فضای اشغال شده‌ی بوسیله GC یا همان Garbage collection  آزاد می‌شود.
                  تخصیص حافظه در Stack  بر اساس قانون LIFO انجام و به ترتیب و پشت سر هم، حافظه تخصیص داده می‌شود. دیاگرام تخصیص حافظه به stack:


                  تخصیص حافظه در Heap بصورت تصادفی است؛ بر عکس پشته (stack) که به ترتیب و متوالی انجام می‌شد. انواع ارجاعی در Stack  ذخیره می‌شوند؛ ولی داده‌ی واقعی در heap قرار می‌گیرد.
                  حافظه‌های پویا در بخش heap و حافظه‌های استاتیک در بخش stack تخصیص داده می‌شوند.
                   

                  ‫اشیاء تغییر ناپذیر (Immutable Object)

                  $
                  0
                  0
                  کلمه‌ی mutable به معنای تغییر پذیر و کلمه‌ی immutable به معنای تغیر ناپذیر در زبان انگلیسی تعریف شده‌اند. در دنیای IT این دو واژه نیز همین معنا را دارند. بطور مثال: یک رشته‌ی mutable، یعنی رشته‌ای که بتوان آن را تغییر داد و یک رشته‌ی immutable یعنی رشته‌ای که غیر قابل تغییر است.



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

                  تعریف اشیاء تغییر ناپذیر (Immutable Objects) :
                  این اشیاء، اشیائی هستند که بعد از بارگزاری در حافظه، به هیچ وجه نمی‌توانند اصلاح و یا تغییر کنند. نه از طریق خارجی (کاربران External) و نه از طریق داخلی (اعضای کلاس Internal).

                  چه زمانی از این اشیاء استفاده می‌کنیم؟
                  اشیاء تغییر ناپذیر برای داده‌های استاتیک استفاده می‌شوند و نمونه‌هایی از آن در بخش زیر لیست شده‌اند:

                   • داده‌های اصلی (Master Data): یکی از بیشترین کاربرد‌های اشیاء تغییر ناپذیر، برای بارگذاری داده‌های اصلی است (کشور‌ها، واحد‌های پولی، استان ها) و داده‌هایی که به ندرت تغییر می‌کنند. این داده‌های اصلی بعد از بارگزاری در حافظه، دیگر تغییر نخواهند کرد.

                   • اطلاعات پیکره‌بندی (Configuration Data): همه‌ی برنامه‌ها نیاز به اطلاعات پیکره‌بندی دارند. در دنیای برنامه‌های مایکروسافت، عموما این اطلاعات پیکره بندی را در فایل‌های web.config و App.config ذخیره می‌کنیم. این نوع اطلاعات بصورت یک شیء در حافظه بارگذاری می‌شوند و بعدا تغییر نخواهند کرد.

                   • اشیاء Singleton: اشیاء singleton اشیائی هستند که تنها یک نمونه از آنها را می‌توان ایجاد کرد. در برنامه‌ها از این اشیاء برای اشتراک گذاشتن اطلاعات استاتیک استفاده می‌کنند. اگر این اطلاعات تغییر نکنند، یکی از گزینه‌ها، استفاده از اشیاء تغییر ناپذیر هستند.

                  چگونه می‌توان در سی شارپ اشیاء تغییر ناپذیر را ایجاد کنیم؟

                  اشیاء تغییر ناپذیر (immutable objects) تنها توسط کلاس‌های تغییر ناپذیر (immutable classes) می‌توانند ایجاد شوند.
                  برای ایجاد کلاسی تغییر ناپذیر، سه مرحله باید طی شود:
                   1- حذف بلاک Set: همانطور که گفته شد، بخش Set از property ‌ها باید حذف شود. با حذف این بخش بعد از بارگزاری شیء در حافظه، دیگر نمی‌توان آن را تغییر داد:
                  public class Currency
                  {
                    private string _currencyName;
                    private string _countryName;
                  
                    public string CurrencyName
                    {
                       get { return _currencyName; }
                    }
                  
                    public string CountryName
                    {
                       get { return _countryName; }
                    }
                  }

                   2- مهیا کردن پارامتر‌ها از طریق سازنده‌ی کلاس: با حذف بلاک set، راهی برای بارگزاری اطلاعات، در کلاس وجود ندارد. از این رو می‌توان از طریق پارامتر‌های سازنده‌ی کلاس، اطلاعات را به شیء ارسال کرد.
                    private string _currencyName;
                    private string _countryName;
                    public Currency(string paramCurrencyName,string paramCountryName)
                    {
                       _currencyName= paramCurrencyName;
                       _countryName = paramCountryName;
                    }

                   3- تعریف متغیر‌های کلاس به صورت فقط خواندنی READONLY
                  در تعریف اولیه گفته شد که اشیاء immutable نه از طریق خارجی (کاربر) و «کمی فراتر» نه از طریق داخلی (اعضای کلاس) قابل تغییر نیستند. اما کلاس ایجاد شده را می‌توان بعد از ایجاد نمونه‌ای از آن، مجددا تغییر داد. کافی است یک متد به شکل زیر در آن تعریف کنیم و به‌راحتی وضعیت شیء را از طریق آن تغییر دهیم.
                    public void DoSomthing()
                    {
                       _countryName = "somthing else";
                    }

                  راه حل ارائه شده‌ی برای حل این موضوع، معرفی متغیر‌ها به صورت readonly می‌باشد. متغیرهایی که بصورت فقط خواندنی تعریف می‌شوند، تنها از طریق سازنده‌ی شیء می‌توانند مقداردهی اولیه شوند.
                  باز طراحی نهایی کلاس Currency  به صورت زیر است:
                   public class Currency
                  {
                    private readonly string _currencyName;
                    private readonly string _countryName;
                    public string CurrencyName
                    {
                      get { return _currencyName; }
                    }
                  
                    public string CountryName
                    {
                      get { return _countryName; }
                    }
                    public Currency(string paramCurrencyName,string paramCountryName)
                    {
                       _currencyName= paramCurrencyName;
                       _countryName = paramCountryName;
                    }
                  }

                  ‫آموزش LINQ بخش پنجم

                  $
                  0
                  0
                  در ادامه سری آموزشی LINQ، موارد دیگری از عبارت‌های پرس و جو را بررسی می‌کنیم:
                   • عبارت group
                   • عبارت orderby
                   • کلمات کلیدی ascending و descending
                   • کلمه کلیدی by


                  بررسی عبارت group و کلمه‌ی کلیدی by

                  عبارت group یک توالی یک بعدی (flat sequence) یا ساده را از ورودی دریافت و یک توالی گروه بندی شده را تولید می‌کند.
                  مثال: در بخش دوماین سری آموزشی، کلاسی به نام Ingredient تعریف کردیم که مواد غذایی و کالری آنها را نگهداری می‌کرد. در این مثال قصد داریم مواد غذایی دریافت شده از توالی ورودی را بر اساس کالری آنها، به گروه‌هایی تقسیم کنیم، بطوریکه مواد غذایی با کالری‌های یکسان در یک گروه قرار بگیرند:
                  Ingredient[] ingredients =
                  {
                     new Ingredient{Name = "Sugar", Calories=500},
                     new Ingredient{Name = "Lard", Calories=500},
                     new Ingredient{Name = "Butter", Calories=500},
                     new Ingredient{Name = "Egg", Calories=100},
                     new Ingredient{Name = "Milk", Calories=100},
                     new Ingredient{Name = "Flour", Calories=50},
                     new Ingredient{Name = "Oats", Calories=50}
                  };
                  
                  IEnumerable<IGrouping<int, Ingredient>> query =
                  from i in ingredients
                  group i by i.Calories;
                  
                  foreach (IGrouping<int, Ingredient> group in query)
                  {
                     Console.WriteLine($"Ingredients with {group.Key} calories");
                     foreach (Ingredient ingredient in group)
                     {
                        Console.WriteLine($"- { ingredient.Name}");
                     }
                  }
                  در این مثال، از نوع جنریکی به نام IGrouping استفاده شده‌است که اولین پارامتر آن کلید گروه است (در این مثال کالری مواد غذایی) و دومین پارامتر آن، نوع لیست را مشخص می‌کند که در اینجا کلاس Ingredient است. اعضای لیست تولید شده، کالری یکسانی دارند. دلیل استفاده‌ی از نوع‌ها بصورت صریح و آشکار (Explicit) خوانایی بیشتر برای تشخیص خروجی‌های تولید شده‌ی در کد‌ها می‌باشد. ولی راه بهتر، جایگزین کردن کد <<IEnumerable<IGrouping<int, Ingredient با کلمه‌ی کلیدی var  می‌باشد.
                  خروجی مثال بالا:
                   Ingredients with 500 calories
                  - Sugar
                  - Lard
                  - Butter
                  Ingredients with 100 calories
                  - Egg
                  - Milk
                  Ingredients with 50 calories
                  - Flour
                  - Oats

                  برای آشنایی بیشتر با اینترفیس IGrouping اینجارا مطالعه کنید.


                  عبارت orderby و کلمات کلیدی ascending  و descending 

                  عبارت orderby برای تولید یک توالی مرتب شده‌ی بصورت صعودی (ascending)  و یا نزولی (descending) مورد استفاده قرار می‌گیرد.
                  مثال: توالی مثال قبل را در نظر بگیرد:
                  IOrderedEnumerable<Ingredient> sortedByNameQuery =
                  from i in ingredients
                  orderby i.Name
                  select i;
                  
                  foreach (var ingredient in sortedByNameQuery)
                  {
                     Console.WriteLine(ingredient.Name);
                  }
                  خروجی مثال بالا:
                  Butter
                  Egg
                  Flour
                  Lard
                  Milk
                  Oats
                  Sugar
                  علملیات مرتب سازی بصورت پیش فرض بصورت صعودی می‌باشد (Ascending). برای تغییر این حالت می‌توانید از کلمه‌ی کلیدی descending استفاده کنید:
                  IOrderedEnumerable<Ingredient> sortedByNameQuery =
                  from i in ingredients
                  orderby i.Name descending
                  select i;
                  با اجرای مجدد برنامه، خروجی زیر را مشاهده خواهید کرد:
                   Sugar
                  Oats
                  Milk
                  Lard
                  Flour
                  Egg
                  Butter
                  عبارت orderby  را می‌توان با عبارت groupby ترکیب کرد و نتیجه‌ی حاصل از مثال اول این مطلب را بصورت مرتب شده بر اساس کالری مواد غذایی مشاهده کرد.
                  مثال:
                   Ingredient[] ingredients =
                  {
                     new Ingredient{Name = "Sugar", Calories=500},
                     new Ingredient{Name = "Lard", Calories=500},
                     new Ingredient{Name = "Butter", Calories=500},
                     new Ingredient{Name = "Egg", Calories=100},
                     new Ingredient{Name = "Milk", Calories=100},
                     new Ingredient{Name = "Flour", Calories=50},
                     new Ingredient{Name = "Oats", Calories=50}
                  };
                  
                  IEnumerable<IGrouping<int, Ingredient>> query =
                  from i in ingredients
                  group i by i.Calories
                  into calorieGroup
                  orderby calorieGroup.Key
                  select calorieGroup;
                  
                  foreach (IGrouping<int, Ingredient> group in query)
                  {
                     Console.WriteLine($"Ingredients with {group.Key} calories");
                     foreach (Ingredient ingredient in group)
                     {
                       Console.WriteLine($"- { ingredient.Name}");
                     }
                  }
                  خروجی مثال بالا :
                   Ingredients with 50 calories
                  - Flour
                  - Oats
                  Ingredients with 100 calories
                  - Egg
                  - Milk
                  Ingredients with 500 calories
                  - Sugar
                  - Lard
                  - Butter
                  همانطور که مشاهده می‌کنید بر عکس حالت قبلی که خروجی گروه‌ها بصورت نزولی بود، اینجا خروجی بر اساس کالری غذا و بصورت صعودی، نمایش داده شده است.


                  در این سری آموزشی با دو روش نوشتن پرس و جو، در LINQ آشنا شدیم:
                   1- Fluent Style (استفاده از متدهای الحاقی برای انجام عملیات‌های مختلف بر روی توالی)
                   2- Expression Style (استفاده از کلمات کلیدی (key word) برای انجام عملیات‌های مختلف بر روی توالی)

                   هر یک از روش‌های فوق مزایایی دارند که با توجه به شرایطی که با آن روبرو هستیم از آنها استفاده می‌کنیم:
                   • تعداد عملگر‌ها: در صورتی که پرس و جو نیاز به یک عملگر بر روی توالی داشته باشد می‌توان از روش اول استفاده کرد. به این خاطر که نسبت به روش دوم تعداد دستورات کمتری مورد استفاده قرار خواهد گرفت.
                  مثال :
                   var q1 = ingredients.Where(x => x.Calories > 100);
                  var q2 = from i in ingredients
                   where i.Calories >
                  100 select i;
                  در مثال فوق نتیجه‌ی اجرای دو پرس و جو، موادی است که کالری آنها بیشتر از 100 می‌باشد. همانطور که مشاهده می‌کنید روش اول مختصرتر از روش دوم است. این مورد در زمانی است که تنها یک عملیات بر روی توالی اجرا شود.
                   • پرس و جویی که قرار است نوشته شود، از نظر عملیات بر روی توالی ساده باشد: در این حالت روش استفاده شده تفاوتی نمی‌کند و بستگی به روش برگزیده و مورد علاقه‌ی برنامه نویس و یا تیم برنامه نویسی دارد. مثلا زمانیکه تنها عملگر‌های Where  و orderby بخواهند اجرا شود.
                   • پرس و جوهایی که با متغیر‌های range مختلفی رو برو هستند:در این حالت استفاده از عبارت‌های پرس و جو راحت از روش عملگر‌های پرس و جو می‌باشد.

                  لیستی از عملگرهای جستجو که در روش عبارت‌های جستجو معادل آنها مهیا شده است :
                   • GroupBy
                   • GroupJoin
                   • Join
                   • OrderBy
                   • OrderByDescending
                   • Select
                   • SelectMany
                   • ThenBy
                   • ThenByDescending
                   • Where

                  در بسیاری از پرس و جو‌ها می‌توانیم هر دو روش را با هم ترکیب کنیم که نمونه‌ای از آن، در جلسه‌ی چهارمدر زمان استفاده‌ی از متد DefaultIfEmpty استفاده شد. درمثال زیر استفاده از عملگر count، با استفاده از روش اول، به همراه عبارت پرس و جوی تولید شده‌ی با روش دوم، نمایش داده شده است:
                   int mixedQuery =
                  (from i in ingredients
                  where i.Calories > 100
                  select i).Count();

                  ‫استفاده از GZip توکار IISهای جدید و تنظیمات مرتبط با آن‌ها

                  $
                  0
                  0
                  یکی از نقش‌های IISهای جدید (از نگارش 7 به بعد) که در ویندوز سرورهای قابل نصب است، نقش Performance است و ذیل آن دو نقش فشرده سازی استاتیک و پویا قابل انتخاب است. اگر این نقش‌ها بر روی سرور نصب باشند، دیگر نیازی به استفاده از HTTP Moduleهای متداول فشرده سازی صفحات وب نیست. برای استفاده‌ی از آن تنها کافی است کمی web.config را ویرایش کرد و ... گفته شده‌است که کار می‌کند! اما پس از اعمال تنظیمات، اگر به هدرهای خروجی Response صفحه در ابزارهای web developer مرورگرها دقت کنید، خبری از encoding جدیدی به نام gzip نیست (Content-Encoding: gzip) و به نظر اعمال نمی‌شود. در ادامه بررسی خواهیم کرد که چرا اینگونه است.


                  فعال سازی GZip توکار IIS

                  تنظیمات پیش فرض فعال سازی ماژول توکار GZip وب سرورهای جدید شامل دو مرحله است:
                  الف) تنظیمات سرور جهت فعال سازی فشرده سازی
                  بر روی ویندوزهای سرور، پس از مراجعه به Administrative Tools -> Server Manager و گشودن Roles آن، ذیل قسمت Web Server که در اینجا IIS است، نیاز است نقش جدیدی به نام Performance اضافه شود و مطابق تصویر، هر دو گزینه‌ی فشرده سازی استاتیک و پویا انتخاب گردد.


                  بنابراین اولین قدم برای عیب یابی کار نکردن GZip توکار IIS، از این مرحله شروع می‌شود که آیا اصلا ماژول مربوطه نصب هست یا خیر؟

                  ب) تنظیمات برنامه جهت فعال سازی ماژول GZip
                  پس از اطمینان از نصب ماژول توکار فشرده سازی صفحات وب IIS در سمت تنظیمات سرور، اکنون باید چند سطر ذیل را به Web.Config برنامه اضافه کرد:
                  <system.webServer><httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files"><scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" /><dynamicTypes><add mimeType="text/*" enabled="true" /><add mimeType="message/*" enabled="true" /><add mimeType="application/x-javascript" enabled="true" /><add mimeType="application/javascript" enabled="true" /><add mimeType="application/json" enabled="true" /><add mimeType="application/json; charset=utf-8" enabled="true" /><add mimeType="application/atom+xml" enabled="true" /><add mimeType="application/xaml+xml" enabled="true" /><add mimeType="*/*" enabled="false" /></dynamicTypes><staticTypes><add mimeType="text/*" enabled="true" /><add mimeType="message/*" enabled="true" /><add mimeType="application/x-javascript" enabled="true" /><add mimeType="application/javascript" enabled="true" /><add mimeType="application/json" enabled="true" /><add mimeType="application/json; charset=utf-8" enabled="true" /><add mimeType="application/atom+xml" enabled="true" /><add mimeType="application/xaml+xml" enabled="true" /><add mimeType="*/*" enabled="false" /></staticTypes></httpCompression><urlCompression doStaticCompression="true" doDynamicCompression="true" /></system.webServer>
                  در اینجا تنظیمات مخصوص نحوه‌ی فعال سازی فشرده سازی توکار صفحات پویا و فایل‌های استاتیک را مشاهده می‌کنید. در این تنظیمات محل قرارگیری فایل‌های موقتی فشرده شده‌ی توسط این ماژول و همچنین mime typeهای مدنظر جهت فشرده سازی، ذکر شده‌اند. با این تنظیمات، تنها mime typeهایی که به صورت صریح ذکر شده‌اند فشرده خواهند شد و از سایر mime types صرفنظر می‌شود.
                  این تنظیماتی است که در اکثر سایت‌ها نیز یافت می‌شود. ذکر آن‌ها اجباری است و پس از اعمال، اگر برنامه را اجرا کنید ... چیزی فشرده نمی‌شود! علت اصلی را باید در تنظیماتی یافت که مخصوص سرور است و در اینجا ذکر نشده‌اند.


                  تنظیمات مخصوص آستانه‌ی فشرده سازی صفحات

                  علت اصلی عدم مشاهده‌ی هدر gzip، در Response برنامه، به frequent hit threshold تنظیم شده‌ی در IIS بر می‌گردد. مقدار آن به 2 درخواست در طی 10 ثانیه تنظیم شده‌است. یعنی اگر به صفحه‌ای در طی 10 ثانیه دو درخواست نرسد، فشرده نخواهد شد. این تنظیم را می‌توان با مراجعه‌ی به configuration editor نود اصلی سرور وب در IIS manager، ویرایش کرد:



                  برای نمونه در تصویر فوق، این آستانه به یک درخواست در طی 10 ساعت تنظیم شده‌است. این عدد سبب خواهد شد تا تمامی درخواست‌های رسیده حتما فشرده سازی شوند.
                  این تنظیم معادل یک سطر ذیل در فایل web.config است. اما چون قسمت system.webServer/serverRuntime در تنظیمات سرور قفل شده‌است، هیچ تاثیری نخواهد داشت و حتما باید در سمت سرور و توسط IIS manager اعمال شود:
                  <system.webServer><serverRuntime frequentHitThreshold="1" frequentHitTimePeriod="10:00:00" /></system.webServer>
                  برای آزاد سازی این تنظیمات نیاز است دستور ذیل بر روی سرور اجرا شود. پس از آن کاربران برنامه‌های وب می‌توانند از تنظیمات وب کانفیگ خاص خود استفاده کنند:
                   C:\Windows\System32\inetsrv\appcmd.exe unlock config /section:system.webServer/serverRuntime

                  یک نکته
                  اگر از سرورهای پس از 2008 استفاده می‌کنید، گزینه‌ی staticCompressionIgnoreHitFrequency نیز به تنظیمات serverRuntime اضافه شده‌است که با تنظیم آن به true، از این حد آستانه، برای فایل‌های استاتیک صرفنظر خواهد شد.


                  تنظیمات مخصوص اندازه‌ی فایل‌هایی که باید فشرده سازی شوند

                  تنها حد آستانه‌ی درخواست صفحات وب نیست که بر روی فشرده سازی یا عدم آن ثاثیرگذار است. در اینجا میزان CPU Usage سیستم و یا حتی اندازه‌ی Response خروجی نیز مهم هستند که نمونه‌ای از تنظیمات آن‌را در شکل ذیل مشاهده می‌کنید:


                  در اینجا با تنظیم minFileSizeForComp به 1024، اعلام شده‌است که حجم‌هایی کمتر از یک کیلوبایت، فشرده سازی نشوند (مقدار پیش فرض آن، بیش از این عدد است).
                  البته این عدد را به شکل زیر نیز می‌توان به تنظیمات httpCompression وب کانفیگ اضافه کرد:
                  <httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files" minFileSizeForComp="1024">

                  پس از اعمال این تنظیمات نیاز است یکبار IIS را نیز ری استارت کرد.


                  نتیجه گیری

                  اگر پس از فعال سازی GZip وب سرور، خروجی برنامه فشرده سازی نشد (Content-Encoding: gzip)، علت اینجا است که هنوز 2 درخواست مورد نیاز، در طی 10 ثانیه به سمت سرور ارسال نشده‌اند و تنظیمات پیش فرض این ماژول، جهت حداقل مصرف CPU و فشار بر روی سرور است.

                  ‫فیلترها در MVC

                  $
                  0
                  0
                  هنگامی که درخواستی به سرور ارسال می‌گردد، به کنترلر و اکشن مربوطه جهت پاسخگویی هدایت می‌شود. خب شاید مواقعی شما نیاز داشته باشید قبل یا بعد از اجرای اکشن متدی، کدی اجرا گردد. به‌همین جهت در MVC قابلیتی بنام Filter ارائه گردید.
                  فیلتر، یک کلاس سفارشی است که شما می‌توانید منطق برنامه را جهت اجرا، قبل یا بعد از اجرای یک اکشن متد، در آن پیاده سازی نمایید. فیلترها می‌توانند به یک اکشن متد و یا کنترلری منتسب شوند که در ادامه با این روشها آشنا خواهید شد.

                  در لیست زیر انواع فیلترها و اینترفیس‌هایی که باید توسط کلاس سفارشی شما پیاده سازی شوند، معرفی شده است.

                   نوعتوضیح
                   فیلتر توکار
                   اینترفیس
                   Authorization
                  انجام عملیات احراز هویت و سطح دسترسی، قبل از اجرای کد اکشن متد  
                   [Authorize] و [RequireHttps]  
                   IAuthorizationFilter 
                   Action
                  اجرای کدهایی قبل از اجرای کدهای اکشن متد 
                    IActionFilter 
                   Result
                  اجرای کدهایی قبل یا بعد از تولید ویو (View result) 
                   [OutputCache]   IResultFilter 
                  Exception
                  اجرای کدهایی در صورت وجود استثنای مدیریت نشده 
                  [HandleError] 
                  IExceptionFilter
                  مثال: هنگامی که خطایی در حین اجرای اکشن متدی رخ می‌دهد، فیلتر توکار MVC بنام HandleErrorاجرا می‌شود. این فیلتر توکار فایل Error.cshtml را که در فولدر Shared قرار دارد، رندر می‌کند و نمایش می‌دهد.
                  در تکه کد زیر نحوه‌ی استفاده از این فیلتر را مشاهده می‌کنید:
                  [HandleError]
                  public class HomeController : Controller
                  {
                      public ActionResult Index()
                      {
                          //throw exception for demo
                          throw new Exception("This is unhandled exception");
                          return View();
                      }
                  
                      public ActionResult About()
                      {
                          return View();
                      }
                  
                      public ActionResult Contact()
                      {
                          return View();
                      }        
                  }

                  نکته: فیلترهای اعمال شده‌ی به یک کنترلر، به تمام اکشن متدهای آن نیز اعمال می‌گردند. 

                  در کد بالا خصیصه‌ی HandleError به HomeController اعمال شده است. بنابراین در صورت بروز خطایی در هر کدام از اکشن‌ها، صفحه‌ی Error.cshtml نمایش داده خواهد شد و در تظر داشته باشید که خطاها توسط try-catch هندل نشده‌اند.
                  باید جهت عملکرد صحیح فیلتر توکار HandleErrorAttribute، مقدار customErrors در قسمت System.web فایل web.config مساوی on باشد.
                  <customErrors mode="On" />


                  مهیا کنندگان فیلترها

                  بصورت پیش فرض MVC از سه طریق زیر فیلترها را جهت استفاده‌ی در برنامه فراهم می‌کند:

                  1. خصیصه‌ی GlobalFilters.Filters برای فیلترهای سراسری
                  2. کلاس FilterAttributeFilterProvider برای فیلترهای خصیصه‌ای
                  3. کلاس ControllerInstanceFilterProvider جهت افزودن کنترلر به یک وهله از FilterProviderCollection

                  در ادامه با نحوه‌ی ایجاد یک فیلتر، بوسیله‌ی هر یک از روش‌های بالا، با ذکر مثالی بیشتر آشنا خواهید شد.

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

                  همانطور که در ابتدا اشاره شد، در MVC چهار نوع فیلتر معرفی شده است که امکان استفاده‌ی از آنها به‌صورت همزمان در سطح کنترلر و یا اکشن متد وجود دارد. اما ترتیب  اجرای آنها متفاوت و به ترتیب زیر است:

                  1. فیلترهای Authorization
                  2. فیلترهای Action
                  3. فیلترهای Result یا Response
                  4. فیلترهای Exception

                  فیلترها براساس ترتیب اشاره شده‌ی در بالا اجرا خواهند شد. در صورتیکه چند فیلتر از یک نوع استفاده شود، جهت تقدم و تاخر در اجرا، از خاصیت Order استفاده خواهد شد. بعنوان مثال در کد زیر بدلیل خاصیت Order=1 ابتدا AuthorizationFilterB  و سپس AuthorizationFilterA اجرا می‌شود.

                  [AuthorizationFilterA(Order=2)]
                  [AuthorizationFilterB(Order=1)]
                  public ActionResult Index()
                  {          
                      return View();
                  }
                  علاوه بر خاصیت Order، مقدار Scopeنیز سطح سومی از ترتیب اجرای فیلترها می‌باشد. مقادیر Scope بشرح زیر است:
                  public enum FilterScope
                  {
                      First = 0,
                      Global = 10,
                      Controller = 20,
                      Action = 30,
                      Last = 100,
                  }
                  این خصیصه‌ی فیلترها در محل بکار گیری آنها مقدار دهی می‌شود. در صورتیکه فیلتری بصورت سراسری رجیستر شود، Scope آن برابر 10 و در سطح کنترلر، برابر 20 خواهد بود و الی آخر.

                  نکته: مقدار Scope فیلترهای Authorization برابر 0 و فیلترهای Exception برابر 100 می‌باشد.

                  ایجاد فیلتر سفارشی

                  روش اول:پیاده سازی اینترفیس یکی از انواع فیلترها و ارث بری از کلاس FilterAttribute

                  در این روش متدهایی که باید پیاده سازی شوند متفاوت خواهد بود. به همین جهت متدهای هر نوع بشرح زیر معرفی می‌شود:

                  • IAuthorizationFilter
                  // Called when authorization is required
                  void OnAuthorization(AuthorizationContext filterContext)
                  • IActionFilter
                  // Called after the action method executes
                  void OnActionExecuted(ActionExecutedContext filterContext)
                  
                  // Called before an action method executes
                  void OnActionExecuting(ActionExecutingContext filterContext)
                  • IResultFilter
                  // Called after an action result executes
                  void OnResultExecuted(ResultExecutedContext filterContext)
                  
                  // Called before an action result executes
                  void OnResultExecuting(ResultExecutedContext filterContext)
                  • IException
                  // Called when an exception occurs
                  void OnException(ExceptionContext filterContext)

                  یادآوری: همانطور که در ابتدای مقاله اشاره شد، فیلترها قبل یا بعد از اجرای اکشن متدها فراخوانی خواهند شد. بنابراین به کامنت بالای متد فیلترها دقت داشته باشید.

                  مثال: پیاده سازی اینترفیس IExceptionFilter و ارث بری از کلاس FilterAttribute جهت تهیه‌ی فیلتری سفارشی از نوع Exception

                  class CustomErrorHandler : FilterAttribute, IExceptionFilter
                  {
                      public override void IExceptionFilter.OnException(ExceptionContext filterContext)
                      {
                          Log(filterContext.Exception);
                  
                          base.OnException(filterContext);
                      }
                  
                      private void Log(Exception exception)
                      {
                          //log exception here..
                      }
                  }

                  روش دوم:
                  ارث بری از ActionFilterAttribute
                  کلاس abstract فوق دارای چهار متد زیر جهت تحریف است. همانطور که مشاهده می‌کنید این کلاس علاوه بر دو متد OnActionExecuted و OnActionExecuting دارای دو متد دیگر OnResultExecuting و OnResultExecuted که به‌ترتیب قبل و بعد خروجی (Result) اکشن متد اجرا می‌شوند، نیز می‌باشد. این نوع فیلترها عموما جنبه‌ی استفاده عمومی داشته و می‌توان از آنها جهت logging ،caching و یا authorization استفاده کرد.
                  // Called by MVC after the action method executes
                  void OnActionExecuted(ActionExecutedContext filterContext)
                  
                  // Called by MVC before the action method executes
                  void OnActionExecuted(ActionExecutedContext filterContext)
                  
                  // Called by MVC after the action result executes
                  void OnResultExecuted(ResultExecutedContext filterContext)
                  
                  // Called by MVC before the action result executes
                  void OnResultExecuting(ResultExecutingContext filterContext)

                  مثال: کلاس LogAttribute که از کلاس ActionFilterAttribute ارث بری کرده است، عملیات قبل و بعد از اجرای اکشن متد را لاگ می‌کند.
                  public class LogAttribute : ActionFilterAttribute
                  {
                      public override void OnActionExecuted(ActionExecutedContext filterContext)
                      {
                          Log("OnActionExecuted", filterContext.RouteData); 
                      }
                  
                      public override void OnActionExecuting(ActionExecutingContext filterContext)
                      {
                          Log("OnActionExecuting", filterContext.RouteData);      
                      }
                  
                      public override void OnResultExecuted(ResultExecutedContext filterContext)
                      {
                          Log("OnResultExecuted", filterContext.RouteData);      
                      }
                  
                      public override void OnResultExecuting(ResultExecutingContext filterContext)
                      {
                          Log("OnResultExecuting ", filterContext.RouteData);      
                      }
                  
                      private void Log(string methodName, RouteData routeData)
                      {
                          var controllerName = routeData.Values["controller"];
                          var actionName = routeData.Values["action"];
                          var message = String.Format("{0}- controller:{1} action:{2}", methodName, 
                                                                                      controllerName, 
                                                                                      actionName);
                          Debug.WriteLine(message);
                      }
                  }

                  روش سوم:
                  پیاده سازی داخل کنترلر
                  کلاس Controller  می‌تواند هر یک از اینترفیس‌های فیلترها را پیاده سازی نماید. به عبارت دیگر در هر کلاس کنترلر می‌توانید متدهای زیر را تحریف نمایید.
                  • OnAuthorization ^
                  • OnException ^
                  • OnActionExecuting ^
                  • OnActionExecuted ^
                  • OnResultExecuting ^
                  • OnResultExecuted ^


                  روش چهارم:ارث بری از کلاس فیلترهای توکار و مهیای در MVC و تحریف متدهای آن 
                  در کد زیر با تحریف و سفارشی سازی متد OnException مخصوص فیلتر توکار HandleError، قابلیت‌های آن افزایش یافته است:

                  class CustomErrorHandler : HandleErrorAttribute
                  {
                      public override void OnException(ExceptionContext filterContext)
                      {
                          Log(filterContext.Exception);
                  
                          base.OnException(filterContext);
                      }
                  
                      private void Log(Exception exception)
                      {
                          //log exception here..
                      }
                  }


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

                  • سراسری:

                  درصورتی که قصد داشته باشید فیلتری بصورت سراسری و در کل برنامه فعال گردد باید آن را در رویداد Application_Start فایل Global.asax.cs بوسیله‌ی متد RegisterGlobalFilters کلاس FiterConfig رجیستر نمایید. بعد از آن فیلتر به کلیه‌ی کنترلرها و اکشن متدها اعمال می‌گردد.

                  public class MvcApplication : System.Web.HttpApplication
                  {
                      protected void Application_Start()
                      {
                            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                      }
                  }
                  
                  // FilterConfig.cs located in App_Start folder 
                  public class FilterConfig
                  {
                      public static void RegisterGlobalFilters(GlobalFilterCollection filters)
                      {
                          filters.Add(new HandleErrorAttribute());
                  
                          // add your new custom filters
                          filters.Add(new LogAttribute()); 
                          filters.Add(new CustomErrorHandler());
                       }
                  }

                  در کد بالا فیلتر توکار HandleError و البته فیلترهای سفارشی دیگری نیز به صورت سراسری به تمام اکشن متدهای کنترلرها اعمال گردیده است.

                  • کنترلر: در صورتی که فقط بخواهید یک فیلتر به کل اکشن‌های یک کنترلر اعمال گردد. همانند آنچه که در مثال ابتدایی بدان اشاره شد.
                  [HandleError]
                  public class HomeController : Controller
                    • اکشن متد: اعمال یک فیلتر به یک اکشن متد خاص کنترلر. در کد زیر فیلتر HandleError فقط به اکشن متد Index کنترلر Home اعمال خواهد شد.
                    public class HomeController : Controller
                    {
                        [HandleError]
                        public ActionResult Index()
                        {
                            return View();
                        }
                    }

                      ‫بررسی الگوهای ایندکس‌های Non-Clustered در SQL Server

                      $
                      0
                      0

                      قصد داریم الگوهای مختلف ایندکس گذاری و استراتژی Non-Clustered Indexes را در Sql Server، بررسی کنیم.

                      مزایایایجاد ایندکس‌های صحیح بر اساس نیازهای واقعی کاری:

                      • سریعتر شدن اجرای کوئری‌های جستجو در تعداد رکوردهای بالا
                      • مرتب سازی سریعتر نتایج (sorting)
                      • کوئری‌هایی که بر اساس عبارت GROUP BY ایجاد شده‌اند، سریعتر اجرا خواهند شد 

                      Non-Clustered Indexes 

                      تقریبا در تمام دیتابیس‌ها به راه‌های دیگری برای دسترسی به داده‌های جداول نیاز خواهد شد که لزوما این داده‌ها براساس ترتیب هنگام ذخیره سازی، مرتب نیستند. در چنین شرایطی ایندکس‌های غیر خوشه‌ای بر سر کار خواهند آمد.
                      در ادامه الگوهای مختلف ایندکس گذاری مرتبط با ایندکس‌های غیر خوشه‌ای را بررسی کرده و برای هر کدام از آنها مثالی را بررسی خواهیم کرد. خواهیم دید هر ایندکسی که از جانب ما ایجاد می‌شود، نمیتوان مطمئن شد که توسط Sql Server  مورد استفاده قرار می‌گیرد!
                      این الگو‌ها در تعیین زمان و مکان ساخت ایندکس‌های غیر خوشه‌ای، به ما کمک خواهند کرد که به شرح زیر می‌باشند:
                      • Search Columns
                      • Index Intersection
                      • Multiple Columns
                      • Covering Indexes
                      • Included Columns
                      • Filterd Indexes
                      • Foreign Keys

                      Search Columns

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

                      USE AdventureWorks2012;
                      
                      GO
                      CREATE TABLE dbo.Contacts (
                          ContactID         INT           IDENTITY (1, 1),
                          FirstName         NVARCHAR (50),
                          LastName          NVARCHAR (50),
                          IsActive          BIT          ,
                          EmailAddress      NVARCHAR (50),
                          CertificationDate DATETIME     ,
                          FillerData        CHAR (1000)  ,
                          CONSTRAINT PK_Contacts PRIMARY KEY CLUSTERED (ContactID)
                      );
                      
                      INSERT INTO dbo.Contacts (FirstName, LastName, IsActive, EmailAddress, CertificationDate)
                      SELECT pp.FirstName,
                             pp.LastName,
                             IIF (pp.BusinessEntityID / 10 = 1, 1, 0),
                             pea.EmailAddress,
                             IIF (pp.BusinessEntityID / 10 = 1, pp.ModifiedDate, NULL)
                      FROM   Person.Person AS pp
                             INNER JOIN
                             Person.EmailAddress AS pea
                             ON pp.BusinessEntityID = pea.BusinessEntityID;

                      ابتدا قصد داریم از جدول Contacts بدون استفاده از هیچ ایندکس غیر خوشه‌ای، کوئری بگیریم. نتیجه‌های نشان داده شده‌ی در کوئری حاصل از کد T-SQL زیر به شرح زیر است:

                      SET STATISTICS IO ON;
                      
                      SELECT ContactID,
                             FirstName
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine';
                      
                      SET STATISTICS IO OFF;

                      22 رکورد را واکشی کرده است؛ ولی با خواندن 2866 page ! که این تعداد، تمام صفحات موجود در جدول می‌باشد. بنابراین واکشی این تعداد رکورد از کل رکورد‌های موجود در جدول (19000) نیاز به چک کردن همه‌ی صفحات را خواهد داشت که واقعا روش بهینه‌ای نمی‌باشد. 

                      همانطور که در تصویر پلن کوئری بالا هم مشخص است، کل ایندکس خوشه دار ما Scan شده است که هزینه‌ی بالایی خواهد داشت.

                      حال با کد T-SQL زیر یک ایندکس غیر خوشه دار را بر روی فیلد FirstName ایجاد خواهیم کرد:

                      CREATE INDEX IX_Contacts_FirstName ON dbo.Contacts(FirstName);

                      اگر دوباره کوئری قبلی را اجرا کنیم، به نتایج خیلی بهتری خواهیم رسید و تعداد صفحات خوانده شده به 2 کاهش یافته است! 

                      Sql Server این بار به جای اسکن دوباره‌ی ایندکس خوشه دار، با استفاده از Index Seekو بهره بردن از ایندکس ایجاد شده‌ی توسط ما، یک پلن قابل قبول را برای ما ارائه داده است.

                      Index Intersection

                      در برخی از سناریوها لازم است یکسری ستون دیگر هم علاوه بر ستونی که ایندکس را بر روی آن تعریف کرده‌ایم، در بخش شرط یا خروجی select استفاده شوند. یکی از راه‌حل‌ها، ایجاد یک ایندکس غیر خوشه‌ای که سایر ستون‌ها را نیز Include می‌کند، می‌باشد. با وجود ایندکس‌هایی که هر کدام از آنها می‌توانند برای ادا کردن بخشی از شروط، نقش ایفا کنند، Sql Server  هم با به کار بردن آنها می‌تواند رکوردهایی که در فصل مشترک حاصل از جسجتوی این ایندکس‌ها بدست آمده را به عنوان خروجی کوئری ما بازگشت دهد. این عملیات Index Intersection نام دارد. به مثال زیر توجه کنید:

                      SET STATISTICS IO ON;
                      
                      SELECT ContactID,
                             FirstName,
                             LastName
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine'
                             AND LastName = 'Cox';
                      
                      SET STATISTICS IO OFF;

                      در کوئری بالا علاوه بر FirstName که یک ایندکس غیر خوشه دار را بر روی آن ایجاد کرده‌ایم، فیلد LastName را هم در بخش Select و شرط، مطرح کرده‌ایم. حالا اگر آن را اجرا کنیم، به آمار و پلن زیر دست خواهیم یافت:

                      بله تعداد Page‌های خوانده شده این بار به 68 افزایش یافته است که نسبت به حالت بدون LastName که 2 Page خوانده شده بود، زیاد است. همانطور که در پلن زیر مشخص است، به دلیل ایندکسی که برروی FirstName ایجاد کرده‌ایم، نمی‌تواند تمام داده‌های مورد نیاز کوئری را مهیا کند. عملیات Key Lookup و nested loop هم این بار اضافه شده‌اند. Sql Server همچنان استفاده از ایندکس موجود را در کنار Key Lookup از ایندکس خوشه دار، ارزان‌تر از اسکن ایندکس خوشه دار، تشخیص داده است.

                      مشکل زمانی گریبان گیر ما خواهد شد که به ازای هر مطابقتی در ایندکس غیر خوشه دار، یک بار به ایندکس خوشه دار برای بررسی شرط بعدی و واکشی دیتا، رجوع خواهد شد. باید دقت کرد که Key Lookupهمیشه به عنوان مشکل مطرح نمی‌شود. ولی باعث افزایش غیرضروری هزینه‌های CPUو I/Oبرای کوئری خواهد شد.

                      برای استفاده از الگوی Index Intersection، یک ایندکس غیر خوشه دار برروی ستون LastNameایجاد خواهیم کرد:

                      CREATE INDEX IX_Contacts_LastName ON dbo.Contacts(LastName);

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

                      بله تعداد Page‌های خوانده شده به 5 کاهش یافته و این بار به جای استفاده از Key Lookup، از دو index seek استفاده کرده است که هزینه‌ای کمتر را نسبت به حالت قبل خواهد داشت. به دلیل اینکه این دو ایندکس تمام دیتای لازم را می‌توانند مهیا کنند، دیگر نیازی به رجوع به ایندکس خوشه دار نخواهد بود. تصویر زیر در درک پلن بالا و این الگو می‌تواند مفید باشد:

                      Multiple Columns

                      در دو الگوی قبل، بیشتر به ایجاد ایندکس‌، بر روی یک ستون متمرکز شده بودیم. اگر تعدادی از ستون‌ها در بخش شروط مربوط به کوئری مطرح شوند، بهتر است آنها را در قالب یک ایندکس نگهداری کنیم. برای نشان دادن تأثیر این مورد،  یک ایندکس غیر خوشه دار را بر روی دو ستون ایجاد می‌کنیم: 

                      CREATE INDEX IX_Contacts_FirstNameLastName
                          ON dbo.Contacts(FirstName, LastName);
                      
                      SET STATISTICS IO ON;
                      
                      SELECT ContactID,
                             FirstName,
                             LastName
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine'
                             AND LastName = 'Cox';
                      
                      SET STATISTICS IO OFF;

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

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

                      الگوی Multiple Columns هم به مانند الگوی Search Columns باید هنگام ایندکس گذاری دیتابیس در نظر گرفته شود و از اهمیت بالایی برخوردار است. باید توجه داشت اگر فیلدهایی که در قسمت شرطی کوئری مطرح می‌شوند، متغییر باشد، استفاده از الگوی Index Intersection مفید خواهد. ولی برای مواقعی که نیاز است یکسری فیلد به صورت یکجا در بخش شرطی کوئری مطرح شوند، الگوی Multiple Columns کارآیی بهتری خواهد داشت. از این دو الگوی مطرح شده که در تناقض باهم قرار دارند، می‌توان به نحوی استفاده برد تا هزینه‌ی کلی را کاهش داد.

                      Covering Index

                      الگوی بعدی، ایندکس پوشش دهنده نام گرفته است. همانند نامی که دارد، هدف آن نگهداری یکسری ستون در ستون‌های ایندکس تولیدی که اتفاقا این ستون‌ها در قسمت شرطی کوئری قرار ندارند، ولی قرار است به عنوان خروجی Select برگردانده شوند، می‌باشد.
                      این الگو به عنوان یک روش استاندارد ایندکس گذاری در Sql Server مطرح بوده است. البته در ادامه و با بروز شدن روش‌هایی که می‌توان ایندکس‌ها را ایجاد کرد، این الگو نسبت به قبل کمتر مفید است! از آن جهت که یک روش شناخته شده می‌باشد، در این قسمت این مورد را هم مطرح کردیم. به مثال زیر توجه کنید:

                      SET STATISTICS IO ON;
                      
                      SELECT ContactID,
                             FirstName,
                             LastName,
                             IsActive
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine'
                             AND LastName = 'Cox';
                      
                      SET STATISTICS IO OFF;

                      در کوئری بالا این بار قصد داریم خصوصیت IsActive را که در ایندکس IX_Contacts_FirstNameLastName نگهداری نمی‌شود و همچنین در قسمت شرطی هم مطرح نشده و نیازی به آن نبوده، هم واکشی کنیم. با توجه به نتایج بدست آمده که در آمار و پلن زیر مشخص است، باز هم تعداد Page‌های خوانده شده به 5 افزایش یافته و بار دیگر، Key Lookup و Nested Loop را در کنار یک Index Seek، برروی ایندکسی که با الگوی Multiple Columns ایجاد کرده‌ایم، خواهیم داشت.


                      الگوی index covering پیشنهاد می‌کند ستونی را هم که در قسمت شرطی مطرح نمی‌شود، به عنوان ستونی اصلی در ایندکس، نگهداری کنیم؛ به شکل زیر:

                      CREATE INDEX IX_Contacts_FirstNameLastNameIsActive ON dbo.Contacts(FirstName, LastName,IsActive)

                      ایندکس غیر خوشه دار بالا، 3 فیلدی را که قرار است در بخش شرطی مطرح شوند، یا به عنوان خروجی Select برگردانده شوند، در بر می‌گیرد. سپس کوئری قبلی را دوباره اجرا میکنیم. به نتایج زیر خواهیم رسید:

                      باز هم هزینه‌ی Key Lookup حذف شده و این بار از ایندکس جدید ما استفاده شده و تعداد Page‌های خوانده شده هم به 2 کاهش یافته است.
                      این الگو در بیشتر سناریو‌ها کاملا مفید بوده و پتانسیل افزایش کارآیی را در بیشتر سناریو‌ها دارد. اما در سال‌های اخیر از زمانیکه امکانات جدیدی در Sql Server 2005 به بعد ایجاد شد، از استفاده‌ی آن کاسته شده است. با وجود این امکانات جدید که در الگوی بعد به آن خواهیم پرداخت، می‌توان ستون‌های اضافی را در ایندکس‌ها، Include کنیم و نیازی نیست که جزء ستون‌های اصلی ایندکس باشند. 

                      Included Columns

                      الگوی Included Columns درواقعا پسر عموی الگوی Covering Index می‌باشد. در این الگو از عبارت INCLUDE در ایجاد یا تغییر ایندکس استفاده می‌شود و از این طریق امکان این را مهیا می‌کند تا یکسری ستون که جز ستون‌های اصلی ایندکس نیستند هم در ایندکس غیر خوشه دار ما افزوده شوند و حتی در قسمت شرطی هم مطرح شوند. این عمل خیلی شبیه به نگهداری دیتا‌های غیر کلیدی در یک ایندکس خوشه دار می‌باشد و این همان تفاوت اصلی بین دو الگو مطرح شده است.

                      اگر کوئری زیر را اجرا کنیم:

                      SET STATISTICS IO ON;
                      
                      SELECT ContactID,
                             FirstName,
                             LastName,
                             EmailAddress
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine';
                      
                      SET STATISTICS IO OFF;

                      68 Page خوانده شده خواهیم داشت که حاصل یک Index Seek بر روی ایندکس IX_Contacts_FirstName می‌باشد و برای واکشی بقیه ستون‌ها هم یک Key Lookup بر روی ایندکس خوشه دار در پلن مشخص خواهد بود.

                      علاوه بر ایندکس‌های ایجاد شده‌ی در مراحل قبل، حال یک ایندکس غیر خوشه‌ای را با استفاده از الگوی INC ایجاد می‌کنیم:

                      CREATE INDEX IX_Contacts_FirstNameINC ON dbo.Contacts(FirstName)
                      INCLUDE (LastName, IsActive, EmailAddress);

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

                      این بار از ایندکس جدید ایجاد شده استفاده شده و تعداد Page‌های خوانده شده، به 3 کاهش یافته است. با توجه به انعطاف پذیری این الگو می‌توان از اندک افزایشی که در تعداد Page‌های خوانده شده نسبت به الگوی ایندکس پوشش دهنده وجود دارد، چشم پوشی کرد.
                      در مثال‌های قبل چندین ایندکس بر روی جدول Contacts ایجاد کرده‌ایم که 4 مورد از آنها به صورت اختصاصی بر روی فیلد FirstName بوده است. باید توجه کرد این ایندکس‌ها نیاز به فضا و نگهداری در مواقع ویرایش رکورد‌های جدول خواهند داشت. لذا این هزینه‌ها اثر منفی برروی تمام عملیاتی خواهند داشت که روی جدول انجام می‌شود.
                      الگوی INC می‌تواند این مشکل را برطرف کند. برای مثال با استفاده از آن می‌توان ایندکس‌های تولید شده‌ی در مراحل قبل را بر روی FirstName، توسط یک ایندکس نیز پوشش داد. لذا ایندکس‌های قبلی را حذف کرده و با یکسری کوئری، مشخص خواهیم کرد که گفته‌ی ما صحت دارد:

                      IF EXISTS(SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.Contacts')
                      AND name = 'IX_Contacts_FirstNameLastName')
                      DROP INDEX IX_Contacts_FirstNameLastName ON dbo.Contacts
                      GO
                      IF EXISTS(SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.Contacts')
                      AND name = 'IX_Contacts_FirstNameLastNameIsActive')
                      DROP INDEX IX_Contacts_FirstNameLastNameIsActive ON dbo.Contacts
                      GO
                      IF EXISTS(SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.Contacts')
                      AND name = 'IX_Contacts_FirstName')
                      DROP INDEX IX_Contacts_FirstName ON dbo.Contacts
                      GO

                      با کدهای بالا ایندکس‌هایی را که بر روی FirstName ایجاد شده بودند، حذف کرده و این بار تمام کوئری‌های مطرح شده‌ی در مراحل قبل را یکبار دیگر اجرا می‌کنیم:

                      SET STATISTICS IO ON;
                      
                      SELECT ContactID,
                             FirstName
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine';
                      
                      SELECT ContactID,
                             FirstName,
                             LastName
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine'
                             AND LastName = 'Cox';
                      
                      SELECT ContactID,
                             FirstName,
                             LastName,
                             IsActive
                      FROM   dbo.Contacts
                      WHERE  FirstName = 'Catherine'
                             AND LastName = 'Cox';
                      
                      SET STATISTICS IO OFF;

                      دو نکته‌ای که باید به آنها توجه کرد:

                      1. کوئری‌ها بالا در مقایسه با الگوهای قبلی به چه شکلی اجرا خواهند شد؟
                      2. توجه کردن به تعداد Page‌های خوانده شده
                      در جواب مورد اول، Sql Server از عملیات Index Seek برای فیلترینگ برروی FirstName استفاده کرده و اگر ستون دیگری هم در بخش شرطی کوئری آورده شده، باز هم از این نوع عملیات استفاده شده است. به عنوان مثلا در دو کوئری بعد، LastName هم در بخش شرطی مطرح شده است‌. دلیل این کار که باز هم از Index Seek استفاده می‌شود این است که بعد از اعمال فیلترینگ بر روی FirstName، حالا یکسری رکورد در اختیار داریم که اتفاقا به LastName آنها هم دسترسی هست و فقط رکورد‌ها براساس آن مرتب نشده اند و نیازی نیست به ایندکس خوشه دار دسترسی داشته باشیم. لذا می‌توان همینجا بر روی این فیلد هم فیلترینگ را اعمال کرد. به پلن زیر توجه کنید:

                      در جواب مورد دوم، با اینکه حدود 50% افزایش در تعداد Page‌های خوانده شده نسبت به حالتی که به صورت جدا از هم برای هر کوئری خاص یک ایندکس در نظر گرفته بودیم، داشته‌ایم ولی این تغییر کارآیی نمی‌تواند ساخت 4 ایندکس را به جای 1 ایندکس که تمام آنها را پوشش می‌دهد، توجیه کند! در حالیکه ما به کارآیی مورد نظر خود دست یافته‌ایم.

                      در نتیجه الگوی INC هنگام ساخت ایندکس‌های غیر خوشه دار خیلی مهم است و باید به آن توجه زیادی کرد. بیشتر در مواقعی‌که نیاز است عملیات Lookup را حذف کنید و سرعت خواندن و کارآیی اجرای کوئری را افزایش دهید، این الگو مناسب خواهد بود. همچنین با کاهش تعداد ایندکس‌ها برای پوشش دادن ایندکس‌های لازم برای کوئری‌ها مشابه، باید توجه کرد که باز هم نسبت به حالتی که هیچ ایندکس غیر خوشه داری ایجاد نشده، کارآیی افزایش می‌یابد.

                      Filtered Indexes

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

                      بله همانطور که از نام این الگو نیز مشخص است، هدف آن کاهش تعداد رکوردهایی است که در ایندکس نگهداری می‌شوند. به دو کوئری زیر توجه کنید:
                      SET STATISTICS IO ON;
                      
                      SELECT   ContactID,
                               FirstName,
                               LastName,
                               CertificationDate
                      FROM     dbo.Contacts
                      WHERE    CertificationDate IS NOT NULL
                      ORDER BY CertificationDate;
                      
                      SELECT   ContactID,
                               FirstName,
                               LastName,
                               CertificationDate
                      FROM     dbo.Contacts
                      WHERE    CertificationDate BETWEEN '20050101' AND '20050201'
                      ORDER BY CertificationDate;
                      
                      SET STATISTICS IO OFF;
                      در کوئری اول به دنبال رکورد هایی هستیم که CertificationDate آنها نال می‌باشد و در دومی هم به دنبال آنهایی هستیم که در یک بازه‌ی زمانی قرار دارند. از آمار و پلن زیر مشخص است که چون هیچ ایندکس غیر خوشه داری بر روی CertificationDate ایجاد نشده‌است، از Index Scan برروی ایندکس خوشه دار استفاده شده است که حاصل آن خوانده شدن 2866 عدد Page می‌باشد!

                      زمانیکه مقدار آن نال باشد، استفاده نخواهد شد. آیا عقل سلیم قبول می‌کند که این مقادیر نال را در ایندکس نگهداری و رکوردهایی با مقادیر نال داشته باشیم؟ برای پیاده سازی این الگو باید از عبارت Where به هنگام ساخت ایندکس‌های غیر خوشه‌ای استفاده کنیم.
                       توجه کنید که امکان استفاده از مقادیر متغیر در بخش Where، وجود ندارد.
                      نکته‌ی بعدی این است که نمی‌توان مقایسه‌های پیچیده را در این مورد استفاده کرد. برای مثال استفاده از LIKE و BETWEEN امکان پذیر نیست.

                      این بار با استفاده از الگوی Filtered Indexes یک ایندکس غیر خوشه‌ای را بر روی ستون CertificationDate ایجاد می‌کنیم:

                      CREATE INDEX IX_Contacts_CertificationDate ON dbo.Contacts(CertificationDate)
                      INCLUDE (FirstName, LastName)
                      WHERE CertificationDate IS NOT NULL;

                      حال دوباره دو کوئری قبلی را اجرا می‌کنیم. آمار و پلن زیر نشان می‌دهند که این بار فقط 2 عدد Page خوانده شده است و عملیات به Index Seek بر روی ایندکس جدید تغییر کرده است.


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

                      • کم شدن تعداد رکورد‌های ایندکس‌ها موجب کاهش تعداد Page‌های مورد نیاز برای ذخیره سازی آنها و در نتیجه کاهش حجم مورد نیاز برای ذخیره سازی خواهد شد.
                      • با توجه به مورد اول، اگر تعداد Page‌های برای نگهداری ایندکس کم باشند، لذا فرصت Fragmentation برای ایندکس کم خواهد بود و در نتیجه، هزینه و تلاش کمی برای نگهداری آن لازم است.
                      • زمانیکه تعداد مقادیر نگهداری شده‌ی در ایندکس محدود هستند، تعداد Page هایی که برای پیمایش نیاز است، کم خواهند بود و اینجاست که حتی Index Scan هم بروری آن خیلی بهینه‌تر از Index Scan بر روی ایندکس خوشه دار می‌باشد.
                      شرایطی که می‌توان و باید از Filtered Indexes استفاده کرد:
                      • اگر لازم است بر روی یک ستون که به‌صورت نال‌پذیر است، ایندکس ایجاد کنید(دلایل آن پیش‌تر گفته شد).
                      • اگر لازم است برروی Sparse Column، یک ایندکس یکتا ایجاد کنید.
                      • مورد بعدی همان بحث کاهش تعداد رکوردهایی می‌باشد که در ایندکس ذخیره می‌شوند.
                      Foreign Keys
                      آخرین الگویی که به آن می‌پردازیم مربوط می‌شود به کلید خارجی. این مورد تنها الگویی است که به طور مستقیم به اشیاء موجود در طراحی دیتابیس مربوط می‌باشد. کلید‌های خارجی گاهی مواقع می‌توانند باعث بروز مشکلی کارآیی شوند، بدون آنکه کسی متوجه این دخالت در کارآیی باشد.
                      از آنجائیکه کلید خارجی یک قید را بر روی مقادیر مجاز برای یک ستون مهیا می‌کند، لذا یک بررسی برای زمانیکه مقادیر نیاز به اعتبارسنجی دارند، وجود خواهد داشت. این اعتبارسنجی با توجه به شکل زیر دو نوع می‌باشد که به شرح زیر است:

                      1. اعتبارسنجی بر روی جدول ParentTable  
                      2. اعتبارسنجی بر روی جدول ChildTable 

                      در مورد نوع اول، هر وقت که رکوردهای جدول ChildTable تغییر کند، در این صورت مقدار ParentID موجود جدول ChildTable با یک جستجو در جدول ParentTable اعتبارسنجی خواهد شد. از آنجایی که این کلید خارجی در جدول ParentTable یک کلید اصلی بوده، یک ایندکس خوشه دار بر روی آن ایجاد شده است و تأثیری در کاهش کارآیی نخواهد داشت.
                      در مورد نوع دوم، هروقت تغییراتی بر روی  ParentID موجود در جدول ParentTable داشته باشیم، نیاز است اعتبار سنجی بر روی جدول ChildTable انجام شود. برای مثال با حذف یک رکورد در جدول پدر، لازم است که جدول فرزند بررسی کند که آیا این ParentID در رکورد‌ها موجود استفاده شده است یا خیر؟ در این نوع از اعتبارسنجی، الگوی Foreign Key خود را نشان می‌دهد.

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

                      USE AdventureWorks2012;
                      
                      
                      GO
                      CREATE TABLE dbo.Customer (
                          CustomerID  INT        ,
                          FillterData CHAR (1000),
                          CONSTRAINT PK_Customer_CustomerID PRIMARY KEY CLUSTERED (CustomerID)
                      );
                      
                      CREATE TABLE dbo.SalesOrderHeader (
                          SalesOrderID INT        ,
                          OrderDate    DATETIME   ,
                          DueDate      DATETIME   ,
                          CustomerID   INT        ,
                          FillterData  CHAR (1000),
                          CONSTRAINT PK_SalesOrderHeader_SalesOrderID PRIMARY KEY CLUSTERED (SalesOrderID),
                          CONSTRAINT GK_SalesOrderHeader_CustomerID_FROM_Customer FOREIGN KEY (CustomerID) REFERENCES dbo.Customer (CustomerID)
                      );

                      کد T-SQL بالا دو جدول مشتری و سفارش را ایجاد کرده و یک ارتباط یک به چند مابین آنها را از سمت مشتری به سفارش ایجاد می‌کند. برای انجام آزمایش خود، یکسری دیتای موجود را هم از جداول دیتابیس AdventureWorks2012 در جداول بالا درج می‌کنیم:

                      INSERT INTO dbo.Customer (CustomerID)
                      SELECT CustomerID
                      FROM   Sales.Customer;
                      
                      INSERT INTO dbo.SalesOrderHeader (SalesOrderID, OrderDate, DueDate, CustomerID)
                      SELECT SalesOrderID,
                             OrderDate,
                             DueDate,
                             CustomerID
                      FROM   Sales.SalesOrderHeader;

                      در واقع می‌خواهیم نشان دهیم که در زمان تغییر یک رکورد از جدول Customers، چه اتفاقاتی می‌افتد. برای مثال این تغییر می‌تواند حذف یک رکورد باشد که به شکل زیر آن را انجام خواهیم داد:

                      SET STATISTICS IO ON;
                      
                      DELETE dbo.Customer
                      WHERE  CustomerID = 701;
                      
                      SET STATISTICS IO OFF;

                      آمار و پلن زیر نشان می‌دهد که برای حذف یک رکورد در جدول مشتری، چون از عملیات Index Seek برروی ایندکس خوشه دار موجود برروی ستون CustomerID استفاده شده است، تنها 3 Page خوانده شده‌است؛ ولی برای اعتبارسنجی برروی جدول سفارش، با خواندن 4513 page و انجام عملیات Index Scan برروی ایندکس خوشه دار باعث کاهش کارآیی شده است.

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

                      CREATE INDEX IS_SalesOrderHeader_CustomerID ON dbo.SalesOrderHeader(CustomerID)

                      اگر دوباره کوئری بالا را با یک CustomerID دیگر انجام دهیم، به نتایج بهتری دست خواهیم یافت. تعداد Page‌های خوانده شده‌ی برای اعتبارسنجی جدول سفارشات، به عدد 2 کاهش یافته است! و از یک عملیات Index Seek بر روی ایندکس ایجاد شده، استفاده شده است.

                      اگر از EF استفاده می‌کنید، در حال حاضر به غیر از الگوهای Filtered Indexes و Include Indexes، پیاده سازی بقیه الگوهای ذکر شده به صورت توکار پشتیبانی می‌شود. برای دو الگوی مذکور هم می‌توان از نوشتن T-SQL خام استفاده کرد. برای مثال:

                      public partial class AddIndexes : DbMigration
                          {
                              private const string IndexName = "IX_LogSamples";
                      
                              public override void Up()
                              {
                                  Sql(String.Format(@"CREATE NONCLUSTERED INDEX [{0}]
                                                     ON [dbo].[Logs] ([SampleId],[Date])
                                                     INCLUDE ([Value])", IndexName));
                      
                              }
                      
                              public override void Down()
                              {
                                  DropIndex("dbo.Logs", IndexName);
                              }
                          }

                      یا حتی خیلی تمیزتر و  با ایده گرفتن از این مطلبمی‌توان به یک کد Refactoring friendly نیز دست یافت.

                      پ.ن: این مطلب خلاصه‌ای از فصل 8 کتاب Expert Performance Indexing for SQL Server 2012  می‌باشد. 

                      ‫آموزش LINQ بخش ششم - عملگرهای پرس و جو قسمت اول

                      $
                      0
                      0
                      عملگرهای استاندارد پرس و جو

                      در یک طبقه بندی کلی، عملگرهای پرس و جو بر اساس ورودی و خروجی آنها به سه دسته تقسیم می‌شوند:
                      1- نتیجه‌ی توالی ورودی، بصورت یک توالی، به خروجی ارسال می‌شود.
                      2- نتیجه‌ی توالی ورودی، بصورت یک عنصر یکتا و واحد به خروجی ارسال می‌شود.
                      3- اثری از ورودی در توالی خروجی وجود ندارد (این عملگرها عناصر خودشان را تولید می‌کنند).

                      دسته‌ی آخر شاید کمی عجیب به نطر برسد. این عملگرها هیچ توالی ورودی را دریافت نمی‌کنند. مثلا می‌توان از طریق این عملگر‌ها، یک توالی از اعداد صحیح را تولید کرد.
                      تقسیم بندی عملگرهای پرس و جو بر اساس عملکرد به صورت زیر می‌باشد : 
                      • محدود کننده (Restriction)
                      where
                      • بازتابی (Projection)
                      Select,SelectMany 
                      • جداکننده (Partitioning)
                      Take,Skip,TakeWhile,SkipWhile 
                      • مرتب سازی (Ordering)
                      OrderBy,OrderByDescending,ThenBy,ThenByDescending,Reverse 
                      • گروه بندی (Grouping)
                      GroupBy 
                      • مجموعه (Set)
                      Concat,Union,Intersect,Except 
                      • تبدیل (Conversion)
                      ToArray,ToList,ToDictionary,ToLookup,OfType,Cast 
                      • عنصر(Element)
                      First,FirstOrDefault,Last,LastOrDefalt,Single,SingleOrDefault 
                      • عنصر در (ElementAt)
                      ElementAtOrDefault,DefaultIfEmpty 
                      • تولید (Generation)
                      Empty,Range,Report 
                      • کمی (Quantifier)
                      Any,All,Contains,SequenceEqual 
                      • مجموعه (Aggregate)
                      Count,LongCount,Sum,Min,Max,Average,Aggregate 
                      • اتصال (Join)
                      Join,GroupJoin,Zip 

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

                      عملگرهای محدود کننده (Restriction Operators)
                      این عملگرها یک توالی ورودی را دریافت و یک توالی محدود شده یا به بیان دیگر فیلتر شده را تولید می‌کنند. عناصر توالی خروجی، عناصری هستند که با فیلتر اعمال شده مطابقت دارند.
                      Where
                      این عملگر، عناصری را به خروجی ارسال می‌کند که با گزاره‌ی (Predicate) تعریف شده مطابقت داشته باشند.
                      نکته : گزاره (Predicate) تابعی است که اگر شرط آن تامین شود، مقدار true و در غیر اینصورت مقدار false را باز می‌گرداند.
                      مثال : 
                       Ingredient[] ingredients =
                      {
                         new Ingredient{Name = "Sugar", Calories=500},
                         new Ingredient{Name = "Egg", Calories=100},
                         new Ingredient{Name = "Milk", Calories=150},
                         new Ingredient{Name = "Flour", Calories=50},
                         new Ingredient{Name = "Butter", Calories=200},
                      };
                      
                      IEnumerable<Ingredient> query = ingredients.Where(x => x.Calories >= 200);
                      foreach (var ingredient in query)
                      {
                         Console.WriteLine(ingredient.Name);
                      }
                      در کد فوق از عملگر where استفاده شده است. گزاره‌ی (x=>x.Calories>=200) به ازای هر غذایی که کالری آن مساوی یا بزرگتر از 200 باشد، مقدار true را باز می‌گرداند.
                      خروجی کد بالا:
                       Sugar
                      Butter
                      عملگر where امضای دیگری دارد که اندیس عنصر ورودی توالی را نیز می‌پذیرد. در مثال قبل، اندیس Sugar برابر 0 و اندیس Butter برابر 4 است. پرس و جوی زیر خروجی مشابه مثال قبل را تولید می‌کند.
                       IEnumerable<Ingredient> query = ingredients.Where((ingredient, index) => ingredient.Name == "Sugar" || index == 4);
                      گزاره نوشته شده در این پرس و جو  از نوع <Func<Ingredient,int,bool خواهد بود و پارامتر int، اندیس عنصر در توالی ورودی می‌باشد.

                      پیاده سازی توسط عبارت‌های پرس و جو
                       در روش عبارت‌های پرس و جو، کلمه‌ی کلیدی where به‌همراه یک عبارت منطقی در پرس و جو ظاهر می‌شود:
                       IEnumerable<Ingredient> gueryExpression =
                      from i in ingredients
                      where i.Calories >= 200
                      select i;


                      عملگرهای بازتاب (Projection Operators)

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

                      Select
                      عملگر پرس و جوی select هر عنصر توالی ورودی را به یک عنصر در توالی خروجی تبدیل می‌کند. تعداد عناصر ورودی و خروجی در این حالت یکسان می‌باشند.
                      پرس و جوی زیر عناصر توالی ورودی Ingredient را به عناصر رشته‌ای در توالی خروجی بازتاب می‌کند. عبارت Lambda تعریف شده، نحوه‌ی بازتاب عناصر را مشخص می‌کند (هر عنصر ingredient به یک عنصر رشته‌ای بازتاب می‌شود):
                       IEnumerable<string> query = ingredients.Select(x => x.Name);
                        می‌توان توالی خروجی با عناصر صحیح را نیز تولید کرد:  
                       IEnumerable<int> query = ingredients.Select(x => x.Name.Length);

                      در عملیات بازتاب می‌توان یک شیء جدید را در توالی خروجی ایجاد کرد. در کد زیر عناصر Ingredient به یک عنصر جدید از نوع IngredientNameAndLenght بازتاب شده است.
                      class IngredientNameAndLength
                      {
                          public string Name { get; set; }
                          public int Length { get; set; }
                          public override string ToString()
                          {
                            return Name + " " + Length;
                          }
                      }
                      
                      IEnumerable<IngredientNameAndLength> query = ingredients.Select(x =>
                      new IngredientNameAndLength
                      {
                         Name = x.Name,
                         Length = x.Name.Length
                      });
                      پرس و جوی بالا را می‌توان به شکل نوع‌های بی نام نیز بازنویسی کرد. باید دقت شود که نوع بازگشتی این پرس و جو باید از نوع var باشد.
                      var query = ingredients.Select(x =>
                      new
                      {
                         Name = x.Name,
                         Length = x.Name.Length
                      });
                      خروجی کد بالا به شکل زیر است :
                      { Name = Sugar, Length = 5 }
                      { Name = Egg, Length = 3 }
                      { Name = Milk, Length = 4 }
                      { Name = Flour, Length = 5 }
                      { Name = Butter, Length = 6 }

                      پیاده سازی توسط عبارت‌های پرس و جو

                      کلمه‌ی کلیدی select در عبارت‌های پرس و جو، به شکل زیر استفاده می‌شود:
                      var query = from i in ingredients
                      select new
                      {
                          Name=i.Name,
                          Length=i.Name.Length
                      };

                      SelectMany 
                      برعکس دستور select که به ازای هر عنصر در توالی ورودی، یک عنصر را در توالی خروجی بازتاب می‌کرد، دستور SelectMany ممکن است تعداد عناصر کمتر و یا بیشتری را در توالی خروجی بازتاب کند (انتخاب مقادیر یک مجموعه از مجموعه‌ی دیگر).
                      عبارت Lambda نوشته شده در عملگر Select، یک مقدار را باز می‌گرداند. اما عبارت Lambda نوشته شده در عملگر SelectMany، یک توالی فرزند (Child Sequence) را ایجاد می‌کند. توالی فرزند ممکن است حاوی تعداد مختلفی از عناصر به ازای هر عنصر در توالی ورودی باشد.
                      در مثال زیر عبارت Lambda یک توالی فرزند از کاراکتر‌ها ایجاد می‌کند (یک کاراکتر به ازای هر حرف از هر عنصر توالی ورودی). به‌طور مثال عنصر ورودی Sugar، پس از پردازش توسط  عبارت Lambda، یک توالی فرزند با 5 عنصر 's','u','g','e','r' فراهم می‌کند. هر رشته‌ی در توالی Ingredient می‌تواند تعداد حروف متفاوتی داشته باشد. در نتیجه عبارت Lambda، توالی‌های فرزندی با طول‌های مختلف ایجاد می‌کند.
                      مثال:
                      string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"};
                      IEnumerable<char> query = ingredients.SelectMany(x => x.ToCharArray());
                      foreach (var item in query)
                      {
                         Console.WriteLine(item);
                      }
                      خروجی مثال بالا :
                       S
                      u
                      g
                      a
                      r
                      E
                      g
                      g
                      M
                      i
                      l
                      k
                      F
                      l
                      o
                      u
                      r
                      B
                      u
                      t
                      t
                      e
                      r

                      پیاده سازی توسط عبارت‌های پرس و جو

                      در روش عبارت‌های پرس و جو یک عبارت (clause) اضافی from برای تولید یک توالی فرزند به کار برده می‌شود. خروجی کد زیر مشابه کد قبلی است:
                       string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"};
                      IEnumerable<char> query2 = from i in ingredients
                      from c in i.ToCharArray()
                      select c;
                      
                      foreach (var item in query2)
                      {
                         Console.WriteLine(item);
                      }

                      عملگرهای جداکننده (Partitioning Operators)
                      عملگر‌های جداکننده، یک توالی ورودی را دریافت و آنها را از هم جدا می‌کنند.

                      Take
                      عملگر Takeیک توالی ورودی را دریافت کرده و تعداد مشخصی از توالی را باز می‌گرداند.
                      مثال: عملگر Take، سه عضو اول توالی Ingredient را باز می‌گرداند:
                       Ingredient[] ingredients =
                      {
                         new Ingredient{Name = "Sugar", Calories=500},
                         new Ingredient{Name = "Egg", Calories=100},
                         new Ingredient{Name = "Milk", Calories=150},
                         new Ingredient{Name = "Flour", Calories=50},
                         new Ingredient{Name = "Butter", Calories=200},
                      };
                      
                      IEnumerable<Ingredient> query = ingredients.Take(3);
                      foreach (var ingredient in query)
                      {
                         Console.WriteLine(ingredient.Name);
                      }
                      خروجی کد بالا :
                       Sugar
                      Egg
                      Milk
                      همچون سایر عملگر‌های پرس و جو، عملگر Take هم می‌تواند بصورت زنجیروار استفاده شود. در مثال زیر ابتدا عملگر Where برای محدود کردن عناصر با شرطی خاص و سپس عملگر Take برای جدا کردن عناصر حاصل از نتیجه‌ی مرحله قبل مورد استفاده قرار گرفته است:
                      Ingredient[] ingredients =
                      {
                         new Ingredient{Name = "Sugar", Calories=500},
                         new Ingredient{Name = "Egg", Calories=100},
                         new Ingredient{Name = "Milk", Calories=150},
                         new Ingredient{Name = "Flour", Calories=50},
                         new Ingredient{Name = "Butter", Calories=200},
                      };
                      
                      IEnumerable<Ingredient> query = ingredients.Where(x=>x.Calories>100).Take(2);
                      foreach (var ingredient in query)
                      {
                         Console.WriteLine(ingredient.Name);
                      }
                      خروجی کد بالا :
                      Sugar
                      Milk

                      پیاده سازی توسط عبارت‌های پرس و جو

                      کلمه‌ی کلیدی (Key word) جایگزینی برای عملگر Take وجود ندارد، ولی می‌توان با ترکیب دو روش نوشتن پرس و جو، خروجی مورد نظر را تولید کرد:
                       IEnumerable<Ingredient> query =
                      (from i in ingredients
                        where i.Calories > 100
                        select i).Take(2);
                      TakeWhile
                      عملگر TakeWhile بر عکس عملگر Take تعداد مشخصی را باز می‌گرداند . این عملگر تا زمانی که گزاره با عناصر مطابقت داشته باشد، اجرا می‌شود و در غیر اینصورت خاتمه پیدا می‌کند.
                      کد زیر تا زمانی که خصوصیت Calorie توالی ورودی بزرگتر و مساوی 100 باشد، عناصر را جدا می‌کند.
                      Ingredient[] ingredients =
                      {
                         new Ingredient{Name = "Sugar", Calories=500},
                         new Ingredient{Name = "Egg", Calories=100},
                         new Ingredient{Name = "Milk", Calories=150},
                         new Ingredient{Name = "Flour", Calories=50},
                         new Ingredient{Name = "Butter", Calories=200},
                      };
                      
                      IEnumerable<Ingredient> query = ingredients.TakeWhile(x => x.Calories >= 100);
                      foreach (var ingredient in query)
                      {
                         Console.WriteLine(ingredient.Name);
                      }
                      خروجی کد بالا :
                       Sugar
                      Egg
                      Milk
                      همانطور که مشاهده می‌کنید، وقتی عملگر TakeWhile به عنصری می‌رسد که گزاره‌ی آن را نقض می‌کند، متوقف می‌شود (در اینجا Flour). در حالی که ممکن است عناصری بعد از Flour وجود داشته باشند که با گزاره‌ی TakeWhile تطابق داشته باشند.

                      پیاده سازی توسط عبارت‌های پرس و جو
                      برای این عملگر هم کلمه‌ی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو نتیجه‌ی دلخواه حاصل می‌شود.
                       
                      Skip
                      این عملگر تعداد مشخصی از عناصر را از ابتدای توالی نادیده گرفته و باقی عناصر را باز می‌گرداند.
                      کد زیر سه عضو اول توالی را نادیده گرفته و مابقی را باز می‌گرداند:
                      Ingredient[] ingredients =
                      {
                         new Ingredient{Name = "Sugar", Calories=500},
                         new Ingredient{Name = "Egg", Calories=100},
                         new Ingredient{Name = "Milk", Calories=150},
                         new Ingredient{Name = "Flour", Calories=50},
                         new Ingredient{Name = "Butter", Calories=200},
                      };
                      
                      IEnumerable<Ingredient> query = ingredients.Skip(3);
                      foreach (var ingredient in query)
                      {
                         Console.WriteLine(ingredient.Name);
                      }
                      خروجی کد بالا :
                       Flour
                      Butter

                      پیاده سازی توسط عبارت‌های پرس و جو

                      برای این عملگر هم کلمه‌ی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو، نتیجه‌ی دلخواه حاصل می‌شود.
                      با ترکیب عملگر Take و Skip می‌توان اطلاعات را به‌صورت صفحه بندی به کاربر ارائه کرد. مثال زیر این حالت را نشان می‌دهد.
                      IEnumerable<Ingredient> firstPage = ingredients.Take(2);
                      IEnumerable<Ingredient> secondPage = ingredients.Skip(2).Take(2);
                      IEnumerable<Ingredient> thirdPage = ingredients.Skip(4).Take(2);
                      
                      Console.WriteLine("First Page : ");
                      foreach (var ingredient in firstPage)
                      {
                         Console.WriteLine(" - " + ingredient.Name);
                      }
                      
                      Console.WriteLine("Second Page : ");
                      foreach (var ingredient in secondPage)
                      {
                         Console.WriteLine(" - " + ingredient.Name);
                      }
                      
                      Console.WriteLine("Third Page : ");
                      foreach (var ingredient in thirdPage)
                      {
                         Console.WriteLine(" - " + ingredient.Name);
                      }
                      خروجی کد بالا :
                       First Page :
                       - Sugar
                       - Egg
                      Second Page :
                       - Milk
                       - Flour
                      Third Page :
                       - Butter
                      SkipWhile
                      عملگر SkipWhile، مثل عملگر TakeWhile، از یک گزاره برای ارزیابی عناصر توالی استفاده می‌کند. این عملگر تا زمانیکه عناصر توالی، گزاره را نقض نکنند، عناصر را نادیده می‌گیرد.

                      مثال:
                      Ingredient[] ingredients =
                      {
                         new Ingredient{Name = "Sugar", Calories=500},
                         new Ingredient{Name = "Egg", Calories=100},
                         new Ingredient{Name = "Milk", Calories=150},
                         new Ingredient{Name = "Flour", Calories=50},
                         new Ingredient{Name = "Butter", Calories=200},
                      };
                      
                      IEnumerable<Ingredient> query = ingredients.SkipWhile(x => x.Name != "Milk");
                      foreach (var ingredient in query)
                      {
                         Console.WriteLine(ingredient.Name);
                      }
                      خروجی کد بالا:
                       Milk
                      Flour
                      Butter
                      Viewing all 1980 articles
                      Browse latest View live


                      <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>