مطالب
C# 7 - Out variables
در زبان برنامه نویسی #C، هر متد، توانایی برگرداندن یک مقدار را دارد و در مواقعی نیاز داریم بیشتر از یک مقدار را از یک متد به عنوان خروجی دریافت کنیم که استفاده از پارامتر‌های out و  ref و یا Tuple راه حل‌هایی برای رسیدن به این مقصود می‌باشند.
یکی از تازه‌های 7 #C، ساده شدن تعریف out parameter‌ها نسبت به قبل می‌باشد که باعث مرتب شدن کدها نیز می‌شود که در ادامه به آن می‌پردازیم.
تا قبل از ارائه 7 #C، برای ارسال یک پارامتر به صورت out می‌بایستی قبل از استفاده و ارسال آن به متد مورد نظر، ابتدا متغیر مربوطه را تعریف می‌کردیم که نمونه‌ای از تعریف مذکور، قطعه کدهایی شبیه به زیر است:
int numericResult;
if (int.TryParse(input, out numericResult))
    WriteLine(numericResult);
else
    WriteLine("Could not parse input");
خروجی متد TryParse  به صورت boolean می‌باشد و در صورتیکه نتواند مقدار input را به int تبدیل کند، مقدار false برگردانده می‌شود؛ در غیر اینصورت مقدار true برگردانده خواهد شد و مقدار input در numericResult قرار می‌گیرد. همانطور که مشاهده می‌کنید برای اینکه بتوان مقدار تبدیل شده را داشته باشیم، ابتدا اقدام به تعریف یک متغیر کرده و بعد با استفاده از پارامتر out، آن را ارسال کرده‌ایم.
قطعه کد فوق در 7 #C به صورت زیر در خواهد آمد:
if (int.TryParse(input, out int result))
    WriteLine(result);
else
    WriteLine("Could not parse input");
در 7 #C امکان ترکیب تعریف پارمتر، به صورت inline در محل ارسال آرگومان وجود دارد و دیگر نیازی به تعریف متغیر، قبل از ارسال آن به تابع  نیست.

در مثال فوق نوع متغیر را به صورت صریح مشخص کرده‌ایم؛ همچنین امکان تعریف متغیر به صورت ضمنی را هم داریم:
if (int.TryParse(input, out var answer))
    WriteLine(answer);
else
    WriteLine("Could not parse input");

اولین سوالی که ممکن است با دیدن نمونه کد فوق پیش بیاید، محدوده‌ی متغیر‌های out که به صورت inline تعریف می‌شوند، می‌باشد و احتمالا تصور کرده‌اید که محدوده دید آنها  محدود به بلاک if می‌باشد؛ اما در عمل دامنه‌ی دید متغیر result برابر است با بلاک بیرونی شرط، که می‌توان از متغیر مورد نظر بعد از دستور if نیز استفاده نمود. قطعه کد زیر بیان کننده‌ی این موضوع می‌باشد:
if (!int.TryParse(input, out int result))
{    
    return null;
}

return result;


out variables را می‌توان یکی از ساده‌ترین تازه‌های 7 #C دانست که باعث خوانا‌تر شدن کدها می‌شود. در واقع دقیقا در جائیکه نیاز به متغیر است، تعریف می‌شود و اطمینان داریم که متغییر مربوطه قبل از ارسال به تابع، به صورت ناخواسته یا تصادفی مقداری اشتباه به آن انتساب داده نشده است.
 قبل از 7#C:
class Program
{
  static void Main(string[] args)
  {
    string firstName;
    string lastName;
    
    CreateName(out firstName, out lastName);
    Console.WriteLine($"Hello {firstName} {lastName}");
  }
  
  private static void CreateName(out string firstName, out string lastName)
  {
    firstName = "Kevin";
    lastName = "Griffin";
  }
}

و کد فوق در 7 #C  :
class Program
{
  static void Main(string[] args)
  {   
    CreateName(out string firstName, out string lastName);
    Console.WriteLine($"Hello {firstName} {lastName}");
  }
  
  private static void CreateName(out string firstName, out string lastName)
  {
    firstName = "Kevin";
    lastName = "Griffin";
  }
}
مطالب
ردیابی تغییرات در Entity Framework، بخش اول
همان طور که می‌دانید، Entity Framework  تغییراتی را که بر روی اشیا انجام می‌دهید، ردیابی می‌کند. بدیهی است که EF از طریق ردیابی این تغییرات است که می‌تواند تغییرات انجام شده را شناسایی کند و آن‌ها را در مواقع مورد نیاز مانند ذخیره‌ی تغییرات  (DbContext.SaveChanges)، بر روی پایگاه داده اعمال  کند. شما می‌توانید به اطلاعات این ردیاب تغییر و اعمال مرتبط به آن از طریق ویژگی  DbContext.ChangeTracker دسترسی پیدا کنید. 
در این مقاله بیشتر سعی به بررسی مفاهیم ردیابی و روش هایی که EF برای ردیابی تغییرات استفاده می‌کند، بسنده می‌کنم و بررسی  API‌های مختلف آن را به مقاله ای دیگر موکول می‌کنم.
به طور کلی EF از دو روش برای ردیابی تغییرات رخ داده شده در اشیا استفاده می‌کند:
1) ردیابی تغییر عکس فوری! (Snapshot change tracking) 
2) پروکسی‌های ردیابی تغییر  (Change tracking proxies)

 ردیابی تغییر عکس فوری

به نظر من، اسم مناسبی برای این روش انتخاب کرده اند و دقیقا بیان گر کاری است که  EF انجام می‌دهد. در حالت عادی کلاس‌های دامین ما یا همان کلاس‌های POCO، هیچ منطق و کدی را برای مطلع ساختن EF از تغییراتی که در آن‌ها رخ می‌دهد پیاده سازی نکرده اند. چون هیچ راهی برای EF، برای مطلع شدن از تغییرات رخ داده وجود ندارد، EF راه جالبی را بر می‌گزیند. EF هر گاه شیئی را می‌بیند از مقادیر ویژگی‌های آن یک عکس فوری می‌گیرد! و آن‌ها را در حافظه ذخیره می‌کند.این عمل هنگامی که یک شی از پرس و جو (query) حاصل می‌شود، و یا شیئی را به DbSet اضافه می‌کنیم رخ می‌دهد.
زمانی که EF می‌خواهد بفهمد که چه تغییراتی رخ داده است، مقادیر کنونی موجود در کلیه اشیا را اسکن می‌کند و با مقادیری که در عکس فوری ذخیره کرده است مقایسه می‌کند و متوجه تغییرات رخ داده می‌شود. این فرآیند اسکن کردن کلیه اشیا زمانی رخ می‌دهد که متد DetectChanges ویژگی DbSet.ChangeTracker صدا زده شود.

 پروکسی‌های ردیابی تغییر

پروکسی‌های ردیابی تغییر، مکانیزم دیگری برای ردیابی تغییرات EF است و به EF این اجازه را می‌دهد تا از تغییرات رخ داده، مطلع شود.
اگر به یاد داشته باشید در مباحث Lazy loading نیز از واژه پروکسی‌های پویا استفاده شد. پروکسی‌های ردیابی تغییر نیز با استفاده از همان مکانیزم کار می‌کنند و علاوه بر فراهم کردن Lazy loading ،این امکان را می‌دهند تا تغییرات را به Context انتقال دهند.
برای استفاده از پروکسی‌های ردیابی تغییر، شما باید ساختار کلاس‌های خود را به گونه ای تغییر دهید، تا EF بتواند در زمان اجرا، نوع پویایی را که هریک، از کلاس‌های POCO شما مشتق می‌شوند ایجاد کند، و تک تک ویژگی‌های آن‌ها را تحریف (override) کند.
این نوع پویا که به عنوان پروکسی پویا نیز شناخته می‌شود، منطقی را در ویژگی‌های تحریف شده شامل می‌شود، تا EF را از تغییرات صورت گرفته در ویژگی هایش مطلع سازد.
 برای بیان ادامه‌ی مطلب، من مدل یک دفترچه تلفن ساده را به شرح زیر در نظر گرفتم که روابط مهم و اساسی در آن در نظر گرفته شده است. 
namespace EntitySample1.DomainClasses
{
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public virtual PersonInfo PersonInfo { get; set; }
        public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
        public virtual ICollection<Address> Addresses { get; set; }
    }
}

namespace EntitySample1.DomainClasses
{
    public class PersonInfo
    {
        public int Id { get; set; }
        public string Note { get; set; }
        public string Major { get; set; }
    }
}

namespace EntitySample1.DomainClasses
{
    public enum PhoneType
    {
        Home,
        Mobile,
        Work
    }

    public class PhoneNumber
    {
        public int Id { get; set; }
        public string Number { get; set; }
        public PhoneType PhoneType { get; set; }
        public virtual Person Person { get; set; }
    }
}

namespace EntitySample1.DomainClasses
{
    public class Address
    {
        public int Id { get; set; }
        public string City { get; set; }
        public string Street { get; set; }
        public virtual ICollection<Person> Persons { get; set; }
    }
}
طبق کلاس‌های فوق ، ما تعدادی شخص ، اطلاعات شخص ، شماره تلفن و آدرس داریم. رابطه‌ی بین شخص و اطلاعات آن شخص یک به یک، شخص و آدرس  چند به چند  و شخص با شماره تلفن یک به چند است. همچنین به این نکته توجه داشته باشید که  کلیه کلاس‌های فوق به صورت public تعریف، و کلیه خواص راهبری (navigation properties) به صورت virtual تعریف شده اند. دلیل این کار هم این است که این دو مورد، جز الزامات، برای فعال سازی Lazy loading هستند. 
تعریف کلاس context نیز به شکل زیر است:

namespace EntitySample1.DataLayer
{
    public class PhoneBookDbContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }
        public DbSet<PhoneNumber> PhoneNumbers { get; set; }
        public DbSet<Address> Addresses { get; set; }

    }
}

استفاده از ردیابی تغییر عکس فوری

ردیابی تغییر عکس فوری، وابسته به این است که EF بفهمد، چه زمانی تغییرات رخ داده است. رفتار پیش فرض DbContext API ، این هست که به صورت خودکار بازرسی لازم را در نتیجه‌ی رخداد‌های DbContext انجام دهد. DetectChanges تنها اطلاعات مدیریت حالت context، که وظیفه‌ی انعکاس تغییرات صورت گرفته به پایگاه داده را دارد، به روز نمی‌کند، بلکه اصلاح رابطه(ralationship) ترکیبی از خواص راهبری مرجع ، مجموعه ای  و کلید‌های خارجی را انجام می‌دهد. این خیلی مهم خواهد بود که درک روشنی داشته باشیم از این که چگونه و چه زمانی تغییرات تشخیص داده می‌شوند،چه چیزی باید از آن انتظار داشته باشیم و چگونه کنترلش کنیم.

چه زمانی تشخیص خودکار تغییرات اجرا می‌شود؟

متد DetectChanges کلاس ObjectContext، از EF نسخه‌ی 4 به عنوان بخشی از الگوی ردیابی تغییر عکس فوری اشیای POCO ،در دسترس بوده است. تفاوتی که در مورد DataContext.ChangeTracker.DetectChanges( در حقیقت ObjectContext.DetectChanges فراخوانی می‌شود) وجود دارد این است که، رویداد‌های خیلی بیشتری وجود دارند که به صورت خودکار DetectChanges را فراخوانی می‌کنند.
لیستی از متدهایی که باعث انجام عمل تشخیص تغییرات (DetectChanges)، می‌شوند را  در ادامه مشاهده می‌کنید:
• DbSet.Add
• DbSet.Find
• DbSet.Remove
• DbSet.Local
• DbSet.SaveChanges
• فراخوانی Linq Query از DbSet
• DbSet.Attach
• DbContext.GetValidationErrors
• DbContext.Entry
• DbChangeTracker.Entries

 
کنترل زمان فراخوانی DetectChanges

بیشترین زمانی که EF احتیاج به فهمیدن تغییرات دارد، در زمان SaveChanges است، اما حالت‌های زیاد دیگری نیز هست. برای مثال، اگر ما از ردیاب تغییرات، درخواست وضعیت فعلی یک شی را بکنیم،EF احتیاج به اسکن کردن و بررسی تغییرات رخ داده را دارد. همچنین وضعیتی را در نظر بگیرید که شما از پایگاه داده یک شماره تلفن را واکشی می‌کنید و سپس آن را به مجموعه شماره تلفن‌های یک شخص جدید اضافه می‌کنید.آن شماره تلفن اکنون تغییر کرده است، چرا که انتساب آن به یک شخص جدید،خاصیت PersonId آن را تغییر داده است. ولی EF برای اینکه بفهمد تغییر رخ داده است(یا حتی نداده است) ، احتیاج به اسکن کردن همه‌ی اشیا Person دارد.
بیشتر عملیاتی که بر روی DbContext API انجام می‌دهید، موجب فراخوانی DetectChanges می‌شود. در بیشتر موارد DetectChanges به اندازه کافی سریع هست تا باعث ایجاد مشکل کارایی نشود. با این حال ممکن است ، شما تعداد خیلی زیادی اشیا در حافظه داشته باشید، و یا تعداد زیادی عملیات در DbContext ، در مدت خیلی کوتاهی انجام دهید، رفتار تشخیص خودکار تغییرات ممکن است، باعث نگرانی‌های کارایی شود. خوشبختانه گزینه ای برای خاموش کردن رفتار تشخیص خودکار تغییرات وجود دارد و هر زمانی که می‌دانید لازم است، می‌توانید آن را به صورت دستی فراخوانی کنید.
 EF بر مبنای این فرض ساخته شده است که شما ، در صورتی که در فراخوانی آخرین API، موجودیتی تغییر پیدا کرده است، قبل از فراخوانی API جدید، باید DetectChanges صدا زده شود. این شامل فراخوانی DetectChanges، قبل از اجرای هر query نیز می‌شود.اگر این عمل ناموفق یا نابجا انجام شود،ممکن است عواقب غیر منتظره ای در بر داشته باشد. DbContext انجام این وظیفه را بر عهده گرفته است و به همین دلیل به طور پیش فرض تشخیص تغییرات خودکار آن فعال است.

نکته:
تشخیص اینکه چه زمانی احتیاج به فراخوانی DetectChanges است،آن طور که ساده و بدیهی به نظر می‌آید نیست. تیم EF شدیدا توصیه کرده اند که فقط، وقتی با مشکلات عدم کارایی روبرو شدید، تشخیص تغییرات را به حالت دستی در بیاورید.همچنین توصیه شده که در چنین مواقعی، تشخیص خودکار تغییرات را فقط برای قسمتی از کد که با کارایی پایین مواجه شدید خاموش کنید و پس از اینکه اجرای آن قسمت از کد تمام شد،دوباره آن را روشن کنید.

برای خاموش یا روشن کردن تشخیص خودکار تغییرات، باید متغیر بولین DbContext.Configuration.AutoDetectChangesEnabled را تنظیم کنید.
در مثال زیر، ما در متد ManualDetectChanges، تشخیص خودکار تغییرات را خاموش کرده ایم و تاثیرات آن را بررسی کرده ایم.

        private static void ManualDetectChanges()
        {
            using (var context = new PhoneBookDbContext())
            {
                context.Configuration.AutoDetectChangesEnabled = false; // turn off Auto Detect Changes

                var p1 = context.Persons.Single(p => p.FirstName == "joe");

                p1.LastName = "Brown";

                Console.WriteLine("Before DetectChanges: {0}", context.Entry(p1).State);

                context.ChangeTracker.DetectChanges(); // call detect changes manually

                Console.WriteLine("After DetectChanges: {0}", context.Entry(p1).State);
            }
        }

در کدهای بالا ابتدا تشخیص خودکار تغییرات را خاموش کرده ایم و سپس یک شخص با نام joe را از دیتابیس فراخواندیم و سپس نام خانوادگی آن را به Brown تغییر دادیم. سپس در خط بعد، وضعیت فعلی موجودیت p1 را از context جاری پرسیدیم. در خط بعدی، DetectChanges را به صورت دستی صدا زده ایم و دوباره همان پروسه را برای به دست آوردن وضیعت شی p1، انجام داده ایم. همان طور که می‌بینید ، برای به دست آوردن وضعیت فعلی شی مورد نظر از متد Entry متعلق به ChangeTracker API استفاده می‌کنیم، که در آینده مفصل در مورد آن بحث خواهد شد. اگر شما متد Main را با صدا زدن ManualDetectChanges ویرایش کنید ، خروجی زیر را مشاهده خواهید کرد:

Before DetectChanges: Unchanged
After DetectChanges: Modified

همان طور که انتظار می‌رفت، به دلیل خاموش کردن تشخیص خودکار تغییرات، context قادر به تشخیص تغییرات صورت گرفته در شی p1 نیست، تا زمانی که متد DetectChanges را به صورت دستی صدا بزنیم. دلیل این که در دفعه اول، ما نتیجه‌ی غلطی مشاهده می‌کنیم، این است که ما قانون را نقض کرده ایم و قبل از صدا زدن هر API ، متد DetectChanges را صدا نزده ایم. خوشبختانه چون ما در اینجا وضعیت یک شی را بررسی کردیم، با عوارض جانبی آن روبرو نشدیم.

نکته: به این نکته توجه داشته باشید که متد Entry به صورت خودکار، DetectChanges را فراخوانی می‌کند. برای اینکه دانسته بخواهیم این رفتار را غیر فعال کنیم، باید AutoDetectChangesEnabled را غیر فعال کنیم.
در مثال فوق ،خاموش کردن تشخیص خودکار تغییرات، برای ما مزیتی به همراه نداشت و حتی ممکن بود برای ما دردسر ساز شود. ولی حالتی را  در نظر بگیرید که ما یک سری API را فراخوانی می‌کنیم ،بدون این که در این بین ،در حالت اشیا تغییری ایجاد کنیم.در نتیجه می‌توانیم از فراخوانی‌های بی جهت DetectChanges جلوگیری کنیم.
 
در متد AddMultiplePersons مثال بعدی، این کار را نشان داده ام:

        private static void AddMultiplePerson()
        {
            using (var context = new PhoneBookDbContext())
            {
                context.Configuration.AutoDetectChangesEnabled = false;

                context.Persons.Add(new Person
                    {
                        FirstName = "brad",
                        LastName = "watson",
                        BirthDate = new DateTime(1990, 6, 8)
                    });

                context.Persons.Add(new Person
                {
                    FirstName = "david",
                    LastName = "brown",
                    BirthDate = new DateTime(1990, 6, 8)
                });

                context.Persons.Add(new Person
                {
                    FirstName = "will",
                    LastName = "smith",
                    BirthDate = new DateTime(1990, 6, 8)
                });

                context.SaveChanges();

            }
        }
در مثال بالا ما از فراخوانی چهار DetectChanges غیر ضروری که شامل DbSet.Add و SaveChanges می‌شود، جلوگیری کرده ایم.

استفاده از DetectChanges برای فراخوانی اصلاح رابطه


DetectChanges همچنین مسئولیت انجام اصلاح رابطه ، برای هر رابطه ای که تشخیص دهد تغییر کرده است را دارد.اگر شما بعضی از روابط را تغییر دادید و مایل بودید تا همه‌ی خواص راهبری و خواص کلید خارجی را منطبق کنید، DetectChanges این کار را برای شما انجام می‌دهد. این قابلیت می‌تواند برای سناریوهای data-binding که در آن ممکن است در رابط کاربری(UI) یکی از خواص راهبری (یا حتی یک کلید خارجی) تغییر کند، و شما بخواهید که خواص دیگری این رابطه به روز شوند و تغییرات را نشان دهند، مفید واقع شود.
متد DetectRelationshipChanges در مثال زیر از DetectChanges برای انجام اصلاح رابطه استفاده می‌کند.
 
        private static void DetectRelationshipChanges()
        {
            using (var context = new PhoneBookDbContext())
            {

                var phone1 = context.PhoneNumbers.Single(x => x.Number == "09351234567");

                var person1 = context.Persons.Single(x => x.FirstName == "will");

                person1.PhoneNumbers.Add(phone1);

                Console.WriteLine("Before DetectChanges: {0}", phone1.Person.FirstName);

                context.ChangeTracker.DetectChanges(); // ralationships fix-up

                Console.WriteLine("After DetectChanges: {0}", phone1.Person.FirstName);
            }
        }

در اینجا ابتدا ما شماره تلفنی را از دیتابیس لود می‌کنیم. سپس شخص دیگری را نیز با نام will از دیتابیس می‌خوانیم. قصد داریم شماره تلفن خوانده شده را به این شخص نسبت دهیم و مجموعه شماره تلفن‌های وی اضافه کنیم و ما این کار را با افزودن phone1 به مجموعه شماره تلفن‌های person1 انجام داده ایم. چون ما از اشیای POCO استفاده کرده ایم،EF  نمی‌فهمد که ما این تغییر را ایجاد کرده ایم و در نتیجه کلید خارجی PersonId شی phone1 را اصلاح نمی‌کند. ما می‌توانیم تا زمانی صبر کنیم تا متدی مثل SaveChanges، متد DetectChanges را فراخوانی کند،ولی اگر بخواهیم این عمل در همان لحظه انجام شود، می‌توانیم DetectChanges را دستی صدا بزنیم.

اگر ما متد Main را با اضافه کردن فرخوانی DetectRealtionShipsChanges تغییر بدهیم و آن را اجرا کنیم، نتیجه زیر را مشاهده می‌کنید:

Before DetectChanges: david
After DetectChanges: will

تا قبل از فراخوانی تشخیص تغییرات(DetectChanegs)، هنوز phone1 منتسب به شخص قدیمی(david) بوده، ولی پس از فراخوانی DetectChanges ، اصلاح رابطه رخ داده و همه  چیز با یکدیگر منطبق می‌شوند.

فعال سازی و کار با پروکسی‌های ردیابی تغییر

اگر پروفایلر کارایی شما، فراخوانی‌های بیش از اندازه DetectChnages را به عنوان یک مشکل شناسایی کند، و یا شما ترجیح می‌دهید که اصلاح رابطه به صورت بلادرنگ صورت گیرد ، ردیابی تغییر پروکسی‌های پویا، به عنوان گزینه ای دیگر مطرح می‌شود.فقط با چند تغییر کوچک در کلاس‌های POCO، EF قادر به ساخت پروکسی‌های پویا خواهد بود.پروکسی‌های ردیابی تغییر به EF اجازه ردیابی تغییرات در همان لحظه ای که ما تغییری در اشیای خود می‌دهیم را می‌دهند و همچنین امکان انجام اصلاح رابطه را در هر زمانی که تغییرات روابط را تشخیص دهد، دارد.
برای اینکه پروکسی ردیابی تغییر بتواند ساخته شود، باید قوانین زیر رعایت شود:
 
• کلاس باید public باشد و seald نباشد.
• همه‌ی خواص(properties) باید virtual تعریف شوند.
• همه‌ی خواص باید getter و setter با سطح دسترسی public داشته باشند.
• همه‌ی خواص راهبری مجموعه ای باید نوعشان، از نوع ICollection<T> تعریف شوند.


کلاس Person مثال خود را به گونه ای بازنویسی کرده ایم که تمام قوانین فوق را پیاده سازی کرده باشد.

نکته: توجه داشته باشید که ما دیگر در داخل سازنده کلاس ،کدی نمی‌نویسیم و منطقی  که باعث نمونه سازی اولیه خواص راهبری می‌شدند، را پیاده سازی نمی‌کنیم. این پروکسی ردیاب تغییر، همه‌ی خواص راهبری مجموعه ای را تحریف کرده و ار نوع مجموعه ای مخصوص خود(EntityCollection<T>) استفاده می‌کند. این نوع مجموعه ای، هر تغییری که در این مجموعه صورت می‌گیرد را زیر نظر گرفته و به ردیاب تغییر گزارش می‌دهد. اگر تلاش کنید تا نوع دیگری مانند List<T> که معمولا در سازنده کلاس از آن استفاده می‌کردیم را به آن انتساب دهیم، پروکسی، استثنایی را پرتاب می‌کند.

namespace EntitySample1.DomainClasses
{
    public class Person
    {
        public virtual int Id { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual DateTime BirthDate { get; set; }
        public virtual PersonInfo PersonInfo { get; set; }
        public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
        public virtual ICollection<Address> Addresses { get; set; }
    }
}
همان طور که در مباحث مربوط به Lazy loading نیز مشاهده کردید،EF زمانی پروکسی‌های پویا را برای یک کلاس ایجاد می‌کند که یک یا چند خاصیت راهبری آن با virtual علامت گذاری شده باشند.آن پروکسی‌ها که از کلاس مورد نظر، مشتق شده اند، به خواص راهبری virtual امکان می‌دهند تا به صورت lazy لود شوند.پروکسی‌های ردیابی تغییر نیز به همان شکل در زمان اجرا ایجاد می‌شوند، با این تفاوت که این پروکسی ها، امکانات بیشتری دارند.
با این که احتیاجات رسیدن به پروکسی‌های ردیابی تغییر خیلی ساده هستند، اما ساده‌تر از آن ها، فراموش کردن یکی از آن هاست.حتی از این هم ساده‌تر می‌شود که در آینده تغییری در آن کلاس‌ها ایجاد کنید و ناخواسته یکی از آن قوانین را نقض کنید.به این خاطر، فکر خوبیست که یک آزمون واحد نیز اضافه کنیم تا مطمئن شویم که EF توانسته، پروکسی ردیابی تغییر را ایجاد کند یا نه.
در مثال زیر یک متد نوشته شده که این مورد را مورد آزمایش قرار می‌دهد. همچنین فراموش نکنید که فضای نام System.Data.Object.DataClasses را به using‌های خود اضافه کنید.
 
        private static void TestForChangeTrackingProxy()
        {
            using (var context = new PhoneBookDbContext())
            {
                var person = context.Persons.First();
                var isProxy = person is IEntityWithChangeTracker;
                Console.WriteLine("person is a proxy: {0}", isProxy);
            }
        }
زمانی که EF ، پروکسی پویا برای ردیابی تغییر ایجاد می‌کند، اینترفیس IentityWithChangeTrackerرا پیاده سازی  خواهد کرد.متد تست در مثال بالا، نمونه ای از Person را با دریافت آن از دیتابیس ایجاد می‌کند و سپس آن را با اینترفیس ذکر شده چک می‌کند تا مطمئن شود که Person ، توسط پروکسی ردیابی تغییر احاطه شده است. این نکته را نیز به یاد داشته باشید که چک کردن این که EF ، کلاس پروکسی ای که از کلاس ما مشتق شده است ایجاد کرده است یا نه،کفایت نمی‌کند، چرا که پروکسی‌های Lazy Loading نیز چنین کاری انجام می‌دهند. در حقیقت آن چیزی که سبب می‌شود EF به تغییرات صورت گرفته به صورت بلادرنگ گوش دهد،حضور IEntityWithChangeTracker است.
اکنون متد ManualDetectChanges را که کمی بالاتر بررسی کرده ایم را در نظر بگیرید و کد context.ChangeTracker.DetectChanges آن را حذف کنید و بار دیگر آن را فرا بخوانید  و نتیجه را مشاهده کنید:
 
Before DetectChanges: Modified
After DetectChanges: Modified
این دفعه،EF از تغییرات صورت گرفته آگاه است،حال چه DetectChanges فراخوانده شود یا نشود.

اکنون متد DetectRelationshipChanges را ویرایش کرده و برنامه را اجرا کنید:

Before DetectChanges: will
After DetectChanges: will
این بار می‌بینیم که EF، تغییر رابطه را تشخیص داده و اصلاح رابطه را بدون فراخوانی DetectChanges انجام داده است.

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

تذکر: اگر شما موجودیت هایی داشته باشید که شامل انواع پیچیده(Complex Types) می‌شوند،EF هنوز هم از ردیابی تغییر عکس فوری، برای خواص موجود در نوع پیچیده استفاده می‌کند، و از این جهت لازم است کهEF، برای نمونه‌ی نوع پیچیده، پروکسی ایجاد نمی‌کند.شما هنوز هم، تشخیص خودکار تغییرات خواصی که مستقیما درون آن موجودیت(Entity) تعریف شده اند را دارید، ولی تغییرات رخ داده درون نوع پیچیده، فقط از طریق DetectChanges قابل تشخیص است.

چگونگی اطمینان از اینکه نمونه‌های جدید ، پروکسی‌ها را دریافت خواهند کرد

EF به صورت خودکار برای نتایج حاصل از کوئری هایی که شما اجرا می‌کنید، پروکسی‌ها را ایجاد می‌کند. با این حال اگر شما فقط از سازنده‌ی کلاس POCO خود برای ایجاد نمونه‌ی جدید استفاده کنید،دیگر پروکسی‌ها ایجاد نخواهند شد.بدین منظور برای دریافت پروکسی ها، شما باید از متد DbSet.Create برای دریافت نمونه‌های جدید آن موجودیت استفاده کنید.
نکته: اگر شما، پروکسی‌های ردیابی تغییر را برای موجودیتی از مدلتان فعال کرده باشید،هنوز هم می‌توانید،نمونه‌های فاقد پروکسی آن موجودیت را ایجاد و بیافزایید.خوشبختانه EF با موجودیت‌های پروکسی و غیر پروکسی در همان مجموعه(set) کار می‌کند.شما باید آگاه باشید که ردیابی خودکار تغییرات و یا اصلاح رابطه، برای نمونه هایی که پروکسی هایی ردیابی تغییر نیستند، قابل استفاده نیستند.داشتن مخلوطی از نمونه‌های پروکسی و غیر پروکسی در همان مجموعه، می‌تواند گیج کننده باشد.بنابر این عموما توصیه می‌شود که برای ایجاد نمونه‌های جدید از DbSet.Create استفاده کنید، تا همه‌ی موجودیت‌های موجود در مجموعه، پروکسی‌های ردیابی تغییر باشند.
متد CreateNewProxies را به برنامه‌ی خود اضافه کرده و آن را اجرا کنید.

        private static void CreateNewProxies()
        {
            using (var context = new PhoneBookDbContext())
            {
                var phoneNumber = new PhoneNumber { Number = "987" };

                var davidPersonProxy = context.Persons.Create();
                davidPersonProxy.FirstName = "david";
                davidPersonProxy.PhoneNumbers.Add(phoneNumber);

                Console.WriteLine(phoneNumber.Person.FirstName);
            }
        }

خروجی مثال فوق david خواهد بود.همان طور که می‌بینید با استفاده از context.Persons.Create، نمونه‌ی ساخته شده، دیگر شی POCO نیست، بلکه davidPersonProxy، از جنس پروکسی ردیابی تغییر است و تغییرات آن به طور خودکار ردیابی شده و رابطه آن نیز به صورت خودکار اصلاح  می‌شود.در اینجا نیز با افزودن phoneNumber به شماره تلفن‌های davidPersonProxy، به طور خودکار رابطه‌ی بین phoneNumber و davidPersonPeroxy بر قرار شده است. همان طور که می‌دانید این عملیات بدون استفاده از پروکسی‌های ردیابی تغییرات امکان پذیر نیست و موجب بروز خطا می‌شود.


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

اورلود جنریک دیگری برای DbSet.Create وجود  دارد که برای نمونه سازی کلاس‌های مشتق شده در مجموعه ما استفاده می‌شود .برای مثال، فراخوانی Create بر روی مجموعه‌ی Persons ،نمونه ای از کلاس Person را  بر می‌گرداند.ولی ممکن است کلاس هایی در مجموعه‌ی Persons وجود داشته باشند، که از آن مشتق شده باشند، مانند Student. برای دریافت نمونه‌ی پروکسی Student، از اورلود جنریک Create استفاده می‌کنیم.

var newStudent = context.Persons.Create<Student>();
واکشی موجودیت‌ها بدون ردیابی تغییرات

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

متد PrintPersonsWithoutChangeTracking را به برنامه اضافه کنید و آن را اجرا کنید: 
 
        private static void PrintPersonsWithoutChangeTracking()
        {
            using (var context = new PhoneBookDbContext())
            {
                var persons = context.Persons.AsNoTracking().ToList();

                foreach (var person in persons)
                {
                    Console.WriteLine(person.FirstName);
                }

            }
        }
در مثال بالا از متد AsNoTracking برای گرفتن کوئری فاقد ردیابی استفاده کردیم تا محتویات مجموعه Persons را دریافت کنیم. در نهایت با یک حلقه‌ی foreach، نتایج را بر روی کنسول به نمایش در آوردیم.به دلیل اینکه، این یک کوئری بدون ردیابی هست، context دیگر تغییراتی که روی Persons رخ می‌دهد را ردیابی نمی‌کند.در نتیجه اگر شما یکی از خواص یکی از Persons را تغییردهید و SaveChanges را صدا بزنید، تغییرات به دیتابیس ارسال نمی‌شوند.

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

AsNoTracking یک متد الحاقی است، که در <IQueryable<T تعریف شده است، در نتیجه شما می‌توانید از آن، در کوئری‌های LINQ نیز استفاده کنید. شما می‌توانید از AsNoTracking، در انتهای DbSet ،در خط from کوئری استفاده کنید.

var query = from p in context.Persons.AsNoTracking()
                            where p.FirstName == "joe"
                            select p;

شما همچنین از AsNoTracking می‌توانید برای تبدیل یک کوئری LINQ موجود، به یک کوئری فاقد ردیابی استفاده کنید. این نکته را به یاد داشته باشید که فقط AsNoTracking بر روی کوئری، فرانخوانده شده است، بلکه متغیر query را با نتیجه‌ی حاصل از فراخوانی AsNoTracking بازنویسی(override)  کرده است و این، از این جهت لازم است که AsNoTracking ،تغییری در کوئری ای که بر روی آن فراخوانده شده نمی‌دهد، بلکه یک کوئری جدید بر می‌گرداند. 

 
                var query = from p in context.Persons
                            where p.FirstName == "joe"
                            select p;
                query = query.AsNoTracking();
نکته: به دلیل اینکه AsNoTracking یک متد الحاقی است، شما احتیاج به افزودن فضای نام System.Data.Entity به فضا‌های نام خود دارید.

منبع: ترجمه ای آزاد از کتاب Programming Entity Framework: DbContext
مطالب
PowerShell 7.x - قسمت چهارم - نوشتن اولین اسکریپت
دستوراتی که درون کنسول مینویسیم، تک خطی یا one-linear هستند؛ هر چند میتوان با زدن کلیدهای Shift + Enter دستورات چندخطی هم نوشت یا حتی با گذاشتن semicolon بعد از هر دستور میتوانیم دریک خط چندین دستور را پشت‌سر هم بنویسیم. اما برای نوشتن دستورات طولانی‌تر بهتر است دستورات را درون فایل‌های جدایی قرار دهیم و از VSCode یا PowerShell ISE (فقط در ویندوز) نیز برای نوشتن اسکریپت‌ها استفاده کرد. اسکریپت‌های PowerShell با پسوند ps1 و psm1 (برای نوشتن ماژول) هستند؛ هر چند چندین پسوند دیگر نیز برای فایل‌های PowerShell وجود دارند که در اینجا میتوانید لیست آنها را مشاهده کنید. درون یک فایل ps1 امکان نوشتن و ترکیب دستورات مختلف را داریم. همچنین میتوانیم از امکانات زبان سی‌شارپ هم استفاده کنیم؛ زیرا PowerShell در واقع اپلیکیشنی است که توسط NET Core. و با زبان #C نوشته شده‌است. در نتیجه میتوانیم بگوئیم زبان اسکریپتی که در PowerShell استفاده میشود، یک DSL برای زبان #C است. در PowerShell همه چیز یک آبجکت محسوب میشود. برای تست این مورد میتوانید درون کنسول PowerShell دستور زیر را وارد کنید:
PS> "" | Get-Member
دستور فوق یک لیست از تمامی توابع و پراپرتی‌های نوع System.String را نمایش خواهد داد:
   TypeName: System.String

Name                 MemberType            Definition
----                 ----------            ----------
Clone                Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo            Method                int CompareTo(System.Object value), int CompareTo(strin…
Contains             Method                bool Contains(string value), bool Contains(string value…
CopyTo               Method                void CopyTo(int sourceIndex, char[] destination, int de…
EndsWith             Method                bool EndsWith(string value), bool EndsWith(string value…
EnumerateRunes       Method                System.Text.StringRuneEnumerator EnumerateRunes()
Equals               Method                bool Equals(System.Object obj), bool Equals(string valu…
GetEnumerator        Method                System.CharEnumerator GetEnumerator(), System.Collectio…
GetHashCode          Method                int GetHashCode(), int GetHashCode(System.StringCompari…
GetPinnableReference Method                System.Char&, System.Private.CoreLib, Version=6.0.0.0, …
GetType              Method                type GetType()
GetTypeCode          Method                System.TypeCode GetTypeCode(), System.TypeCode IConvert…
IndexOf              Method                int IndexOf(char value), int IndexOf(char value, int st…
IndexOfAny           Method                int IndexOfAny(char[] anyOf), int IndexOfAny(char[] any…
Insert               Method                string Insert(int startIndex, string value)
IsNormalized         Method                bool IsNormalized(), bool IsNormalized(System.Text.Norm…
LastIndexOf          Method                int LastIndexOf(string value, int startIndex), int Last…
LastIndexOfAny       Method                int LastIndexOfAny(char[] anyOf), int LastIndexOfAny(ch…
Normalize            Method                string Normalize(), string Normalize(System.Text.Normal…
PadLeft              Method                string PadLeft(int totalWidth), string PadLeft(int tota…
PadRight             Method                string PadRight(int totalWidth), string PadRight(int to…
Remove               Method                string Remove(int startIndex, int count), string Remove…
Replace              Method                string Replace(string oldValue, string newValue, bool i…
ReplaceLineEndings   Method                string ReplaceLineEndings(), string ReplaceLineEndings(…
Split                Method                string[] Split(char separator, System.StringSplitOption…
StartsWith           Method                bool StartsWith(string value), bool StartsWith(string v…
Substring            Method                string Substring(int startIndex), string Substring(int …
ToBoolean            Method                bool IConvertible.ToBoolean(System.IFormatProvider prov…
ToByte               Method                byte IConvertible.ToByte(System.IFormatProvider provide…
ToChar               Method                char IConvertible.ToChar(System.IFormatProvider provide…
ToCharArray          Method                char[] ToCharArray(), char[] ToCharArray(int startIndex…
ToDateTime           Method                datetime IConvertible.ToDateTime(System.IFormatProvider…
ToDecimal            Method                decimal IConvertible.ToDecimal(System.IFormatProvider p…
ToDouble             Method                double IConvertible.ToDouble(System.IFormatProvider pro…
ToInt16              Method                short IConvertible.ToInt16(System.IFormatProvider provi…
ToInt32              Method                int IConvertible.ToInt32(System.IFormatProvider provide…
ToInt64              Method                long IConvertible.ToInt64(System.IFormatProvider provid…
ToLower              Method                string ToLower(), string ToLower(cultureinfo culture)
ToLowerInvariant     Method                string ToLowerInvariant()
ToSByte              Method                sbyte IConvertible.ToSByte(System.IFormatProvider provi…
ToSingle             Method                float IConvertible.ToSingle(System.IFormatProvider prov…
ToString             Method                string ToString(), string ToString(System.IFormatProvid…
ToType               Method                System.Object IConvertible.ToType(type conversionType, …
ToUInt16             Method                ushort IConvertible.ToUInt16(System.IFormatProvider pro…
ToUInt32             Method                uint IConvertible.ToUInt32(System.IFormatProvider provi…
ToUInt64             Method                ulong IConvertible.ToUInt64(System.IFormatProvider prov…
ToUpper              Method                string ToUpper(), string ToUpper(cultureinfo culture)
ToUpperInvariant     Method                string ToUpperInvariant()
Trim                 Method                string Trim(), string Trim(char trimChar), string Trim(…
TrimEnd              Method                string TrimEnd(), string TrimEnd(char trimChar), string…
TrimStart            Method                string TrimStart(), string TrimStart(char trimChar), st…
TryCopyTo            Method                bool TryCopyTo(System.Span[char] destination)
Chars                ParameterizedProperty char Chars(int index) {get;}
Length               Property              int Length {get;}
در واقع میتوانیم بگوئیم هرچیزی در PowerShell یک آبجکت NET. است. در ادامه لیستی را از قابلیت‌های PowerShell به عنوان یک زبان اسکریپتی، بررسی خواهیم کرد.

تعریف متغیر
برای تعریف یک متغیر از علامت $ قبل از نام متغیر استفاده میکنیم. نوع متغیر نیز براساس مقداری که به آن انتساب داده میشود، تعیین خواهد شد: 
$stringVariable = "Hello World"
$letter = 'A'
$isEnabled = $false
$age = 33
$height = 76
$doubleVar = 54321.21
$singleVar = 76549.11
$longVar = 2382.22
$dateVar = "July 24, 1986"
$arrayVar = "A", "B", "C"
$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
همچنین میتوانیم نوع متغیر را نیز به صورت صریح تعیین کنیم: 
[string]$stringVariable = "Hello World"
[char]$letter = 'A'
[bool]$isEnabled = $false
[int]$age = 33
[decimal]$height = 76
[double]$doubleVar = 54321.21
[single]$singleVar = 76549.11
[long]$longVar = 2382.22
[DateTime]$dateVar = "July 24, 1986"
[array]$arrayVar = "A", "B", "C"
[hashtable]$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
لازم به ذکر است scope متغیرها در حالت پیش‌فرض به local تنظیم میشود. به این معنا که در جایی که تعریف میشوند، قابل دسترسی خواهند بود. قاعدتاً اگر متغیرها را در ابتدای اسکریپت تعریف کنید، میدان دید آن به صورت سراسری خواهد بود و در هرجایی از کد در دسترس خواهند بود. اما برای اینکه به صورت صریح یک متغیر را به صورت سراسری تعریف کنیم میتوانیم از کلمه‌کلیدی global بعد از تعیین نوع متغیر و قبل از علامت $ استفاده کنیم: 
$global:stringVariable = "Hello World"

// Or

[string]$global:stringVariable = "Hello World"

عبارات شرطی
همانند دیگر زبان‌های اسکریپتی، در PowerShell نیز قابلیت تعریف ساختارهای شرطی وجود دارد: 
$guess = 20
switch ($guess) {
    {$_ -eq 20} { Write-Host "You guessed right!" }
    {$_ -gt 20} { Write-Host "You guessed too high!" }
    {$_ -lt 20} { Write-Host "You guessed too low!" }
    default { Write-Host "You didn't guess a number!" }
}

$guess = 20
if ($guess -eq 20) { 
    Write-Host "You guessed right!" 
}
elseif ($guess -gt 20) { 
    Write-Host "You guessed too high!" 
}
elseif ($guess -lt 20) { 
    Write-Host "You guessed too low!" 
}
else { 
  Write-Host "You didn't guess a number!"
}
همانطور که مشاهده میکنید از یکسری اپراتور برای بررسی شرط‌ها استفاده شده است. در اینجا میتوانید لیست کامل آنها را مشاهده کنید. لازم به ذکر است که از PowerShell 7.0 به بعد نیز Ternary Operator اضافه شده است: 
$message = (Test-Path $path) ? "Path exists" : "Path not found"

حلقه‌ها
همچنین از حالت‌های مختلف loop نیز پشتیبانی میشود: 
[int]$num = 10
for ($i = 1; $i -le $num; $i++) {
    Write-Host "`n"

    for ($j = 1; $j -le $num; $j++) {
        Write-Host -NoNewline -ForegroundColor Green ($i * $j).ToString().PadLeft(6)
    }
}
Write-Host "`n`n"

[int]$counter = 1
while ($counter -le 10) {
    Write-Host "Hello World"
    $counter++
}

do {
    Write-Host "Hello World"
    $counter++
} while ($counter -le 10)

foreach ($i in 1..10) {
    Write-Host "Hello World"
}

$items = "Hello", "World"
$items | ForEach-Object {
    Write-Host $_
}

لازم به ذکر است برای ForEach-Object از % نیز میتوانید استفاده کنید. اما بطور کلی بهتر است تا حد امکان از aliaseها استفاده نکنید؛ زیرا خوانایی کد را مقداری سخت میکند. این مورد توسط خود VSCode هم هشدار داده میشود: 


آرایه‌ها
در PowerShell به صورت پیش‌فرض آرایه‌ها از نوع []System.Object در نظر گرفته میشوند. در اینجا نیز آرایه‌ها immutable هستند. چندین روش برای ایجاد آرایه وجود دارد. در ادامه یک آرایه خالی را تعریف کرده‌ایم: 
$array = @()
یک روش دیگر تعریف آرایه اینگونه است: 
$myArray = [Object[]]::new(10)
$byteArray = [Byte[]]::new(100)
$ipAddresses = [IPAddress[]]::new(5)

$mylist = [System.Collections.Generic.List[int]]::new()
از PowerShell 5.0 به بعد میتوانیم سینتکس namespaceها را با کمک using خلاصه‌تر بنویسیم:
using namespace System.Collections.Generic
$mylist = [List[int]]::new()
از range operator هم میتوانیم برای مقداردهی یک آرایه استفاده کرد: 
$numbers = 1..10
$alphabet = "a".."z"

foreach ($item in 1..10) {
    Write-Host "Hello World"
}
برای دسترسی به عناصر یک آرایه نیز میتوانیم از range operator استفاده کنیم: 
$numbers = 1..10

$numbers
Write-Output "".PadRight(10, "-")
$numbers[1..2 + 2..4]
همانطور که مشاهده میکنید از اپراتور + برای append کردن یک بازه از اعداد، به آرایه استفاده کرده‌ایم. دقت داشته باشید که با هر بار اضافه/حذف آیتم‌ها، یک آرایه جدید ساخته میشود. این مورد در آرایه‌هایی با سایز بزرگ میتواند مشکل کارآیی ایجاد کند. بنابراین اگر عملیات اضافه کردن و حذف کردن از یک آرایه را زیاد انجام میدهید، بهتر است از ArrayList استفاده کنید. تفاوت آن نیز این است که برخلاف آرایه‌های عادی، سایز ArrayList ثابت نیست. تعریف ArrayList نیز اینگونه است: 
$colours = [System.Collections.ArrayList]@("Red", "Green", "Blue")
$colours.Add("Yellow")
$colours.Remove("Red")
از دیگر کالکشن‌های NET. نیز میتوانید استفاده کنید؛ به عنوان مثال در مثال زیر از List برای اضافه کردن ده هزار آیتم استفاده کرده‌ایم:
using namespace System.Collections.Generic

$mylist = [List[int]]::new()
Measure-Command { 1..100000 | ForEach-Object { $mylist.Add($_) } }
Measure-Command { $mylist.Where({ $_ -gt 10000 }) }
Measure-Command { $mylist.Contains(10000) }
نکته: کد فوق در حالت استفاده از ArrayList مقداری کندتر است. دلیل آن نیز این است که ArrayList امکان اضافه کردن هر آبجکتی را به ما میدهد. در نتیجه موقع جستجو باید یکبار عملیات unboxing را برای تشخیص نوع درخواست شده انجام دهد.

Hashtable
ساختار دیگری که میتوانید استفاده کنید Hash Tableها هستند؛ از این ساختار برای ایجاد custom objects و همچنین پاس دادن پارامترها به یک command استفاده میشود:
$sensors = @{
    tempreture = "Temperature"
    humidity   = "Humidity"
    pressure   = "Pressure"
    light      = "Light"
    noise      = "Noise"
    co2        = "CO2"
    battery    = "Battery"
    min_temp   = "Min Temp"
    max_temp   = "Max Temp"
}
با دستور زیر میتوانید لیستی از Commandهایی که ورودی‌شان از نوع Hash table است را مشاهده کنید:
PS> Get-Command -ParameterType Hashtable


CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        TabExpansion2                                                 
Cmdlet          Add-Member                                         7.0.0.0    Microsoft.Powe…
Cmdlet          ConvertTo-Html                                     7.0.0.0    Microsoft.Powe…
Cmdlet          Get-Job                                            7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-Command                                     7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-RestMethod                                  7.0.0.0    Microsoft.Powe…
Cmdlet          Invoke-WebRequest                                  7.0.0.0    Microsoft.Powe…
Cmdlet          New-Object                                         7.0.0.0    Microsoft.Powe…
Cmdlet          New-PSRoleCapabilityFile                           7.2.7.500  Microsoft.Powe…
Cmdlet          New-PSSession                                      7.2.7.500  Microsoft.Powe…
Cmdlet          Remove-Job                                         7.2.7.500  Microsoft.Powe…
Cmdlet          Select-Xml                                         7.0.0.0    Microsoft.Powe…
Cmdlet          Set-PSReadLineOption                               2.1.0      PSReadLine
Cmdlet          Stop-Job                                           7.2.7.500  Microsoft.Powe…
Cmdlet          Wait-Job                                           7.2.7.500  Microsoft.Powe…

توابع
برای ایجاد توابع در PowerShell میتوانید از سینتکس زیر استفاده کنید:
function Write-HelloWorld {
    Write-Host "Hello World"
}
برای نامگذاری توابع بهتر است از قالب verb-noun استفاده کنید. برای قسمت verb نیز بهتر است از یکسری افعال تائیدشده (approved verbs) که مستندات مایکروسافت پیشنهاد میدهد استفاده کنید (+) در این زمینه VSCode در صورت انتخاب یک نام نامناسب به شما هشدار خواهد:

تفاوت بین Function و Cmdlet چیست؟
توابع و cmdletها عملاً از لحاظ کاربرد باهم تفاوتی ندارند. تنها تفاوت آنها در نحوه ساخت‌شان است. cmdletها با #C ساخته میشوند. به این معنا که یکسری DLL کامپایل شده هستند که در نهایت از آنها استفاده خواهیم کرد. اما توابع در PowerShell با کمک سینتکسی که اشاره شد ایجاد میشوند. توسط دستور زیر میتوانیم لیستی از توابعی را که درون سشن PowerShellمان بارگذاری شده‌اند، ببینیم: 
PS> Get-Command -CommandType Function

در PowerShell نیز توابع قابلیت دریافت ورودی را نیز دارند. در ساده‌ترین حالت میتوانیم از آرایه args$ استفاده کنیم؛ دقیقاً چیزی مشابه arguments در JavaScript است:
function Write-HelloWorld {
    Write-Host "First Argument: $($args[0])"
    Write-Host "Second Argument: $($args[1])"
    Write-Host "Third Argument: $($args[2])"
    Write-Host "Fourth Argument: $($args[3])"
}
به این حالت Positional Parameters گفته میشود. یک روش دیگر تعریف پارامتر استفاده از Named Parameters است:
function Write-HelloWorld(
    [string]$first,
    [string]$second,
    [string]$third,
    [string]$fourth
) {
    Write-Host "First Argument: $($first)"
    Write-Host "Second Argument: $($second)"
    Write-Host "Third Argument: $($third)"
    Write-Host "Fourth Argument: $($fourth)"
}

در قسمت بعد در مورد Advanced Functionها صحبت خواهیم کرد و اجزای دیگر توابع را بیشتر توضیح خواهیم داد.

یک نکته در مورد خروجی دستورات درون کنسول
در ویندوز میتوانیم خروجی کنسول را به Out-GridView پایپ کنیم که در واقع یک GUI برای نمایش دادن خروجی کنسول است. این کامند فقط در ویندوز قابل استفاده است:

یک نسخه cross-platform آن نیز که مناسب کنسول است تهیه شده که میتوانید از آن استفاده کنید: 
Get-Command -ParameterType Hashtable | Out-ConsoleGridview
با این خروجی:

البته به صورت پیش‌فرض نصب نیست و باید از طریق PowerShell Gallery آن را نصب کنید:

Install-Module -Name Microsoft.PowerShell.ConsoleGuiTools


نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
با سلام و احترام
من سه تا کلاس به شکل زیر دارم
public class Location {
        [Key]
        public int Id { get; set; }
        [Required]
        public string LocationName { get; set; }

        public virtual ICollection<User> Users { get; set; }
        public virtual ICollection<Person> People { get; set; }

    }

   public class Person {
        [Key]
        public int Id { get; set; }

        [MaxLength (250)]
        [Required]
        public string LastName { get; set; }

        [MaxLength (250)]
        [Required]
        public string FirstName { get; set; }


        public int? UserId { get; set; }
        public  virtual User User { get; set; }
        public int? LocationId { get; set; }
        public virtual Location Location { get; set; }

    }

   public class User {
        [Key]
        public int Id { get; set; }

        [Required]
        [MaxLength (250)]
        public string LastName { get; set; }

        [Required]
        [MaxLength (250)]
        public string FirstName { get; set; }

        [Required]
        [MaxLength (250)]
        public string UserName { get; set; }

        [Required]
        [MaxLength (250)]
        public string Password { get; set; }

        public int? LocationId { get; set; }
        public virtual Location Location { get; set; }

        public virtual ICollection<Person> People { get; set; }
}
همانطور که در طراحی کلاسها مشاهده می‌شود 
1- در کلاس Location با استفاده از navigation property‌های Users و People به کاربران و افراد یک مکان دسترسی داریم.
2- در کلاس Person با استفاده از navigation property‌های User و Location میتوان پی برد که این فرد توسط چه کاربری ثبت شده و همچنین فرد در چه مکانی میباشد.
3- در کلاس User نیز با استفاده از navigation property‌های Location و People میتوان پی برد که این کاربر چه افرادی را ثبت کرده و کاربر در چه مکانی میباشد.
مشکل:
1- همانطور که در تعریف این کلاسها مشاهده میکنیم به طور مثال از کلاس کاربر به افراد دسترسی داریم و هر فرد نیز دوباره به کاربر دسترسی دارد و این navigation property به صورت loop به یکدیگر دسترسی دارند و این مسئله در واکشی اطلاعات با زمان زیادی همراه بوده و همچنین در تبدیل آنها به به DTO توسط Automaper مشکلاتی ایجاد می‌کند. و یا به طور مثال یک User به افراد ثبت شده توسط خودش دسترسی دارد و هر فرد دوباره به کاربر دسترسی دارد و ...
2- هنگام  استفاده از متد 
ProjectTo در  automapper موضوع LazyLoading در نظر گرفته نمیشود و تمامی  navigation property ها لود می‌شوند.
سوال : 
1- آیا طراحی کلاسها به این شکل ایراد دارد؟
2- برای حل مشکل این نوع navigation property ها راهکاری وجود دارد؟
3- از متد 
ProjectTo چطور استفاده کنیم تا LazyLoading را لغو نکند؟
4- آیا فعال بودن 
LazyLoading در پروژه‌های وبی اشکالاتی ایجاد میکند؟
مطالب
C# 7 - Pattern matching and switch expressions
هرچند کار کردن با کلاس‌ها و اینترفیس‌های strongly typed ساده‌تر است، اما گاهی از اوقات نیاز است تا با نوع object کار کرد. به علاوه حتی در حین کار کردن با کلاس‌ها و اینترفیس‌ها هم نیاز است تا نوع خاصی از کلاس‌های مشتق شده را جهت فراخوانی متدی ویژه، بررسی کرد. به همین جهت مفهوم «pattern matching» به C# 7 اضافه شده‌است تا بتوان با سلسله مراتب اشیاء، ساده‌تر کار کرد. برای این منظور اپراتور is و عبارت switch، با الگوهای const ،var و type بهبود و تکامل بخشیده شده‌اند.


استفاده از اپراتور is به همراه pattern matching

اپراتور is از اولین نگارش #C مهیا بوده‌است و هدف آن بررسی تطابق شیءایی خاص، با نوعی مفروض است. برای مثال آیا این نوع مورد بررسی، اینترفیس خاصی را پیاده سازی می‌کند و یا اینکه آیا از کلاسی خاص مشتق شده‌است یا خیر؟ حاصل این بررسی هم true یا false است.
با بهبودهای حاصل شده‌ی در C# 7، اکنون می‌توان از اپراتور is جهت بررسی الگوها نیز استفاده کرد.


الگوی const

در مثال ذیل، آرایه‌ای از اشیاء، شامل یک نال، یک عدد و دو شیء کاربر، تعریف شده‌اند:
public class User
{
    public User(string name)
    {
        Name = name;
    }
 
    public string Name { get; }
}


object[] data = { null, 42, new User("User 1"), new User("User 2") };
foreach (var item in data)
{
    if (item is null) Console.WriteLine("it's a const pattern");
    if (item is 42) Console.WriteLine("it's 42");
}
اولین الگوی مهیای در C# 7، با نام «const pattern» شناخته می‌شود که نمونه‌ای از آن‌را در بدنه‌ی حلقه‌ی فوق مشاهده می‌کنید.
در C# 7 می‌توان اپراتور is را بر روی یک عدد ثابت مانند 42 و یا یک null بکار گرفت. پیش از C# 7 برای بررسی نال بودن یک شیء، تنها از پراتور == می‌شد استفاده کرد.


الگوی Type

دومین الگوی مهیای در C# 7، «الگوی نوع» نام دارد و هدف آن بررسی تطابق یک شیء، با شیءایی دیگر است. مهم‌ترین تفاوت آن با نگارش‌های پیشین سی شارپ این است که اگر اکنون تطابقی تشخیص داده شود، شیء، به متغیر جدید تعریف شده، انتساب داده می‌شود:
object[] data = { null, 42, new User("User 1"), new User("User 2") };
foreach (var item in data)
{
    if (item is int i) Console.WriteLine($"it's a type pattern with an int and the value {i}");
    if (item is User p) Console.WriteLine($"it's a person: {p.Name}");
    if (item is User p2 && p2.Name.StartsWith("U"))
    {
        Console.WriteLine($"it's a person starting with U {p2.Name}");
    }
}
همانطور که ملاحظه می‌کنید اینبار می‌توان پس از اپراتور is، یک متغیر جدید را هم تعریف کرد و در صورت تطابق، این متغیر به صورت خودکار مقدار دهی می‌گردد. به علاوه در اینجا امکان ترکیب شرط‌ها نیز پس از is، مانند سومین if نوشته شده، میسر است.

و یا اکنون قطعه کد قدیمی ذیل را
object obj1 = "Hello, World!";
var str1 = obj1 as string;
if (str1 != null)
{
   Console.WriteLine(str1);
}
می‌توان با pattern matching و استفاده از «الگوی نوع»، به نحو ذیل خلاصه کرد:
object obj2 = "Hello, World!";
if (obj2 is string str2)
{
   Console.WriteLine(str2);
}


الگوی Var

سومین الگوی مهیای در C# 7، الگوی var نام دارد و در این حالت می‌توان بجای ذکر صریح نوع تطابق داده شده، از var استفاده کرد.
بدیهی است این الگو همواره با موفقیت روبرو می‌شود؛ چون var به همان نوع شیء مفروض اشاره می‌کند:
object[] data = { null, 42, new User("User 1"), new User("User 2") };
foreach (var item in data)
{
    if (item is var x) Console.WriteLine($"it's a var pattern with the type {x?.GetType()?.Name}");
}
مهم‌ترین مزیت آن این است که متغیر تعریف شده‌ی پس از var دقیقا دارای همان مقدار و نوع اصلی شیء است و پس از فراخوانی GetType می‌توان به خواص آن دسترسی یافت؛ مانند خاصیت Name ذکر شده‌ی در مثال فوق.
در این حالت اگر item دقیقا null باشد، برای بررسی آن می‌توان از null conditional operator معرفی شده‌ی در C# 6 استفاده کرد.


استفاده از عبارت switch به همراه pattern matching

در C# 7، عبارت switch نیز تکامل یافته‌است. در اینجا الگوهای const ،var و type را نیز می‌توان پس از ذکر case بکار گرفت:
public static void SwitchPattern(object o)
{
    switch (o)
    {
        case null:
            Console.WriteLine("it's a constant pattern");
            break;
        case int i:
            Console.WriteLine("it's an int");
            break;
        case User p when p.Name.StartsWith("U"):
            Console.WriteLine($"a U person {p.Name}");
            break;
        case User p:
            Console.WriteLine($"any other person {p.Name}");
            break;
        case var x:
            Console.WriteLine($"it's a var pattern with the type {x?.GetType().Name} ");
            break;
        default:
            break;
    }
}
الگوهایی را که در اینجا مشاهده می‌کنید دقیقا همان‌هایی هستند که پیشتر بررسی کردیم. الگوی const برای بررسی نال و یک عدد. الگوی type برای بررسی تطابق با یک شیء خاص و سپس استفاده‌ی از آن شیء و الگوی var برای دسترسی به نام نوع مفروض.
تنها نکته‌ی جدید در اینجا، استفاده از واژه‌ی کلیدی when است برای ترکیب شرط‌ها (case User p when p.Name.StartsWith). بنابراین در C# 7 امکان نوشتن case null میسر است؛ به همراه نوشتن شرط‌ها توسط when، در حین تعاریف caseها. به علاوه اینبار عبارت switch محدود به نوع‌های پایه مانند اعداد، رشته‌ها و enums نیست و در اینجا می‌توان یک شیء را نیز مشخص کرد.


شبیه سازی switch موجود در ویژوال بیسیک در C# 7

ویژوال بیسیک از نگارش‌های ابتدایی آن دارای case‌های پیشرفته‌تری است نسبت به #C. برای نمونه در اینجا امکان تعریف تعدادی عدد، استفاده از To و استفاده‌ی از =< را هم مشاهده می‌کنید:
Select Case age
  Case 50
    ageBlock = "the big five-oh"
  Case 80, 81, 82, 83, 84, 85, 86, 87, 88, 89
    ageBlock = "octogenarian"
  Case 90 To 99
    ageBlock = "nonagenarian"
  Case Is >= 100
    ageBlock = "centenarian"
  Case Else
    ageBlock = "just old"
End Select

اکنون در C# 7 می‌توان یک چنین توانمندی را با pattern matching هم پیاده سازی کرد:
string ageBlock;
var age = 40;
switch (age)
{
    case 50:
        ageBlock = "the big five-oh";
        break;
    case var testAge when (new List<int> { 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 }).Contains(testAge):
        ageBlock = "octogenarian";
        break;
    case var testAge when ((testAge >= 90) && (testAge <= 99)):
        ageBlock = "nonagenarian";
        break;
    case var testAge when (testAge >= 100):
        ageBlock = "centenarian";
        break;
    default:
        ageBlock = "just old";
        break;
}
در این مثال یکی از کاربردهای عملی الگوی var را مشاهده می‌کنید؛ یا همان دسترسی به مقدار و نوع وارد شده و سپس اعمال شرط بر روی آن.
همانطور که مشاهده می‌کنید، در قسمت when نیز می‌توان توسط && و || نیز شرط‌ها را ترکیب کرد و یا متدی را با خروجی bool (مانند Contains) بر روی مقدار دریافتی اعمال کرد.
نظرات مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت پنجم - اعتبارسنجی تنظیمات آغازین برنامه
در یک پروژه nullable ref types فعال است و در دیگری خیر:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Nullable>enable</Nullable>
  </PropertyGroup>
اگر می‌خواهید هر دو مثل هم عمل کنند، باید به اخطارهای کامپایلر دقت کنید و Name را nullable تعریف کنید:
public class Person
{
     public string? Name { get; set; }
     public string? Family { get; set; }
مطالب دوره‌ها
Lazy loading در تزریق وابستگی‌ها به کمک StructureMap
پیشنیاز این بحث، مطلب «استفاده از StructureMap به عنوان یک IoC Container» می‌باشد که پیشتر در این سری مطالعه کردید (در حد نحوه نصب StructureMap و آشنایی با تنظیمات اولیه آن)

ابتدا ساختار بحث جاری را به نحو زیر درنظر بگیرید:
namespace DI04.Services
{
    public interface IAccounting
    {
        void CreateInvoice(int orderId, int count);
    }
}

namespace DI04.Services
{
    public interface ISales
    {
        bool ShippingAllowed(int orderId);
    }
}

namespace DI04.Services
{
    public interface IOrderHandler
    {
        void Handle(int orderId, int count);
    }
}

using System;

namespace DI04.Services
{
    public class Accounting : IAccounting
    {
        public Accounting()
        {
            Console.WriteLine("Accounting ctor.");
        }

        public void CreateInvoice(int orderId, int count)
        {
            // ...
        }
    }
}

using System;

namespace DI04.Services
{
    public class Sales : ISales
    {
        public Sales()
        {
            Console.WriteLine("Sales ctor.");
        }

        public bool ShippingAllowed(int orderId)
        {
            // فقط جهت آزمایش سیستم
            return false;
        }
    }
}

using System;

namespace DI04.Services
{
    public class OrderHandler : IOrderHandler
    {
        private readonly IAccounting _accounting;
        private readonly ISales _sales;
        public OrderHandler(IAccounting accounting, ISales sales)
        {
            Console.WriteLine("OrderHandler ctor.");
            _accounting = accounting;
            _sales = sales;
        }

        public void Handle(int orderId, int count)
        {
            if (_sales.ShippingAllowed(orderId))
            {
                _accounting.CreateInvoice(orderId, count);
            }
        }
    }
}
در اینجا کار مدیریت سفارشات در کلاس OrderHandler انجام می‌شود. این کلاس دارای دو وابستگی تزریق شده در سازنده کلاس می‌باشد.
در متد Handle، اگر مجوز کار توسط متد ShippingAllowed صادر شد، آنگاه کار نهایی توسط متد CreateInvoice باید صورت گیرد. با توجه به اینکه تزریق وابستگی‌ها در سازنده کلاس صورت می‌گیرد، نیاز است پیش از وهله سازی کلاس OrderHandler، هر دو وابستگی آن وهله سازی و تزریق شوند. در حالیکه در مثال جاری هیچگاه به وهله‌ای از نوع IAccounting نیاز نخواهد شد؛ زیرا متد ShippingAllowed در این مثال، فقط false بر می‌گرداند.
و از این نمونه‌ها زیاد هستند. کلاس‌هایی با تعداد متدهای بالا و تعداد وابستگی‌های قابل توجه که ممکن است در طول عمر شیء وهله سازی شده این کلاس، تنها به یکی از این وابستگی‌ها نیاز شود و نه به تمام آن‌ها.
راه حلی برای این مساله در دات نت 4 با معرفی کلاس Lazy ارائه شده است؛ به این نحو که اگر برای مثال در اینجا accounting را Lazy تعریف کنیم، تنها زمانی وهله سازی خواهد شد که به آن نیاز باشد و نه پیش از آن.
 private readonly Lazy<IAccounting> _accounting;

سؤال: Lazy loading تزریق وابستگی‌ها را چگونه می‌توان توسط StructureMap فعال ساخت؟

ابتدا تعاریف کلاس  OrderHandlerرا به نحو زیر بازنویسی می‌کنیم:
using System;

namespace DI04.Services
{
    public class OrderHandlerLazy : IOrderHandler
    {
        private readonly Lazy<IAccounting> _accounting;
        private readonly Lazy<ISales> _sales;
        public OrderHandlerLazy(Lazy<IAccounting> accounting, Lazy<ISales> sales)
        {
            Console.WriteLine("OrderHandlerLazy ctor.");
            _accounting = accounting;
            _sales = sales;
        }

        public void Handle(int orderId, int count)
        {
            if (_sales.Value.ShippingAllowed(orderId))
            {
                _accounting.Value.CreateInvoice(orderId, count);
            }
        }
    }
}
در اینجا سازنده‌های کلاس، به صورت Lazy معرفی شده‌اند. دسترسی به فیلدهای sales و accounting نیز اندکی تغییر کرده‌اند و اینبار از طریق خاصیت Value آن‌ها باید انجام شود.
مرحله نهایی هم اندکی تغییر در نحوه معرفی تنظیمات اولیه StructureMap است:
using System;
using DI04.Services;
using StructureMap;

namespace DI04
{
    class Program
    {
        static void Main(string[] args)
        {
            // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
            ObjectFactory.Initialize(x =>
            {
                x.For<IOrderHandler>().Use<OrderHandlerLazy>();

                // Lazy loading
                x.For<Lazy<IAccounting>>().Use(c => new Lazy<IAccounting>(c.GetInstance<Accounting>));
                x.For<Lazy<ISales>>().Use(c => new Lazy<ISales>(c.GetInstance<Sales>));
            });

            var orderHandler = ObjectFactory.GetInstance<IOrderHandler>();
            orderHandler.Handle(orderId: 1, count: 10);
        }
    }
}
به این ترتیب زمانیکه برنامه به sales.Value می‌رسد آنگاه نیاز به وهله سازی شیء متناظر با آن‌را خواهد داشت که در اینجا از طریق متد GetInstance به آن ارسال خواهد گردید.

خروجی برنامه در این حالت:
OrderHandlerLazy ctor.
Sales ctor.
همانطور که مشاهده می‌کنید، هرچند کلاس OrderHandlerLazy دارای دو وابستگی تعریف شده در سازنده کلاس است، اما تنها وابستگی Sales آن زمانیکه به آن نیاز شده، وهله سازی گردیده است و خبری از وهله سازی کلاس Accounting نیست (چون مطابق تعاریف کلاس‌های برنامه هیچگاه به مسیر accounting.Value نخواهیم رسید؛ بنابراین نیازی هم به وهله سازی آن نخواهد بود).


دریافت مثال این قسمت
DI04.zip
مطالب
تعریف قالب‌های جداول سفارشی و کار با منابع داده‌ای از نوع Anonymous در PdfReport
تعدادی قالب جدول پیش فرض در PdfReport تعریف شده‌اند، مانند BasicTemplate.RainyDayTemplate ،BasicTemplate.SilverTemplate و غیره. نحوه تعریف این قالب‌ها بر اساس پیاده سازی اینترفیس ITableTemplate است. برای نمونه اگر یک قالب جدید را بخواهیم ایجاد کنیم، تنها کافی است اینترفیس یاد شده را به نحو زیر پیاده سازی نمائیم:
using System.Collections.Generic;
using System.Drawing;
using iTextSharp.text;
using PdfRpt.Core.Contracts;

namespace PdfReportSamples.HexDump
{
    public class GrayTemplate : ITableTemplate
    {
        public HorizontalAlignment HeaderHorizontalAlignment
        {
            get { return HorizontalAlignment.Center; }
        }

        public BaseColor AlternatingRowBackgroundColor
        {
            get { return new BaseColor(Color.WhiteSmoke); }
        }

        public BaseColor CellBorderColor
        {
            get { return new BaseColor(Color.LightGray); }
        }

        public IList<BaseColor> HeaderBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(ColorTranslator.FromHtml("#990000")), new BaseColor(ColorTranslator.FromHtml("#e80000")) }; }
        }

        public BaseColor RowBackgroundColor
        {
            get { return null; }
        }

        public IList<BaseColor> PreviousPageSummaryRowBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(Color.LightSkyBlue) }; }
        }

        public IList<BaseColor> SummaryRowBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(Color.LightSteelBlue) }; }
        }

        public IList<BaseColor> PageSummaryRowBackgroundColor
        {
            get { return new List<BaseColor> { new BaseColor(Color.Yellow) }; }
        }

        public BaseColor AlternatingRowFontColor
        {
            get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
        }

        public BaseColor HeaderFontColor
        {
            get { return new BaseColor(Color.White); }
        }

        public BaseColor RowFontColor
        {
            get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
        }

        public BaseColor PreviousPageSummaryRowFontColor
        {
            get { return new BaseColor(Color.Black); }
        }

        public BaseColor SummaryRowFontColor
        {
            get { return new BaseColor(Color.Black); }
        }

        public BaseColor PageSummaryRowFontColor
        {
            get { return new BaseColor(Color.Black); }
        }

        public bool ShowGridLines
        {
            get { return true; }
        }
    }
}
و برای استفاده از آن خواهیم داشت:
.MainTableTemplate(template =>
{
      template.CustomTemplate(new GrayTemplate());
})
چند نکته:
- در کتابخانه iTextSharp، کلاس رنگ توسط BaseColor تعریف شده است. به همین جهت خروجی رنگ‌ها را در اینجا نیز بر اساس BaseColor مشاهده می‌کنید. اگر نیاز داشتید رنگ‌های تعریف شده در فضای نام استاندارد System.Drawing را به BaseColor تبدیل کنید، فقط کافی است آن‌را به سازنده کلاس BaseColor ارسال نمائید.
- اگر علاقمند هستید که معادل رنگ‌های HTML ایی را در اینجا داشته باشید، می‌توان از متد توکار ColorTranslator.FromHtml استفاده کرد.
- برای تعریف رنگی به صورت شفاف (transparent) آن‌را مساوی null قرار دهید.
- در اینترفیس فوق، تعدادی از خروجی‌ها به صورت IList است. در این موارد می‌توان یک یا دو رنگ را حداکثر معرفی کرد. اگر دو رنگ را معرفی کنید یک گرادیان خودکار از این دو رنگ، تشکیل خواهد شد.
- اگر قالب جدید زیبایی را طراحی کردید، لطفا در این پروژه مشارکت کرده و آن‌را به صورت یک وصله ارائه دهید!


تهیه یک منبع داده ناشناس

مثال زیر را در نظر بگیرید. در اینجا قصد داریم معادل Ascii اطلاعات Hex را تهیه کنیم:
using System;
using System.Collections;
using System.Linq;

namespace PdfReportSamples.HexDump
{
    public static class PrintHex
    {
        public static char ToSafeAscii(this int b)
        {
            if (b >= 32 && b <= 126)
            {
                return (char)b;
            }
            return '_';
        }

        public static IEnumerable HexDump(this byte[] data)
        {
            int bytesPerLine = 16;
            return data
                        .Select((c, i) => new { Char = c, Chunk = i / bytesPerLine })
                        .GroupBy(c => c.Chunk)
                        .Select(g =>
                                  new
                                  {
                                      Hex = g.Select(c => String.Format("{0:X2} ", c.Char)).Aggregate((s, i) => s + i),
                                      Chars = g.Select(c => ToSafeAscii(c.Char).ToString()).Aggregate((s, i) => s + i)
                                  })
                        .Select((s, i) =>
                                        new
                                        {
                                            Offset = String.Format("{0:d6}", i * bytesPerLine),
                                            Hex = s.Hex,
                                            Chars = s.Chars
                                        });
        }
    }
}
نکته مهم این منبع داده، خروجی IEnumerable آن و Select نهایی عبارت LINQ ایی است که مشاهده می‌کنید. در اینجا اطلاعات به یک شیء ناشناس با اعضای Offset، Hex و Chars نگاشت شده‌اند.
مفهوم فوق از دات نت 3 به بعد تحت عنوان anonymous types در دسترس است. توسط این قابلیت می‌توان یک شیء را بدون نیاز به تعریف ابتدایی آن ایجاد کرد. این نوع‌های ناشناس توسط واژه‌های کلیدی new و var تولید می‌شوند. کامپایلر به صورت خودکار برای هر anonymous type یک کلاس ایجاد می‌کند.

نکته‌ای مهم حین کار با کلاس‌های ناشناس:
کلاس‌های ناشناس به صورت خودکار توسط کامپایلر تولید می‌شوند و ... از نوع internal هم تعریف خواهند شد. به عبارتی در اسمبلی‌های دیگر قابل استفاده نیستند. البته می‌توان توسط ویژگی assembly:InternalsVisibleTo ، تعاریف internal یک اسمبلی را دراختیار اسمبلی دیگری نیز گذاشت. ولی درکل باید به این موضوع دقت داشت و اگر قرار است منبع داده‌ای به این نحو تعریف شود، بهتر است داخل همان اسمبلی تعاریف گزارش باشد.

برای نمایش این نوع اطلاعات حاصل از کوئری‌های LINQ می‌توان از منبع داده پیش فرض AnonymousTypeList به نحو زیر استفاده کرد:
using System;
using System.Text;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.HexDump
{
    public class HexDumpPdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\COUR.ttf",
                    Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.TTF");
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
            })
            .PagesHeader(header =>
            {
                header.DefaultHeader(defaultHeader =>
                {
                    defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                    defaultHeader.Message("Hex Dump");
                });
            })
            .MainTableTemplate(template =>
            {
                template.CustomTemplate(new GrayTemplate());
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
            })
            .MainTableDataSource(dataSource =>
            {
                var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog.");
                var list = data.HexDump();
                dataSource.AnonymousTypeList(list);
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("Offset");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(0.5f);
                    column.HeaderCell("Offset");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("Hex");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(2.5f);
                    column.HeaderCell("Hex");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName("Chars");
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(1f);
                    column.HeaderCell("Chars");
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\HexDumpSampleRpt.pdf"));
        }
    }
}

توضیحات:
در اینجا منبع داده بر اساس کلاس‌های کمکی که تعریف کردیم، به نحو زیر مشخص شده است:
            .MainTableDataSource(dataSource =>
            {
                var data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog.");
                var list = data.HexDump();
                dataSource.AnonymousTypeList(list);
            })
و سپس برای معرفی ستون‌های متناظر با این منبع داده ناشناس، فقط کافی است آن‌ها را به صورت رشته‌ای معرفی کنیم:
column.PropertyName("Offset");
//...
column.PropertyName("Hex");
//...
column.PropertyName("Chars");



نکته‌ای در مورد خواص تودرتو:
در حین استفاده از AnonymousTypeList امکان تعریف خواص تو در تو نیز وجود دارد. برای مثال فرض کنید که Select نهایی به شکل زیر تعریف شده است و در اینجا OrderInfoData نیز خود یک شیء است:
.Select(x => new
{
   OrderInfo = x.OrderInfoData
})
برای استفاده از یک چنین منبع داده‌ای، ذکر مسیر خاصیت تودرتوی مورد نظر نیز مجاز است:
column.PropertyName("OrderInfo.Price");

 
مطالب
بررسی دقیق عملکرد AutoMapper
همانطور که اطلاع دارید، AutoMapper ابزاری برای نگاشت خودکار بین Model و Dto می‌باشد؛ که به صورت نادرست تصور کاهش سرعت در استفاده کردن از آن، بین توسعه دهندگان جا افتاده‌است. در این مقاله قصد داریم به صورت دقیق، به بررسی سرعت عملکرد استفاده از AutoMapper و مقایسه آن با نگاشت دستی بپردازیم.
کد‌های کامل این قسمت را میتوانید از اینجا clone کرده و شخصا تست نمایید.

ابتدا یک پروژه‌ی Console Application را ساخته و AutoMapper را به همراه Ef6، نصب مینماییم. سپس دو کلاس جدید را به نام‌های User و Address به صورت زیر در پوشه‌ی Models مینویسیم.
using System.Collections.Generic;

namespace AutoMapperComparison.Models
{
    public class User
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public ICollection<Address> Addresses { get; set; }
    }
}
using System.ComponentModel.DataAnnotations.Schema;

namespace AutoMapperComparison.Models
{
    public class Address
    {
        public int Id { get; set; }

        public double? Code { get; set; }

        public string Title { get; set; }

        public int UserId { get; set; }

        [ForeignKey(nameof(UserId))]
        public virtual User User { get; set; }
    }
}
بدیهی است که این دو مدل با همدیگر رابطه‌ی 1 به چند دارند. حال کافیست AppDbContext خود را به صورت زیر تعریف نماییم.
نکته: در متد Seed، برای ثبت رکورد‌های اولیه، از BulkInsert استفاده شده است (باید پکیج BulkInsert را نیز نصب نمایید)
using EntityFramework.BulkInsert.Extensions;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;

namespace AutoMapperComparison.Models
{
    public class AppDbContextInitializer : DropCreateDatabaseAlways<AppDbContext>
    {
        protected override void Seed(AppDbContext context)
        {
            User user = context.Users.Add(new User { Name = "Test" });

            context.SaveChanges();

            List<Address> addresses = new List<Address>();

            for (int i = 0; i < 500000; i++)
            {
                addresses.Add(new Address { Id = i, Code = 1, Title = "Test", UserId = user.Id });
            }

            context.BulkInsert(addresses);

            base.Seed(context);
        }
    }

    public class AppDbContext : DbContext
    {
        static AppDbContext()
        {
            Database.SetInitializer(new AppDbContextInitializer());

            //Database.SetInitializer<AppDbContext>(null);
        }

        public AppDbContext()
            : base(new SqlConnection(@"Data Source=.;Initial Catalog=AppDbContext;Integrated Security=True"), contextOwnsConnection: true)
        {
            Configuration.AutoDetectChangesEnabled = false;
            Configuration.EnsureTransactionsForFunctionsAndCommands = false;
            Configuration.LazyLoadingEnabled = false;
            Configuration.ProxyCreationEnabled = false;
            Configuration.ValidateOnSaveEnabled = false;
            Configuration.UseDatabaseNullSemantics = false;
        }

        public DbSet<User> Users { get; set; }

        public DbSet<Address> Addresses { get; set; }
    }
}
 برای اینکه مقایسه انجام شده دقیق باشد، تمامی Configuration‌های اضافی را نیز غیر فعال نموده‌ام.
فقط نیاز داریم یک Dto را برای Address نیز تعریف کنیم؛ چون قرار است نگاشت از Model به Dto از روی Address و AddressDto انجام شود.
کلاس AddressDto را به صورت زیر ایجاد میکنیم:
namespace AutoMapperComparison.Models
{
    public class AddressDto
    {
        public int Id { get; set; }

        public double? Code { get; set; }

        public string Title { get; set; }

        public int UserId { get; set; }

        public string UserName { get; set; }
    }
}
قرار است به صورت خودکار از طریق AutoMapper و همچنین به صورت دستی، نگاشت از Model به Dto مربوطه انجام شود.
 حال نیاز است فایل Program.cs را باز کرده و تغییرات زیر را اعمل نماییم:
using AutoMapper;
using AutoMapper.QueryableExtensions;
using AutoMapperComparison.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace AutoMapperComparison
{
    public class Program
    {
        public static void Main()
        {
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Address, AddressDto>();
            });

            Console.WriteLine($"Create Db {DateTimeOffset.UtcNow}");
            using (AppDbContext db = new AppDbContext())
            {
                db.Database.Initialize(force: true);
                db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); //Removes all clean buffers from the buffer pool, and columnstore objects from the columnstore object pool
                Console.WriteLine(db.Addresses.ProjectTo<AddressDto>());
                Console.WriteLine(db.Addresses.Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }));
            }

            Console.WriteLine($"Normal Select {DateTimeOffset.UtcNow}");
            using (AppDbContext db = new AppDbContext())
            {
                db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS");
                Stopwatch watch = Stopwatch.StartNew();
                List<AddressDto> addresses = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList();
                List<AddressDto> addresses2 = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList();
                watch.Stop();
                Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}");
            }

            Console.WriteLine($"AutoMapper Exec {DateTimeOffset.UtcNow}");
            using (AppDbContext db = new AppDbContext())
            {
                db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS");
                Stopwatch watch = Stopwatch.StartNew();
                List<AddressDto> addresses = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList();
                List<AddressDto> addresses2 = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList();
                watch.Stop();
                Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}");
            }

            Console.ReadKey();
        }
    }
}
نکته: از دستور DBCC DROPCLEANBUFFERS جهت خالی کردن بافر sql server برای رسیدن به نتیجه‌ی هرچه دقیق‌تر استفاده شده است.
بعد از اجرا کردن، ابتدا بررسی میکنیم که کوئری اجرا شده‌ی دو نوع مختلف، هیچ تفاوتی با هم نداشته باشند.


حال نتایج بدست آمده، در قسمت پایین‌تر آن نمایان میشود:


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

در سه آزمایش دیگر به صورت متوالی نتیجه‌ی زیر بدست آمد:

Normal   AutoMapper
 2451  2378
 2120  2111
 2202  2124

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

اما دلیل چیست؟ از آنجایی که ProjectTo از Dto به Model انجام شده و Lambda Expressionی که به سمت Entity Framework فرستاده شده‌است با روش Normal کاملا برابر است و بقیه‌ی عملیات توسط EF انجام میشود، با قاطعیت میتوان گفت که هر دو روش ذکر شده از نظر Performance کاملا یکسان خواهند بود.

نکته: البته به این موضوع باید توجه شود که اگر همین آزمایش را بطور مثال با استفاده از یک Listی از رکورد‌های درون Memory ساخته شده توسط خودمان انجام دهیم، آن موقع نتیجه‌ی یکسانی نخواهیم داشت، به دلیل اینکه EFی دیگر وجود نخواهد داشت که مسئولیت بازگشت داده‌ها را بر عهده بگیرد. از آنجائیکه اکثر کارهایی که توقع داریم AutoMapper برای ما انجام دهد، توسط ORM بازگشت داده میشود، پس میتوان گفت نکته‌ی فوق تقریبا در دنیای واقعی رخ نخواهد داد و باعث مشکل نخواهد شد.

مطالب
امکان تعریف توابع خاص بانک‌های اطلاعاتی در EF Core
یکی از اهداف کار با ORMها، رسیدن به کدی قابل ترجمه و استفاده‌ی توسط تمام بانک‌های اطلاعاتی ممکن است و یکی از الزامات رسیدن به این هدف، صرفنظر کردن از قابلیت‌های بومی بانک‌های اطلاعاتی است که در سایر بانک‌های اطلاعاتی دیگر معادلی ندارند. برای مثال SQL Server به همراه توابع توکاری مانند datediff و datepart برای کار با زمان و تاریخ است؛ اما این توابع را به صورت مستقیم نمی‌توان در ORMها استفاده کرد. چون به محض استفاده‌ی از آن‌ها، کد تهیه شده دیگر قابلیت انتقال به سایر بانک‌های اطلاعاتی را نخواهد داشت. اما ... اگر این هدف را نداشته باشیم، چطور؟ آیا می‌توان یک تابع DateDiff سفارشی را برای EF Core تهیه نمود و از تمام قابلیت‌های بومی آن در کوئری‌های LINQ استفاده کرد؟ بله! یک چنین قابلیتی تحت عنوان DbFunctions در EF Core پشتیبانی می‌شود که روش تهیه‌ی آن‌ها را در این مطلب بررسی خواهیم کرد.


معرفی موجودیت Person

در مثال این مطلب قصد داریم، معادل توابع بومی مخصوص SQL Server را که امکان کار با DateTime را مهیا می‌کنند، در EF Core تعریف کنیم. به همین جهت نیاز به موجودیتی داریم که دارای خاصیتی از این نوع باشد:
using System;

namespace EFCoreDbFunctionsSample.Entities
{
    public class Person
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public DateTime AddDate { get; set; }
    }
}


گزارشگیری بر اساس تعداد روز گذشته‌ی از ثبت نام

اکنون فرض کنید می‌خواهیم گزارشی را از تمام کاربرانی که در طی 10 روز قبل ثبت نام کرده‌اند، تهیه کنیم. اگر کوئری زیر را برای این منظور تهیه کنیم:
var usersInfo = context.People.Where(person => (DateTime.Now - person.AddDate).Days <= 10).ToList();
با استثنای زیر متوقف خواهیم شد:
'The LINQ expression 'DbSet<Person>.Where(p => (DateTime.Now - p.AddDate).Days <= 10)'
could not be translated. Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to either
AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
عنوان می‌کند که یک چنین کوئری LINQ ای قابلیت ترجمه‌ی به SQL را ندارد. اما ... نکته‌ی مهم اینجا است که خود SQL Server یک چنین توانمندی را به صورت توکار دارا است:
SELECT [p].[Id], [p].[AddDate], [p].[Name]
FROM [People] AS [p]
WHERE DATEDIFF(Day, [p].[AddDate], GETDATE()) <= 10
برای انجام کوئری مدنظر فقط کافی است از تابع DATEDIFF توکار آن با پارامتر Day، استفاده کنیم تا لیست تمام کاربران ثبت نام کرده‌ی در طی 10 روز قبل را بازگشت دهد. اکنون سؤال اینجا است که آیا می‌توان چنین تابعی را به EF Core معرفی کرد؟


روش تعریف تابع DATEDIFF سفارشی در EF Core

برای تعریف متد DateDiff مخصوص EF Core، ابتدا باید یک کلاس static را تعریف کرد و سپس تنها امضای این متد را، معادل امضای تابع توکار SQL Server تعریف کرد. این متد نیازی نیست تا پیاده سازی را داشته باشد. به همین جهت بدنه‌ی آن‌را صرفا با یک throw new InvalidOperationException مقدار دهی می‌کنیم. هدف از این متد، استفاده‌ی از آن در LINQ Expressions است و قرار نیست به صورت مستقیمی بکار گرفته شود:
namespace EFCoreDbFunctionsSample.DataLayer
{
    public enum SqlDateDiff
    {
        Year,
        Quarter,
        Month,
        DayOfYear,
        Day,
        Week,
        Hour,
        Minute,
        Second,
        MilliSecond,
        MicroSecond,
        NanoSecond
    }

    public static class SqlDbFunctionsExtensions
    {
        public static int SqlDateDiff(SqlDateDiff interval, DateTime initial, DateTime end)
            => throw new InvalidOperationException($"{nameof(SqlDateDiff)} method cannot be called from the client side.");
        public static readonly MethodInfo SqlDateDiffMethodInfo = typeof(SqlDbFunctionsExtensions)
            .GetRuntimeMethod(
                nameof(SqlDbFunctionsExtensions.SqlDateDiff),
                new[] { typeof(SqlDateDiff), typeof(DateTime), typeof(DateTime) }
            );
    }
}
در اینجا علاوه بر تعریف امضای متد DateDiff که در اینجا SqlDateDiff نام گرفته‌است، فیلد SqlDateDiffMethodInfo را نیز مشاهده می‌کنید. در حین تعریف و معرفی DbFunctions سفارشی به EF Core، متدهایی که اینکار را انجام می‌دهند، پارامترهای ورودی از نوع MethodInfo دارند. به همین جهت یک چنین تعریفی انجام شده‌است.


روش معرفی تابع DATEDIFF سفارشی به EF Core

پس از تعریف امضای متد معادل DateDiff، اکنون نوبت به معرفی آن به EF Core است:
namespace EFCoreDbFunctionsSample.DataLayer
{
    public class ApplicationDbContext : DbContext
    {
        // ...
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.HasDbFunction(SqlDbFunctionsExtensions.SqlDateDiffMethodInfo)
                .HasTranslation(args =>
                {
                    var parameters = args.ToArray();
                    var param0 = ((SqlConstantExpression)parameters[0]).Value.ToString();
                    return SqlFunctionExpression.Create("DATEDIFF",
                        new[]
                        {
                            new SqlFragmentExpression(param0), // It should be written as DateDiff(day, ...) and not DateDiff(N'day', ...) .
                            parameters[1],
                            parameters[2]
                        },
                        SqlDbFunctionsExtensions.SqlDateDiffMethodInfo.ReturnType,
                        typeMapping: null);
                });
        }
    }
}
کار تعریف DbFunctions سفارشی توسط متد HasDbFunction صورت می‌گیرد. پارامتر این متد، همان MethodInfo معادل امضای تابع توکار مدنظر است.
سپس توسط متد HasTranslation، مشخص می‌کنیم که این متد به چه نحوی قرار است به یک عبارت SQL ترجمه شود. پارامتر args ای که در اینجا در اختیار ما قرار می‌گیرد، دقیقا همان پارامترهای متد public static int SqlDateDiff(SqlDateDiff interval, DateTime initial, DateTime end) هستند که در این مثال خاص، شامل سه پارامتر می‌شوند. پارامترهای دوم و سوم آن‌را به همان نحوی که دریافت می‌کنیم، به SqlFunctionExpression.Create ارسال خواهیم کرد. اما پارامتر اول را از نوع enum تعریف کرده‌ایم و همچنین قرار نیست به صورت 'N'day و رشته‌ای به سمت بانک اطلاعاتی ارسال شود، بلکه باید به همان نحو اصلی آن (یعنی day)، در کوئری نهایی درج گردد، به همین جهت ابتدا Value آن‌را استخراج کرده و سپس توسط SqlFragmentExpression عنوان می‌کنیم آن‌را باید به همین نحو درج کرد.
پارامتر اول متد SqlFunctionExpression.Create، باید دقیقا معادل نام متد توکار مدنظر باشد. پارامتر دوم آن، لیست پارامترهای این تابع است. پارامتر سوم آن، نوع خروجی این تابع است که از طریق MethodInfo معادل، قابل استخراج است.


استفاده‌ی از DbFunction سفارشی جدید در برنامه

پس از این تعاریف و معرفی‌ها، اکنون می‌توان متد سفارشی SqlDateDiff تهیه شده را به صورت مستقیمی در کوئری‌های LINQ استفاده کرد تا قابلیت ترجمه‌ی به SQL را پیدا کنند:
var sinceDays = 10;
users = context.People.Where(person =>
      SqlDbFunctionsExtensions.SqlDateDiff(SqlDateDiff.Day, person.AddDate, DateTime.Now) <= sinceDays).ToList();
/*
SELECT [p].[Id], [p].[AddDate], [p].[Name]
FROM [People] AS [p]
WHERE DATEDIFF(Day, [p].[AddDate], GETDATE()) <= @__sinceDays_0
*/


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: EFCoreDbFunctionsSample.zip
این کدها به همراه چند تابع سفارشی دیگر نیز هستند.