مطالب
شروع به کار با EF Core 1.0 - قسمت 12 - بررسی تنظیمات ارث بری روابط
پیشنیاز: «تنظیمات ارث بری کلاس‌ها در EF Code first»

در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، می‌توانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطه‌ی TPH که در آن تمام کلاس‌های سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت می‌شوند، پشتیبانی می‌شود. سایر روش‌های ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارش‌های پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارش‌های بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC


طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط

مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاس‌های دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیت‌های برنامه، جهت یافتن سریع آن‌ها توسط Reflection (برای مثال افزودن خودکار موجودیت‌ها به Context برنامه با یافتن تمام کلاس‌هایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
public class BaseEntity
{
   public int Id { set; get; }
   public DateTime? DateAdded { set; get; }
   public DateTime? DateUpdated { set; get; }
}
و پس از آن هر موجودیت برنامه به این شکل خلاصه شده و نشانه گذاری می‌شود:
public class Person : BaseEntity
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
}
حالت پیش فرض ارث بری‌ها در EF Core، همان حالت TPH است که در ادامه توضیح داده خواهد شد. اما هدف ما در اینجا تنظیم هیچکدام از حالت‌های ارث بری نیست. هدف صرفا کاهش تعداد فیلدهای تکراری ذکر شده‌ی در کلاس‌های دومین برنامه است. بنابراین جهت لغو تنظیمات ارث بری EF Core، نیاز است یک چنین تنظیمی را انجام داد:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Ignore<BaseEntity>();
با فراخوانی متد Ignore بر روی کلاس پایه‌ی تهیه شده، این کلاس دیگر وارد تنظیمات روابط EF Core نمی‌شود و در جداول نهایی، فیلدهای آن به صورت معمول در کنار سایر فیلدهای جداول مشتق شده‌ی از آن‌ها قرار می‌گیرند.

مشکل! اگر بر روی کلاس پایه‌ی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاس‌های مشتق شده‌ی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Ignore<BaseEntity>();
   foreach (var entityType in modelBuilder.Model.GetEntityTypes())
   {
     var dateAddedProperty = entityType.FindProperty("DateAdded");
     dateAddedProperty.ValueGenerated = ValueGenerated.OnAdd;
     dateAddedProperty.SqlServer().DefaultValueSql = "getdate()";
     var dateUpdatedProperty = entityType.FindProperty("DateUpdated");
     dateUpdatedProperty.ValueGenerated = ValueGenerated.OnAddOrUpdate;
     dateUpdatedProperty.SqlServer().ComputedColumnSql = "getdate()";
   }
کاری که در اینجا انجام شده، تنظیم خاصیت DateAdded کلاس پایه، به حالت ValueGeneratedOnAdd و تنظیم خاصیت DateUpdated کلاس پایه به حالت ValueGeneratedOnAddOrUpdate با مقدار پیش فرض getdate است. این مفاهیم را در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها» پیشتر بررسی کردیم.
خلاصه‌ی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیت‌های خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی می‌شود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges  (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی می‌شود.

تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار می‌کند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شده‌است و نه سروری که برنامه‌ی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمت‌های بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاس‌های پایه و مقدار دهی خواص ویژه‌ی آن‌ها مطرح می‌کنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.


بررسی تنظیمات رابطه‌ی  Table per Hierarchy یا TPH

رابطه‌ی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاس‌های دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی می‌گردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه می‌شود. از فیلد Discriminator جهت درج نام کلاس‌های متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا می‌داند که چگونه باید خواص متناظر با کلاس‌های مختلف را مقدار دهی کند و نوع ردیف درج شده‌ی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاس‌ها است و نیاز به هیچگونه تنظیم اضافه‌تری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج می‌شوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر می‌باشد:
public class Blog
{
   public int BlogId { get; set; }
   public string Url { get; set; }
}

public class RssBlog : Blog
{
   public string RssUrl { get; set; }
}

class MyContext : DbContext
{
   public DbSet<Blog> Blogs { get; set; }
   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      modelBuilder.Entity<Blog>()
       .HasDiscriminator<string>("blog_type")
       .HasValue<Blog>("blog_base")
       .HasValue<RssBlog>("blog_rss");
   }
}
در اینجا نام فیلد Discriminator، به blog_type، مقدار نوع متناظر با کلاس Blog، به blog_base و مقدار نوع متناظر با کلاس RssBlog، به blog_rss تنظیم شده‌است.
اگر این تنظیمات سفارشی صورت نگیرند، از نام‌های پیش فرض نوع‌ها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:


برای کوئری نوشتن در این حالت می‌توان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
 var blog1 = db.Blogs.OfType<RssBlog>().FirstOrDefault(x => x.RssUrl == "………");
مطالب
متغیرها در ES 6
در ES 6 تغییراتی جهت ساده سازی خواندن، نوشتن و همچنین بالا بردن امنیت متغیرها و پارامترها صورت گرفته‌اند، تا دیگر شاهد یک سری رفتارهای عجیب و غریب، در حین کار با متغیرهای جاوا اسکریپتی نباشیم.


واژه‌ی کلیدی let

تاکنون به کمک واژه‌ی کلیدی var امکان تعریف متغیرها در جاوا اسکریپت مهیا بودند. برای نمونه در مثال زیر، متغیر x داخل بدنه‌ی if با استفاده از var تعریف شده‌است:
var doWork = function(flag){
   if(flag){
     var x = 3;
   }
   return x;
};
در اینجا اگر متد doWork را با پارامتر true اجرا کنیم، خروجی 3  و اگر آن‌را با پارامتر false اجرا کنیم، خروجی undefined را دریافت خواهیم کرد:


زمانیکه از var استفاده می‌شود، برای یک متغیر دو نوع میدان دید را می‌توان متصور شد:
- اگر خارج از بدنه‌ی تابع تعریف شود، این متغیر عمومی خواهد بود.
- اگر داخل بدنه‌ی تابع تعریف شود، میدان دید آن محدود به همان بدنه‌ی تابع می‌شود. در این حالت چیزی به نام block scope بی‌مفهوم است. در متد doWork فوق، هرچند متغیر x داخل بدنه‌ی بلاک if تعریف شده‌است، اما این x در کل بدنه‌ی تابع در دسترس است و نه صرفا داخل بلاک if. این مورد تا پیش از ES 6 منشاء بسیاری از باگ‌ها بوده‌است.
بنابراین در اینجا چون x تعریف شده، میدان دیدی در سطح متد دارد، return x معتبر بوده و در حالت دریافت پارامتر true، مقدار 3 را بر می‌گرداند و در حالت false هم همچنان مقداری را دریافت خواهیم کرد و این مقدار undefined است (اما پیام خطای عدم دسترسی به x را دریافت نمی‌کنیم).
به این رفتار اصطلاحا hoisting می‌گویند. در این حالت موتور جاوا اسکریپت، تمام متغیرهای تعریف شده‌ی توسط var را به صورت ضمنی به ابتدای تعریف متد منتقل کرده و آن‌ها را در آن‌جا تعریف می‌کند. به همین جهت است که return x تعریف شده‌ی در انتهای متد، قابلیت دسترسی به x داخل بدنه‌ی if را دارد.

در ES 6 برای رفع این مشکل، واژه‌ی کلیدی جدیدی به نام let معرفی شده‌است و هدف آن مهیا کردن block scoping تعریف متغیرها است:
var doWork = function(flag){
    if(flag){
      let x = 3;
    }
    return x;
};
اینبار اگر متد doWork را با پارامتر true فراخوانی کنیم، به خروجی ذیل خواهیم رسید:


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

این block scoping مهیا شده‌ی توسط let، با حلقه‌ی for نیز کار می‌کند:
var doWork = function(){
   for(let i = 0; i< 10; i++){
   }

   /* return i won't work */
  return 0;
};
در مثال فوق اگر return i را در انتهای متد قرار دهیم، با همان خطای Uncaught ReferenceError پیشین مواجه خواهیم شد؛ از این جهت که برخلاف var، متغیر تعریف شده‌ی با let، میدان دیدی در سطح قطعه و بلاک تعریف شده‌ی در آن دارد و در اینجا بلاک متغیر i همان حلقه‌ی for است.


یک نکته

مفهوم block scoping با تعریف {} معنا پیدا می‌کند. بنابراین می‌توانید یک قطعه‌ی دلخواه را با تعریف {} نیز مشخص کنید:

و یا در مثال ذیل چندین قطعه‌ی تو در تو را مشاهده می‌کنید:
let outer = 'I am so eccentric!'
{
  let inner = 'I play with neighbors in my block and the sewers'
  {
    let innermost = 'I only play with neighbors in my block'
  }
  // accessing innermost here would throw
}
// accessing inner here would throw
// accessing innermost here would throw
در اینجا میدان دید متغیرهای تعریف شده، محدود است به قطعه‌ی آن‌ها. به همین جهت است که نمی‌توان به متغیر innermost در خارج از بلاک آن دسترسی یافت.

نمونه‌ی دیگر آن تعریف یک متد داخل یک بلاک است:
{
  let _nested = 'secret'
  function nested () {
    return _nested
  }
}
console.log(nested())
اگر این قطعه کد را اجرا کنیم، به خطای ذیل خواهیم رسید:


در ES 6 نمی‌توان به متغیرهای تعریف شده‌ی توسط let داخل یک بلاک، در خارج از آن دسترسی یافت. اگر می‌خواهید سطح دسترسی به متد را افزایش دهید، نیاز است به شکل ذیل عمل کنید و متد را خارج از بدنه‌ی بلاک با سطح دسترسی بیشتری تعریف نمائید:
var nested;
{
  let _nested = 'secret'
  nested = function () {
    return _nested
  }
}
console.log(nested())
// <- 'secret'


واژه‌ی کلیدی const

در ES 6 برای ایجاد و مقدار دهی متغیرهای فقط خواندنی، واژه‌ی کلیدی const افزوده شده‌است. در اینجا const نیز مانند let دارای block scoping است.
doWork = function()
{
   const value = 10;
   value = 11;
   return value;
}
در این مثال ابتدا متغیر value به صورت یک ثابت تعریف شده‌است و سپس مقدار 11 به آن نسبت داده شده‌است. اگر آن‌را در کروم 47 اجرا کنید، از مقدار 11 صرفنظر شده و خروجی 10 را بازگشت می‌دهد. اما اگر آن‌را در فایرفاکس 43 اجرا کنید، خطای متناظر با ES 6 را بازگشت می‌دهد:


در ES 6، انتساب یک مقدار به یک const، پس از تعریف آن، منجر به بروز خطای syntax error خواهد شد. همچنین تعریف مجدد آن نیز چنین خطایی را سبب خواهد شد.

یک نکته
هر چند const سبب read only شدن یک متغیر می‌شود، اما آن‌را immutable نمی‌کند:
const items = { people: ['you', 'me'] }
items.people.push('test')
console.log(items)
با این خروجی:

همانطور که مشاهده می‌کنید، هنوز هم می‌توان به شیء تعریف شده، آیتمی را اضافه کرد (در اینجا test به آرایه‌ی people اضافه شده‌است).


آشنایی با مفهوم shadowing

همان مثال ابتدای بحث را در نظر بگیرید:
var doWork = function(flag){
   if(flag){
        let x = 10;
        var x = 3;
        return x;
   }
};
داخل بدنه‌ی if، متغیر x یکبار توسط let و بار دیگر توسط var تعریف شده‌است.  در این حالت خطای Uncaught SyntaxError: Identifier 'x' has already been declared را دریافت خواهیم کرد (اگر let اول را به var تغییر دهید، مشکلی نخواهد بود و برنامه کامپایل می‌شود). اما اگر let x را به پیش از متد انتقال دهیم، اینبار مثال کامپایل می‌شود و خروجی متد (doWork(true مساوی 3 خواهد بود:
let x = 10;
var doWork = function(flag){
    if(flag){
      var x = 3;
      return x;
   }
};
در این حالت x تعریف شده‌ی داخل بلاک توسط var (یا حتی let) مقدار x تعریف شده‌ی در بلاک بالاتر را مخفی می‌کند که به آن shadowing نیز می‌گویند. در این حالت اگر در خارج از متد doWork، به x دسترسی پیدا کنیم، مقدار آن همان 10 است.
مثال ذکر شده، با مثال ذیل که یک بلاک را توسط {} ایجاد کرده‌ایم، یکی است:
let x = 10;
{
   let x = 3;
   console.log(x);
}
console.log(x);


در اینجا نیز ابتدا مقدار 3 که مرتبط با بلاک داخلی است چاپ خواهد شد و سپس مقدار 10 که مرتبط است به بلاک خارجی‌تر.
مطالب
بررسی کارآیی کوئری‌ها در SQL Server - قسمت هشتم - بررسی عملگرهای Hash Join و Compute Scalar در یک Query Plan
در یک hash join، اطلاعات از دو ورودی نامرتب، دریافت و join می‌شوند که نسبت به merge join، عملیات سنگین‌تری است. برای اینکار، یک hash table را از دیتاست خارجی و یک نمونه‌ی دیگر را بر اساس دیتاست درونی ساخته و سپس کار انطباق ردیف‌ها را انجام می‌دهد.


بررسی عملگر hash join

 ابتدا در management studio از منوی Query، گزینه‌ی Include actual execution plan را انتخاب می‌کنیم. سپس کوئری‌های زیر را اجرا می‌کنیم:
USE [WideWorldImporters];
GO

SET STATISTICS IO ON;
GO


/*
Query with a hash join
*/
SELECT
    [ol].[OrderID],
    [ol].[OrderLineID],
    [ol].[StockItemID],
    [ol].[PickedQuantity],
    [si].[StockItemName],
    [si].[UnitPrice]
FROM [Warehouse].[StockItems] [si]
    JOIN [Sales].[OrderLines] [ol]
    ON [si].[StockItemID] = [ol].[StockItemID];
GO
در اینجا اطلاعات دو جدول StockItems و OrderLines بر روی ستون StockItemID با هم Join شده‌اند و اجرای آن یک چنین کوئری پلنی را تولید می‌کند:


دیتاست بالایی که ضخامت پیکان خارج شده‌ی از آن کمتر است، تعداد ردیف‌های کمتری را نسبت به دیتاست درونی دارد (227 ردیف، در مقابل بیش از 231 هزار ردیف).
با حرکت اشاره‌گر ماوس بر روی هر کدام از ایندکس‌ها، می‌توان با دقت کردن به Output List آن‌ها، دقیقا دریافت که هرکدام، چه ستون‌هایی از کوئری نهایی را تامین می‌کنند:
دیتاست بالایی که از PK_Warehouse_StockItems تامین می‌شود:
ALTER TABLE [Warehouse].[StockItems] ADD  CONSTRAINT [PK_Warehouse_StockItems] PRIMARY KEY CLUSTERED
(
   [StockItemID] ASC
)


دیتاست درونی که از NCCX_Sales_OrderLines تامین می‌شود و یک COLUMNSTORE INDEX است:
CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
(
[OrderID],
[StockItemID],
[Description],
[Quantity],
[UnitPrice],
[PickedQuantity]
)



بهبود کارآیی hash join با فشرده سازی ایندکس‌های آن

ایندکس NCCX_Sales_OrderLines که در کوئری فوق مورد استفاده قرار گرفته، همانطور که در قسمتی از تعریف آن نیز مشخص است، تعداد ستون‌های بیشتری را از آنچه ما نیاز داریم، در بر دارد. در این حالت آیا اگر ایندکس مناسب‌تری را با تعداد ستون کمتری ایجاد کنیم، از آن استفاده می‌کند؟
CREATE NONCLUSTERED INDEX [IX_OrderLines_StockItemID]
ON [Sales].[OrderLines](
[StockItemID] ASC,
[PickedQuantity] ASC,
[OrderID])
ON [PRIMARY];
GO
این ایندکس جدید، نیازهای واقعی کوئری نوشته شده را پوشش می‌دهد و تعداد ستون کمتری را به همراه دارد.
در این حالت اگر کوئری زیر را اجرا کنیم:
SELECT
    [ol].[OrderID],
    [ol].[OrderLineID],
    [ol].[StockItemID],
    [ol].[PickedQuantity],
    [si].[StockItemName],
    [si].[UnitPrice]
FROM [Sales].[OrderLines] [ol]
    JOIN [Warehouse].[StockItems] [si]
    ON [ol].[StockItemID] = [si].[StockItemID]
OPTION
(RECOMPILE);
GO
در کوئری پلن نهایی تفاوتی مشاهده نمی‌شود و باز هم SQL Server، همان COLUMNSTORE INDEX را به ایندکس جدید ترجیح داده‌است. علت اینجا است که ماهیت COLUMNSTORE INDEX‌ها فشرده شده‌است؛ در مقابل NONCLUSTERED INDEXها معمولی که به صورت پیش‌فرض غیر فشرده شده هستند و یک row store می‌باشند.

یک نکته: در این کوئری علت استفاده‌ی از RECOMPILE، وادار کردن SQL server به محاسبه‌ی مجدد کوئری پلن جاری است.

اکنون اگر نگارش فشرده شده‌ی ایندکسی را که ایجاد کردیم، با ذکر گزینه‌ی DATA_COMPRESSION = PAGE تعریف کنیم، چه اتفاقی رخ می‌دهد؟
CREATE NONCLUSTERED INDEX [IX_OrderLines_StockItemID_Compressed]
ON [Sales].[OrderLines](
[StockItemID] ASC,
[PickedQuantity] ASC,
[OrderID])
WITH (DATA_COMPRESSION = PAGE)
ON [PRIMARY];
GO
پس از آن مجددا همان کوئری قبلی را که به همراه RECOMPILE است، اجرا می‌کنیم. اینبار به کوئری پلنی خواهیم رسید که از این ایندکس جدید استفاده می‌کند.

یک نکته: اگر علاقمند بودید تا هزینه‌ی این کوئری‌ها را نسبت به یکدیگر محاسبه و مقایسه کنید، چون یک کوئری معمولی، همواره از آخرین پلن محاسبه شده استفاده می‌کند، اینکار میسر نیست. اما می‌توان با ذکر صریح ایندکس مدنظر توسط راهنمای WITH INDEX، بهینه ساز کوئری‌ها را وارد کرد تا از ایندکسی که ذکر می‌شود، بجای ایندکسی که فکر می‌کند بهتر است، استفاده کند. بنابراین اجرای هر 4 کوئری زیر با هم، 4 کوئری پلن متفاوت را بر اساس ایندکس‌های متفاوتی، محاسبه کرده و نمایش می‌دهد:
SELECT
    [ol].[OrderID],
    [ol].[OrderLineID],
    [ol].[StockItemID],
    [ol].[PickedQuantity],
    [si].[StockItemName],
    [si].[UnitPrice]
FROM [Sales].[OrderLines] [ol]
    JOIN [Warehouse].[StockItems] [si]
    ON [ol].[StockItemID] = [si].[StockItemID]
OPTION
(RECOMPILE);
GO

SELECT
    [ol].[OrderID],
    [ol].[OrderLineID],
    [ol].[StockItemID],
    [ol].[PickedQuantity],
    [si].[StockItemName],
    [si].[UnitPrice]
FROM [Sales].[OrderLines] [ol] WITH (INDEX (IX_Sales_OrderLines_Perf_20160301_02))
    JOIN [Warehouse].[StockItems] [si]
    ON [ol].[StockItemID] = [si].[StockItemID];
GO

SELECT
    [ol].[OrderID],
    [ol].[OrderLineID],
    [ol].[StockItemID],
    [ol].[PickedQuantity],
    [si].[StockItemName],
    [si].[UnitPrice]
FROM [Sales].[OrderLines] [ol] WITH (INDEX (IX_OrderLines_StockItemID))
    JOIN [Warehouse].[StockItems] [si]
    ON [ol].[StockItemID] = [si].[StockItemID];
GO

SELECT
    [ol].[OrderID],
    [ol].[OrderLineID],
    [ol].[StockItemID],
    [ol].[PickedQuantity],
    [si].[StockItemName],
    [si].[UnitPrice]
FROM [Sales].[OrderLines] [ol] WITH (INDEX (IX_OrderLines_StockItemID_Compressed))
    JOIN [Warehouse].[StockItems] [si]
    ON [ol].[StockItemID] = [si].[StockItemID];
GO


بررسی عملگر compute scalar

کار عملگر compute scalar، ارزیابی و محاسبه‌ی یک عبارت است و خروجی آن نیز یک مقدار scalar است؛ مانند functions در SQL Server. مشکلی که با این عملگر وجود دارد این است که هزینه‌ی انجام آن عموما در کوئری پلن ظاهر نمی‌شود (و یا با تخمین نادرستی ظاهر می‌شود) که می‌تواند گمراه کننده باشد. همچنین پلن حاصل، اشیایی را که توسط یک function مورد استفاده قرار می‌گیرند، لحاظ نمی‌کند.

برای نمونه اگر پلن دو کوئری زیر را با هم مقایسه کنیم:
SELECT COUNT(*)
FROM [Sales].[Orders];

SELECT COUNT_BIG (*)
FROM [Sales].[Orders];
تقریبا یکی هستند:


از این جهت که (*)COUNT در SQL server به (*)COUNT_BIG تفسیر شده و اجرا می‌شود. به همین جهت آنچنان تفاوتی در اینجا قابل مشاهده نیست.

اما اگر function زیر را تعریف کنیم:
CREATE FUNCTION dbo.CountProductsSold (
@SalesPersonID INT
) RETURNS INT

AS

BEGIN
    DECLARE @SoldCount INT;

    SELECT @SoldCount = COUNT(DISTINCT [ol].[StockItemID])
    FROM [Sales].[Orders] [o]
        JOIN [Sales].[OrderLines] [ol]
        ON [o].[OrderID] = [ol].[OrderID]
    WHERE [o].[SalespersonPersonID] = @SalesPersonID

    RETURN (@SoldCount);

END
و سپس پلن کوئری که از آن استفاده می‌کند را بررسی نمائیم:
SELECT
    [FullName] AS [SalesPerson],
    [dbo].[CountProductsSold]([PersonID]) AS [NumberOfProductsSold]
FROM [Application].[People]
WHERE [IsSalesperson] = 1;
مشاهده خواهیم کرد که در actual execution plan آن، هزینه‌ی فراخوانی این تابع صفر است و همچنین جزئیاتی از اشیایی که توسط آن فراخوانی شده‌اند نیز ذکر نشده‌است:


یک روش محاسبه‌ی هزینه‌ی فراخوانی این تابع، استفاده از extended events است. روش دیگر آن استفاده از اشیاء DMO's می‌باشد:
SELECT
    [fs].[last_execution_time],
    [fs].[execution_count],
    [fs].[total_logical_reads]/[fs].[execution_count] [AvgLogicalReads],
    [fs].[max_logical_reads],
    [t].[text],
    [p].[query_plan]
FROM sys.dm_exec_function_stats [fs]
CROSS APPLY sys.dm_exec_sql_text([fs].sql_handle) [t]
CROSS APPLY sys.dm_exec_query_plan([fs].[plan_handle]) [p];
این کوئری اطلاعات logical_reads مرتبط با تابع فراخوانی شده را گزارش می‌دهد که ... صفر نیست:


بنابراین compute scalar صورت گرفته دارای هزینه‌ای است که در actual execution plan ظاهر نمی‌شود.
اکنون اگر از منوی Query، گزینه‌ی Include actual execution plan را انتخاب نکنیم و بجای آن گزینه‌ی Display estimated execution plan را انتخاب کنیم، به تصویر زیر خواهیم رسید:


در نیمه‌ی پایینی آن، جزئیات دسترسی‌های تابع فراخوانی شده نیز ذکر می‌شوند. بنابراین استفاده‌ی از estimated execution planها در حین کار با توابع، بسیار مفید است.
نظرات مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت دوم - ایجاد و اجرای اولین برنامه
نکته‌ای در مورد دیباگ برنامه‌های NET Core 2.0.

اگر فایل vscode\launch.json. پیشتر برای یک پروژه‌ی NET Core 1.x. تشکیل شده باشد، پس از ارتقاء برنامه به NET Core 2.0.، با شروع دیباگر در VSCode، خطای ذیل را مشاهده خواهید کرد:
The specified framework 'Microsoft.NETCore.App', version '1.1.2' was not found.
علت اینجا است که در فایل launch.json، مسیر dll اصلی برنامه هنوز به netcoreapp1.0 تنظیم است و باید به صورت ذیل به netcoreapp2.0 اصلاح شود:
"program": "${workspaceRoot}/src/ASPNETCoreIdentitySample/bin/Debug/netcoreapp2.0/ASPNETCoreIdentitySample.dll",
مطالب
افزونه rtlColResizable جهت تغییر سایز ستون‌های جدول‌های راست به چپ HTML
برای تغییر سایز ستون‌های جداول HTML با استفاده از ماوس، افزونه‌های زیادی تدارک دیده شده است که از جمله مطرح‌ترین آن‌ها می‌توان به colResizable اشاره کرد. حتی اگر از DataGrid‌های مطرح وب هم استفاده کرده باشید، اکثر آن‌ها از تغییر سایز ستون‌ها توسط کاربر پشتیبانی می‌کنند. اما مشکل بزرگی که در همه‌ی آن‌ها مشترک است  این است که فقط از چیدمان‌های چپ به راست پشتیبانی می‌کنند و به محض اینکه شما ساختار راست به چپ را به جدول مورد نظر اعمال کنید، عملکرد تغییر سایز ستون‌ها با اشکال مواجه می‌شود.
به همین جهت برای تغییر سایز ستون‌ها توسط کاربر، افزونه ای برای jQuery تدارک دیدم که با جداول معمول HTML که همان table‌ها هستند سازگار است و به راحتی به هر جدولی می‌توان آن را اعمال کرد.
 
کدهای افزونه‌ی rtlColResizable:
(function ($, undefined) {

    $.fn.extend({
        'rtlColResizable': function (options) {
            var defaults = {
                //Default values for the plugin's options here
            };

            options = $.extend(defaults, options);

            var isMouseButtonPressed;
            var $resizingElement = undefined;
            var resizingElementStartWidth;
            var mouseCursorStartX;
            var isCursorInResizingPosition;

            var addResizingCursorStyle = function ($element) {
                $element.css({
                    'cursor': 'col-resize',
                    'user-select': 'none',
                    '-o-user-select': 'none',
                    '-ms-user-select': 'none',
                    '-moz-user-select': 'none',
                    '-khtml-user-select': 'none',
                    '-webkit-user-select': 'none',
                });
            };

            var removeResizingCursorStyle = function ($element) {
                $element.css({
                    'cursor': 'default',
                    'user-select': 'text',
                    '-o-user-select': 'text',
                    '-ms-user-select': 'text',
                    '-moz-user-select': 'text',
                    '-khtml-user-select': 'text',
                    '-webkit-user-select': 'text',
                });
            };

            var canResize = function (e) {
                return (e.offsetX || e.clientX - $(e.target).offset().left) < 10;
            };

            return this.each(function () {

                var opts = options;
                var tableColumns = $(this).find('th');

                tableColumns.filter(':not(:last-child)').mousedown(function (e) {

                    $resizingElement = $(this);
                    isMouseButtonPressed = true;
                    mouseCursorStartX = e.pageX;
                    resizingElementStartWidth = $resizingElement.width();

                });

                tableColumns.mousemove(function (e) {

                    if (canResize(e)) {
                        addResizingCursorStyle($(e.target));
                        isCursorInResizingPosition = true;

                    } else if (!isMouseButtonPressed) {
                        removeResizingCursorStyle($(e.target));
                        isCursorInResizingPosition = false;
                    }

                    if (isCursorInResizingPosition && isMouseButtonPressed) {

                        $resizingElement.width(resizingElementStartWidth + (mouseCursorStartX - e.pageX));
                    }

                });

                $(document).mouseup(function () {
                    if (isMouseButtonPressed) {
                        removeResizingCursorStyle($resizingElement);
                        isMouseButtonPressed = false;
                    }

                });

            });
        }
    });

})(jQuery);
 
نحوه‌ی استفاده:
این افزونه با تگ Table در HTML سازگار است. فقط تنها موردی که باید رعایت شود این است که در هنگام تعریف ساختار جدول، باید استاندارد تعریف ستون‌ها یا همان Header‌ها را رعایت کنید و از تگ‌های thead و th استفاده کنید.
نمونه ای از نحوه‌ی استفاده از آن را در کدهای زیر می‌بینید:
<!DOCTYPE html>
<html>
<head>
    <title>rtlColResizable Sample</title>
</head>
<body>
    <table id="myTable">
        <thead>
            <tr>
                <th>ستون1</th> <!-- نام ستون‌ها را حتما در این تگ باید تعریف کنید -->
                <th>ستون2</th>
                <th>ستون3</th>
            </tr>
        </thead>
        <tr>
            <td>داده مربوط به ستون 1</td>
            <td>داده مربوط به ستون 2 </td>
            <td>داده مربوط به ستون 3</td>
        </tr>
    </table>

    <script type="text/javascript" src="jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="jquery.rtlColResizable.js"></script>
    <script>

        $('table#myTable').rtlColResizable();

    </script>
</body>
</html>
     
دریافت نمونه کدی از نحوه‌ی استفاده از این افزونه
 
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها
برای این حالت خاص (تولید sequence بر اساس مقادیر یک ستون دیگر) باید از window functions استفاده کرد:
CREATE TABLE #Table1
(
   [Id] [int] IDENTITY(1,1) NOT NULL,
   [ForeignKeyId] [int] NOT NULL,
)

Insert Into #Table1 VALUES
(1), (1), (1), (2), (2), (2), (3), (3), (3)

Select [Id], [ForeignKeyId] ,
  ROW_NUMBER() OVER(PARTITION BY [ForeignKeyId] ORDER BY [ForeignKeyId]) [SomeSequence]
 from #Table1

Drop table #Table1
با این خروجی:


برای استفاده‌ی آن در EF Core، عبارت ذیل را
ROW_NUMBER() OVER(PARTITION BY [ForeignKeyId] ORDER BY [ForeignKeyId]) [SomeSequence]
مانند مثال HasComputedColumnSql ذکر شده‌ی در مطلب فوق، استفاده کنید.
نظرات مطالب
مفاهیم برنامه نویسی ـ آشنایی با سازنده‌ها
سلام/با تشکر از مطالب پایه ای و مفیدتون
کاربرد دو قطعه کد زیر در چه زمانی بهتر است؟

1.گرفتن مقدار مساحت به صورت یک Property‌‌‌
public double Area
{
        get { return _height * _baseLength * 0.5; }
}
2.محاسبه مساحت با استفاده از یک Method
public double Area()
{
      return _height * _baseLength * 0.5;
}
نظرات مطالب
بهینه سازی حجم فایل PDF تولیدی در حین کار با تصاویر در iTextSharp
مطلب فوق در مورد iTextSharp بود.
در PdfReport متد header.CacheHeader وجود دارد که کل هدر را کش می‌کند (حالت پیش فرض است) و اشیاء آن را یکبار محاسبه و به صفحات اضافه خواهد کرد (در آخرین نگارش موجود در SVN). به این ترتیب فرقی نمی‌کند که هدر سفارشی است یا هر نوع پیش فرض دیگری. برای تمام آن‌ها کش توکار اعمال خواهد شد.
اگر خواستید به ازای صفحات مختلف هدرهای مختلفی داشته باشید نیاز است header.CacheHeader را false کنید. در این حالت بهینه سازی صورت نخواهد گرفت.
نظرات مطالب
EF Code First #9
سنگینی یا سبکی یک کوئری بر اساس execution plan آن محاسبه می‌شود و نه حجم کوئری در notepad. بررسی این مورد هم نیاز به جزئیات دقیق کار شما دارد مانند ساختار کلاس‌ها و کوئری LINQ نوشته شده. (ضمن اینکه جوین 5 جدول با هم، با هر ابزاری کند است. این مورد ربطی به EF ندارد. باید طراحی کار خودتون رو بررسی و تصمیم گیری یا اصلاح کنید)
نظرات مطالب
EF Code First #12
خیر. به ازای هر SaveChanges یک تراکنش خاتمه یافته و تراکنش جدیدی آغاز می‌شود (این موارد رو می‌تونید با SQL Server Profiler دقیقا مشاهده کنید). 
+ ضرورتی ندارد در یک تراکنش، از چندین و چند SaveChanges استفاده کنید؛ از این جهت که EF از مکانیزم Tracking برخوردار است و می‌تواند با یک SaveChanges ، چندین و چند عملیات insert و update را (بهینه‌ترین حالتی را که محاسبه کرده) با هم در طی یک تراکنش بر اساس مواردی که ردیابی کرده، انجام دهد.