مطالب
بررسی Bad code smell ها: متد حسود یا Feature envy
متد حسود یا Feature envy در دسته بندی «کدهایی بیش از اندازه، وابسته به هم» قرار می‌گیرد. چنین متدی بیش از آنکه از فیلدها و خصوصیات کلاس خود استفاده کند، از فیلدها و خصوصیات شیء دیگری از نوعی دیگر، استفاده می‌کند.  
یکی از اشکالات کدهای بیش از اندازه وابسته به هم، دشواری در نگهداری و تغییر کد است. به‌طوری‌که در زمان تغییر بخشی از کد، نیاز است بخش‌های مرتبط نیز مورد بررسی قرار گیرند. همچنین وابستگی بیش از اندازه کلاس‌ها به یکدیگر قابلیت جداسازی و استفاده مجدد کلاس‌ها را کاهش خواهد داد.
معمولا در نتیجه‌ی جابجایی مسئولیت‌ها، در بخش‌های مختلف کد، شاهد چنین کد بد بویی هستیم. یکی از پر تکرارترین شرایط، زمانی است که تعدادی از متغیرهای متد، به کلاس داده خاص خود منتقل شوند.
 به طور مثال: 
در این کد یک کلاس برای ایجاد یک آیتم صورت حساب نوشته شده است که یک آیتم از قرارداد را به عنوان ورودی دریافت و سپس اقدام به ایجاد یک آیتم صورت حساب می‌کند.
public class OrderItem 
{ 
    public int Quantity { get; set; } 
    public decimal UnitPrice { get; set; } 
    public decimal Discount { get; set; } 
} 
public class InvoiceItemGenerator 
{ 
    private readonly OrderItem _orderItem; 
    public InvoiceItemGenerator(OrderItem orderItem) 
    { 
        _orderItem = orderItem; 
    } 
    public dynamic Generate() 
    { 
        dynamic invoiceItem = new ExpandoObject(); 
        invoiceItem.Amount = _orderItem.Quantity * _orderItem.UnitPrice - _orderItem.Discount; 
        return invoiceItem; 
    } 
}
اگر به متد Generate دقت کرده باشید متوجه خواهید شد که این متد از خصوصیات شی OrderItem استفاده بیش از اندازه می‌کند و در حال انجام محاسبه‌ای است که ظاهرا بهتر بود جای دیگری باشد.  
در این مثال خاص دو راه حل برای این موضوع وجود دارد: 
اول: انتقال منطق محاسبه مبلغ نهایی آیتم، به کلاس OrderItem. مانند:
public class OrderItem 
{ 
    public int Quantity { get; set; } 
    public decimal UnitPrice { get; set; } 
    public decimal Discount { get; set; } 
    public decimal GetFinalAmount() 
    { 
        return Quantity * UnitPrice - Discount; 
    } 
} 
public class InvoiceItemGenerator 
{ 
    private readonly OrderItem _orderItem; 
    public InvoiceItemGenerator(OrderItem orderItem) 
    { 
        _orderItem = orderItem; 
    } 
    public dynamic Generate() 
    { 
        dynamic invoiceItem = new ExpandoObject(); 
        invoiceItem.Amount = _orderItem.GetFinalAmount(); 
        return invoiceItem; 
    } 
}
دوم: انقال کل منطق محاسبه قیمت به کلاسی مثلا با نام InvoiceItemAmountCalculator که در نوشته‌های پیشین نمونه‌ای از آن را مشاهده کردیم. در واقع در این روش استراتژی محاسبه قیمت را به صورت کلاسی مجزا پیاده سازی می‌کنیم. 
به طور کلی روش‌های رفع چنین بوی بدی به همین دو نوع برخورد ختم خواهد شد. ایجاد یک کلاس استراتژی از نظر اصل Single responsibility مفید است؛ ولی ممکن است کد را در دام «درخت ارث بری موازی» بیندازد. 

جمع بندی 

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

یک کتابخانه تقریبا جامع برای پروژه‌های Blazor تقریبا شامل تمام اجزاء مورد نیاز به همراه مستندات کامل ، قابلیت Theme  و قالب های Bootstrap ، Material ،  Ant Design  ، Bulma  و eFrolic  . هنوز نسخه نهایی نیست اما بسیار مفید و کامل است.   

یک کتابخانه Blazor
نظرات نظرسنجی‌ها
نظر شما در رابطه با استفاده از کامپوننت های رایگان و آماده Blazor چیست؟
کامپوننت‌های رایگان همیشه ریسک پولی شدن دارند و می‌تونن برای پروژه‌های متوسط و بزرگ مشکل خیلی جدی ایجاد کنند. البته مهمه که کدوم شرکتی پشت اونا هست. من تجربه استفاده ازAnt Blazor رو دارم. تا اینجا هم مشکلی نداشتم باهاش.
مطالب دوره‌ها
استفاده از StructureMap به عنوان یک IoC Container
StructureMap یکی از IoC containerهای بسیار غنی سورس باز نوشته شده برای دات نت فریم ورک است. امکان تنظیمات آن توسط کدنویسی و یا همان Fluent interfaces، به کمک فایل‌های کانفیگ XML و همچنین استفاده از ویژگی‌ها یا Attributes نیز میسر است. امکانات جانبی دیگری را نیز مانند یکی شدن با فریم ورک‌های Dynamic Proxy برای ساده سازی فرآیندهای برنامه نویسی جنبه‌گرا یا AOP، دارا است. در ادامه قصد داریم با نحوه استفاده از این فریم ورک IoC بیشتر آشنا شویم.


دریافت StructureMap

برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نیوگت ویژوال استودیو فراخوانی کنید:
 PM> Install-Package structuremap
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد (در برنامه‌های دسکتاپ البته)؛ از این جهت که StructureMap ارجاعی را به اسمبلی استاندارد System.Web دارد.


آشنایی با ساختار برنامه

ابتدا یک برنامه کنسول را آغاز کرده و سپس یک Class library جدید را به نام Services نیز به آن اضافه کنید. در ادامه کلاس‌ها و اینترفیس‌های زیر را به Class library ایجاد شده، اضافه کنید. سپس از طریق نیوگت به روشی که گفته شد، StructureMap را به پروژه اصلی (ونه پروژه Class library) اضافه نمائید و Target framework آن‌را نیز در حالت Full قرار دهید بجای حالت Client profile.
namespace DI03.Services
{
    public interface IUsersService
    {
        string GetUserEmail(int userId);
    }
}


namespace DI03.Services
{
    public interface IEmailsService
    {
        void SendEmailToUser(int userId, string subject, string body);
    }
}

using System;

namespace DI03.Services
{
    public class UsersService : IUsersService
    {
        public UsersService()
        {
            //هدف صرفا نمایش وهله سازی خودکار این وابستگی است
            Console.WriteLine("UsersService ctor.");
        }

        public string GetUserEmail(int userId)
        {
            //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه
            return "name@site.com";
        }
    }
}

using System;

namespace DI03.Services
{
    public class EmailsService: IEmailsService
    {
        private readonly IUsersService _usersService;
        public EmailsService(IUsersService usersService)
        {
            Console.WriteLine("EmailsService ctor.");
            _usersService = usersService;
        }

        public void SendEmailToUser(int userId, string subject, string body)
        {
            var email = _usersService.GetUserEmail(userId);
            Console.WriteLine("SendEmailTo({0})", email);
        }
    }
}
در لایه سرویس برنامه، یک سرویس کاربران و یک سرویس ارسال ایمیل تدارک دیده شده‌اند.
سرویس کاربران بر اساس آی دی یک کاربر، برای مثال از بانک اطلاعاتی ایمیل او را بازگشت می‌دهد. سرویس ارسال ایمیل، نیاز به ایمیل کاربری برای ارسال ایمیلی به او دارد. بنابراین وابستگی مورد نیاز خود را از طریق تزریق وابستگی‌ها در سازنده کلاس و وهله سازی شده در خارج از آن (معکوس سازی کنترل)، دریافت می‌کند.
در سازنده‌های هر دو کلاس سرویس نیز از Console.WriteLine استفاده شده‌است تا زمان وهله سازی خودکار آن‌ها را بتوان بهتر مشاهده کرد.
نکته مهمی که در اینجا وجود دارد، بی‌خبری لایه سرویس از وجود IoC Container مورد استفاده است.


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

using DI03.Services;
using StructureMap;

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

            //نمونه‌ای از نحوه استفاده از تزریق وابستگی‌های خودکار
            var emailsService = ObjectFactory.GetInstance<IEmailsService>();
            emailsService.SendEmailToUser(userId: 1, subject: "Test", body: "Hello!");
        }
    }
}
کدهای برنامه را به نحو فوق تغییر دهید. در ابتدا نحوه سیم کشی‌های آغازین برنامه را مشاهده می‌کنید. برای مثال کدهای ObjectFactory.Initialize باید در متدهای آغازین یک پروژه قرار گیرند و تنها یکبار هم نیاز است فراخوانی شوند.
به این ترتیب IoC Container ما زمانیکه قرار است object graph مربوط به IEmailsService درخواستی را تشکیل دهد، خواهد دانست ابتدا به سازنده‌ی کلاس EmailsService می‌رسد. در اینجا برای وهله سازی این کلاس به صورت خودکار، باید وابستگی‌های آن‌را نیز وهله سازی کند. بنابراین بر اساس تنظیمات آغازین برنامه می‌داند که باید از کلاس UsersService برای تزریق خودکار وابستگی‌ها در سازنده کلاس ارسال ایمیل استفاده نماید.
در این حالت اگر برنامه را اجرا کنیم، به خروجی زیر خواهیم رسید:
UsersService ctor.
EmailsService ctor.
SendEmailTo(name@site.com)
بنابراین در اینجا با مفهوم Object graph نیز آشنا شدیم. فقط کافی است وابستگی‌ها را در سازنده‌های کلاس‌ها تعریف کرده و سیم کشی‌های آغازین صحیحی را نیز در ابتدای برنامه معرفی نمائیم. کار وهله سازی چندین سطح با تمام وابستگی‌های متناظر با آن‌ها در اینجا به صورت خودکار انجام خواهد شد و نهایتا یک شیء قابل استفاده بازگشت داده می‌شود.
ابتدایی‌ترین مزیت استفاده از تزریق وابستگی‌ها امکان تعویض آن‌ها است؛ خصوصا در حین Unit testing. اگر کلاسی برای مثال قرار است با شبکه کار کند، می‌توان پیاده سازی آن‌را با یک نمونه اصطلاحا Fake جایگزین کرد و در این نمونه تنها نتیجه‌ی کار را بازگشت داد. کلاس‌های لایه سرویس ما تنها با اینترفیس‌ها کار می‌کنند. این تنظیمات قابل تغییر اولیه IoC container مورد استفاده هستند که مشخص می‌کنند چه کلاس‌هایی باید در سازنده‌های کلاس‌ها تزریق شوند.


تعیین طول عمر اشیاء در StructureMap

برای اینکه بتوان طول عمر اشیاء را بهتر توضیح داد، کلاس سرویس کاربران را به نحو زیر تغییر دهید:
using System;

namespace DI03.Services
{
    public class UsersService : IUsersService
    {
        private int _i;
        public UsersService()
        {
            //هدف صرفا نمایش وهله سازی خودکار این وابستگی است
            Console.WriteLine("UsersService ctor.");
        }

        public string GetUserEmail(int userId)
        {
            _i++;
            Console.WriteLine("i:{0}", _i);
            //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه
            return "name@site.com";
        }
    }
}
به عبارتی می‌خواهیم بدانیم این کلاس چه زمانی وهله سازی مجدد می‌شود. آیا در حالت فراخوانی ذیل،
 //نمونه‌ای از نحوه استفاده از تزریق وابستگی‌های خودکار
var emailsService1 = ObjectFactory.GetInstance<IEmailsService>();
emailsService1.SendEmailToUser(userId: 1, subject: "Test1", body: "Hello!");

var emailsService2 = ObjectFactory.GetInstance<IEmailsService>();
emailsService2.SendEmailToUser(userId: 1, subject: "Test2", body: "Hello!");
ما شاهد چاپ عدد 2 خواهیم بود یا عدد یک:
 UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
همانطور که ملاحظه می‌کنید، به ازای هربار فراخوانی ObjectFactory.GetInstance، یک وهله جدید ایجاد شده است. بنابراین مقدار i در هر دو بار مساوی عدد یک است.
اگر به هر دلیلی نیاز بود تا این رویه تغییر کند، می‌توان بر روی طول عمر اشیاء تشکیل شده نیز تاثیر گذار بود. برای مثال تنظیمات آغازین برنامه را به نحو ذیل تغییر دهید:
// تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
ObjectFactory.Initialize(x =>
{
   x.For<IEmailsService>().Use<EmailsService>();
   x.For<IUsersService>().Singleton().Use<UsersService>();
});
اینبار اگر برنامه را اجرا کنیم، به خروجی ذیل خواهیم رسید:
 UsersService ctor.
EmailsService ctor.
i:1
SendEmailTo(name@site.com)
EmailsService ctor.
i:2
SendEmailTo(name@site.com)
بله. با Singleton معرفی کردن تنظیمات UsersService، تنها یک وهله از این کلاس ایجاد خواهد شد و نهایتا در فراخوانی دوم ObjectFactory.GetInstance، شاهد عدد i مساوی 2 خواهیم بود (چون از یک وهله استفاده شده است).

حالت‌های دیگر تعیین طول عمر مطابق متدهای زیر هستند:
 Singleton()
HttpContextScoped()
HybridHttpOrThreadLocalScoped()
با انتخاب حالت HttpContext، به ازای هر HttpContext ایجاد شده، کلاس معرفی شده یکبار وهله سازی می‌گردد.
در حالت ThreadLocal، به ازای هر Thread، وهله‌ای متفاوت در اختیار مصرف کننده قرار می‌گیرد.
حالت Hybrid ترکیبی است از حالت‌های HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ می‌کند.

شاید بپرسید که کاربرد مثلا HttpContextScoped در کجا است؟
در یک برنامه وب نیاز است تا یک وهله از DbContext (مثلا Entity framework) را در اختیار کلاس‌های مختلف لایه سرویس قرار داد. به این ترتیب چون هربار new Context صورت نمی‌گیرد، هربار هم اتصال جداگانه‌ای به بانک اطلاعاتی باز نخواهد شد. نتیجه آن رسیدن به یک برنامه سریع، با سربار کم و همچنین کار کردن در یک تراکنش واحد است. چون هربار فراخوانی new Context به معنای ایجاد یک تراکنش جدید است.
همچنین در این برنامه وب قصد نداریم از حالت طول عمر Singleton استفاده کنیم، چون در این حالت یک وهله از Context در اختیار تمام کاربران سایت قرار خواهد گرفت (و DbContext به صورت Thread safe طراحی نشده است). نیاز است به ازای هر کاربر و به ازای طول عمر هر درخواست، تنها یکبار این وهله سازی صورت گیرد. بنابراین در این حالت استفاده از HttpContextScoped توصیه می‌شود. به این ترتیب در طول عمر کوتاه Object graph‌های تشکیل شده، فقط یک وهله از DbContext ایجاد و استفاده خواهد شد که بسیار مقرون به صرفه است.
مزیت دیگر مشخص سازی طول عمر به نحو HttpContextScoped، امکان Dispose خودکار آن به صورت زیر است:
protected void Application_EndRequest(object sender, EventArgs e)  
{  
  ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();  
}

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

اگر نام اینترفیس‌های شما فقط یک I در ابتدا بیشتر از نام کلاس‌های متناظر با آن‌ها دارد، مثلا مانند ITest و کلاس Test هستند؛ فقط کافی است از قراردادهای پیش فرض StructureMap برای اسکن یک یا چند اسمبلی استفاده کنیم:
 // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
ObjectFactory.Initialize(x =>
{
   //x.For<IEmailsService>().Use<EmailsService>();
   //x.For<IUsersService>().Singleton().Use<UsersService>();  
   x.Scan(scan =>
   {
       scan.AssemblyContainingType<IEmailsService>();
       scan.WithDefaultConventions();
   });  
});
در این حالت دیگر نیازی نیست به ازای اینترفیس‌های مختلف و کلاس‌های مرتبط با آن‌ها، تنظیمات اضافه‌تری را تدارک دید. کار یافتن و برقراری اتصالات لازم در اینجا خودکار خواهد بود.


دریافت مثال قسمت جاری
DI03.zip

به روز شده‌ی این مثال‌ها را بر اساس آخرین تغییرات وابستگی‌های آن‌ها از مخزن کد ذیل می‌توانید دریافت کنید:
Dependency-Injection-Samples
 
بازخوردهای پروژه‌ها
نحوه تبدیل تاریخ میلادی به شمسی
با سلام و تشکر از پروژه عالی شما،
شما برای تبدیل تاریخ میلادی به شمسی از این کلاس استفاده کردید.
using Persia;

namespace Iris.Utilities.DateAndTime
{
    public class DateAndTime
    {
        public static DateTime GetDateTime()
        {
            return DateTime.Now;
        }

        public static string ConvertToPersian(DateTime dateTime, string mod = "")
        {
            SolarDate solar = Calendar.ConvertToPersian(dateTime);
            return string.IsNullOrEmpty(mod) ? solar.ToString() : solar.ToString(mod);
        }
    }
}
یک تابع استاتیک که تاریخ و مد استرینگ رو برای تبدیل میگیره و توسط یه DLL به نام Pershia تبدیل را انجام میده.
من یه روش دیگه میشناسم ( شاید شما هم بلد باشید)
استفاده از این کلاس
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

namespace GSD.Globalization
{
    /// <summary>
    /// <Publisher>http://www.Sayan.ir</Publisher>
    /// <Author>Maziar Rezaie</Author>
    /// </summary>
    public class PersianCulture : CultureInfo
    {
        private readonly Calendar cal;
        private readonly Calendar[] optionals;

        /// <summary>
        /// کد رو بخوان تا بفهمی
        /// </summary>
        /// <param name="cultureName">fa-IR</param>
        /// <param name="useUserOverride">true</param>
        /// <remarks>لطفا در هنگام استفاده به سایت سایان اشاره کنید.</remarks>
        public PersianCulture()
            : this("fa-IR", true)
        {
        }

        public PersianCulture(string cultureName, bool useUserOverride)
            : base(cultureName, useUserOverride)
        {
            //Temporary Value for cal.
            cal = base.OptionalCalendars[0];

            //populating new list of optional calendars.
            var optionalCalendars = new List<Calendar>();
            optionalCalendars.AddRange(base.OptionalCalendars);
            optionalCalendars.Insert(0, new PersianCalendar());


            Type formatType = typeof(DateTimeFormatInfo);
            Type calendarType = typeof(Calendar);


            PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic);
            FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars",
                                                                  BindingFlags.Instance | BindingFlags.NonPublic);

            //populating new list of optional calendar ids
            var newOptionalCalendarIDs = new Int32[optionalCalendars.Count];
            for (int i = 0; i < newOptionalCalendarIDs.Length; i++)
                newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null);

            optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs);

            optionals = optionalCalendars.ToArray();
            cal = optionals[0];
            DateTimeFormat.Calendar = optionals[0];

            DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };
            DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };
            DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };
            DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };


            DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
            DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
            DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" };

            DateTimeFormat.AMDesignator = "ق.ظ";
            DateTimeFormat.PMDesignator = "ب.ظ";

            /*
            DateTimeFormat.ShortDatePattern = "yyyy/MM/dd";
            DateTimeFormat.LongDatePattern = "yyyy/MM/dd";
            
            DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd');
            DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D');
            DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y');
            DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y');
             */

        }

        public override Calendar Calendar
        {
            get { return cal; }
        }

        public override Calendar[] OptionalCalendars
        {
            get { return optionals; }
        }
    }
}

این کلاس به این صورت کار میکنه که در تمام برنامه‌ی شما تاریخ رو به صورت شمسی نشون میده ولی در ذخیره در بانک اطلاعاتی و کار در برنامه به صورت میلادی کار میکنه.
برای استفاده از اون هم باید به Application_BeginRequest  فایل Global.asax این کد‌ها رو اضافه کنید.   
using GSD.Globalization;
using System.Threading; 
 protected void Application_BeginRequest(object sender, EventArgs e)
        {
            var persianCulture = new PersianCulture();
            Thread.CurrentThread.CurrentCulture = persianCulture;
            Thread.CurrentThread.CurrentUICulture = persianCulture;
        }

اینم نمونه کد استفاه شده ازش WebApplication2.zip  
سوال من اینه روشی که شما استفاده کردید بهتره ؟ یا این روش؟ البته در روشی که من ارسال کردم به نظرم چون در Application_BeginRequest استفاده شده سر بار بیشتری داره! ولی همیشه تبدیل رو انجام میده و نیاز هیچ کد نویسی بیشتری نداره (مثل فراخوانی ConvertToPersian رو نیاز نداره)
نظر شما چیه؟
با تشکر از شما و صد تشکر بابت پروژه‌ی خوب و کاربردیتون

مطالب
آشنایی با الگوی طراحی Builder
سناریوی زیر را در نظر بگیرید:
از شما خواسته شده است تا نحوه‌ی ساخت تلفن همراه را پیاده سازی نمایید. شما در گام اول 2 نوع تلفن همراه را شناسایی نموده‌اید (Android و Windows Phone). پس از شناسایی، احتمالا هر کدام از این انواع را یک کلاس در نظر می‌گیرید و به کمک یک واسط یا کلاس انتزاعی، شروع به ساخت کلاس می‌نمایید، تا در آینده اگر تلفن همراه جدیدی شناسایی شد، راحت‌تر بتوان آن را در پیاده سازی دخیل نمود.
اگر چنین فکر کرده اید باید گفت که 90% با الگوی طراحی Builder آشنا هستید و از آن نیز استفاده می‌کنید؛ بدون اینکه متوجه باشید از این الگو استفاده کرده‌اید. در کدهای زیر این الگو را قدم به قدم بررسی خواهیم نمود.
قدم 1: تلفن همراه چه بخش هایی می‌تواند داشته باشد؟ (برای مثال یک OS دارند، یک Name دارند و یک Screen) همچنین برای اینکه تلفن همراهی بتواند ساخته شود ابتدا بایستی نام آن‌را بدانیم. کدهای زیر همین رویه را تصدیق می‌نمایند:
public class Product
{
        public Product(string name)
        {
            Name = name;
        }
        public string Name { get; set; }
        public string Screen { get; set; }
        public string OS { get; set; }
        public override string ToString()
        {
            return string.Format(Screen + "/" + OS + "/" + Name);
        }
}
یک کلاس ساخته‌ایم و نام آن را Product گذاشتیم. بخش‌های مختلفی را نیز در آن تعریف نموده‌ایم. تابع ToString را برای استفاده‌های بعدی override کرده‌ایم (فعلا نیازی بدان نداریم).
قدم 2: برای ساخت تلفن همراه چه کارهایی باید انجام شود؟ (برای مثال بایستی OS روی آن نصب شود، Screen آن مشخص شود. همچنین بایستی به طریقی بتوانم تلفن همراه ساخته شده‌ی خود را نیز پیدا کنم). کدهای زیر همین رویه را تصدیق می‌نمایند:
    public interface IBuilder
    {
        void BuildScreen();
        void BuildOS();
        Product Product { get; }
    }
یک واسط تعریف کرده‌ایم تا به کمک آن هر تلفن همراهی را که خواستیم بسازیم.
قدم 3: از آنجا که فقط دو نوع تلفن همراه را فعلا شناسایی کرده‌ایم (Android و Windows Phone) نیاز داریم تا این دو تا را بسازیم.
ابتدا تلفن همراه Android را می‌سازیم:
  public class ConcreteBuilder1 : IBuilder
    {
        public Product p;
        public ConcreteBuilder1()
        {
            p = new Product("Android Cell Phone");
        }
        public void BuildScreen()
        {
            p.Screen = "Touch Screen 16 Inch!";
        }

        public void BuildOS()
        {
            p.OS = "Android 4.4";
        }
        public Product Product
        {
            get { return p; }
        }
    }
سپس تلفن همراه Windows Phone را می‌سازیم:
    public class ConcreteBuilder2 : IBuilder
    {
        public Product p;

        public ConcreteBuilder2()
        {
            p = new Product("Windows Phone");
        }
        public void BuildScreen()
        {
            p.Screen = "Touch Screen 32 Inch!";
        }

        public void BuildOS()
        {
            p.OS = "Windows Phone 2014";
        }
        public Product Product
        {
            get { return p; }
        }
    }
قدم 4: اول باید OS نصب شود یا Screen مشخص شود؟ برای اینکه توالی کار را مشخص سازم نیاز به یک کلاس دیگر دارم تا اینکار را انجام دهد:
    public class Director
    {
        public void Construct(IBuilder builder)
        {
            builder.BuildScreen();
            builder.BuildOS();
        }
    }
این کلاس در متد Construct خود یک ورودی از نوع IBuilder می‌گیرد و براساس توالی مورد نظر، شروع به ساخت آن می‌کند.
قدم 5: نهایتا میخواهم به برنامه‌ی خود بگویم که تلفن همراه Android را بسازد:
Director d = new Director();
ConcreteBuilder1 cb1 = new ConcreteBuilder1();
d.Construct(cb1);
Console.WriteLine(cb1.p.ToString());
و به این صورت تلفن همراه من آماده است!
متد ToString در اینجا، همان ToString ابتدای بحث است که آن را  Override کردیم.
به این نکته توجه کنید که اگر یک تلفن همراه جدید شناسایی شود، چه مقدار تغییری در کدها نیاز دارید؟ برای مثال تلفن همراه BlackBerry شناسایی شده‌است. تنها کاری که لازم است این است که یک کلاس بصورت زیر ساخته شود:
    public class BlackBerry: IBuilder
    {
        public Product p;

        public BlackBerry ()
        {
            p = new Product("BlackBerry");
        }
        public void BuildScreen()
        {
            p.Screen = "Touch Screen 8 Inch!";
        }

        public void BuildOS()
        {
            p.OS = "BlackBerry XXX";
        }
        public Product Product
        {
            get { return p; }
        }
    }
مطالب
مدیریت سفارشی سطوح دسترسی کاربران در MVC
فرض کنید در پروژه‌ی جاری خودتون قصد دارید یک سیستم مدیریت سطوح دسترسی کاربران رو با انعطاف بالا پیاده سازی کنید . مثلا سیستم شما دارای صفحات مختلفی هستش که هر گروه کاربری اجازه دسترسی به هر صفحه رو نداره ... هدف اینه که شما این گروه‌های کاربری و سطوح دسترسی برای هر گروه رو برای سیستم مشخص کنید .
مثلا فقط کاربرانی که دسترسی admin دارن بتونن به صفحات مدیریتی دسترسی داشته باشن و ....
برای این منظور در دات نت کلاسی با نام Role Provider وجود داره که در ادامه‌ی این مبحث به کار با اون میپردازیم.
مثلا فرض کنید قرار بر اینه که سطوح دسترسی رو از بانک اطلاعاتی استخراج کنیم . کلاس مذکور در فضای نام System.Web.Security قرار گرفته . برای شروع ما نیاز داریم یک نمونه از این کلاس رو پیاده سازی کرده و چند تابع از اون رو بازنویسی کنیم . 

پیاده سازی کلاس به این صورته :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;

namespace Myproject.Security
{
    public class CustomRoleProvider : RoleProvider
    {

    }
}
 خب در مرحله‌ی بعد دو تابع از این کلاس رو بازنویسی میکنیم . اول تابع GetRolesForUser که در این مقاله وظیفه‌ی استخراج لیست مجوز‌ها برای هر کاربر رو از بانک اطلاعاتی داره که به شکل زیر پیاده سازی میشه .
public override string[] GetRolesForUser(string username)
        {
            using (DatabaseEntities db = new DatabaseEntities())
            {
                User user = db.Users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.CurrentCultureIgnoreCase));
 
                var roles = from ur in user.UserRoles
                            from r in db.Roles
                            where ur.RoleId == r.Id
                            select r.Name;
                if (roles != null)
                    return roles.ToArray();
                else
                    return new string[] {}; ;
            }
        }
 همونطور که میبینید در تابع بالا از کلاس CustomRoleProvider ما عملیات استخراج لیست مجوز‌های دسترسی مربوط به هر کاربر رو از بانک اطلاعاتی انجام دادیم . توجه داشته باشین که شما میتونید این واکشی رو از هر محیط دیگه ای جز بانک اطلاعاتی هم بسته به نوع کارتون انجام بدید .

تابع بعد IsUserInRole نام داره که با بازنویسی اون مشخص میکنیم که ایا یک کاربر دارای مجوز لازم برای دسترسی هست یا نه . اون رو به شکل زیر بازنویسی میکنیم.
public override bool IsUserInRole(string username, string roleName)
        {
            using (DatabaseEntities db = new DatabaseEntities())
            {
                User user = db.Users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.CurrentCultureIgnoreCase));
 
                var roles = from ur in user.UserRoles
                            from r in db.Roles
                            where ur.RoleId == r.Id
                            select r.Name;
                if (user != null)
                    return roles.Any(r => r.Equals(roleName, StringComparison.CurrentCultureIgnoreCase));
                else
                    return false;
            }
        }
 همونطور که شاهد هستید در تابع بالا بعد از واکشی لیست مجوز‌های ثبت شده برای هر کاربر بررسی انجام میشه که ایا اولا کاربر یک کاربر ثبت شده هست و ثانیا اینکه ایا درخواستی که ارسال کرده برای دسترسی به یک بخش مجوز اون رو داره یا خیر ... 

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

public override bool IsUserInRole(string username, string roleName)
        {
  return this.GetRolesForUser(username).Contains(roleName);
        }

خب میرسیم به بخش معرفی این Provider در web.config که به صورت زیر انجام میشه ...
<system.web>
    ...
    <rolemanager cacherolesincookie="true" defaultprovider="CustomRoleProvider" enabled="true">
        <providers>
            <clear />
            <add name="CustomRoleProvider" type="Myproject.Security.CustomRoleProvider" />
        </providers>
    </rolemanager>
    ...
</system.web>

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

حالا به این صورت میتونیم مثلا یک Controller رو محافظت  کنیم در برابر درخواست از جانب کاربرانی که سطح دسترسی به اون رو ندارند .
using System;
using System.Web.Mvc;
 
namespace MyProject.Areas.Admin.Controllers
{
    [Authorize(Roles = "Administrators")]
    public class HomeController : Controller
    {
        //
        // GET: /Admin/Home/
 
        public ActionResult Index()
        {
            return View();
        }
 
    }
}
 توجه داشته باشید که کنترل مجوز برای بررسی  وجود مجوز در بخش‌های کوچکتر هم مانند اکشن‌ها و ... میتونه در نظر گرفته بشه .
نظرات مطالب
افزودن و اعتبارسنجی خودکار Anti-Forgery Tokens در برنامه‌های Angular مبتنی بر ASP.NET Core
یک چنین پیاده سازی خواهد داشت:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Helpers;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Mvc;
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;

namespace NgxAntiforgeryWebApi.Providers
{
    public class XsrfCookieGeneratorAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var xsrfTokenCookie = new HttpCookie("XSRF-TOKEN")
            {
                Value = ComputeXsrfTokenValue(),
                HttpOnly = false // Now JavaScript is able to read the cookie
            };
            HttpContext.Current.Response.AppendCookie(xsrfTokenCookie);
        }

        private string ComputeXsrfTokenValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return $"{cookieToken}:{formToken}";
        }
    }

    public class XsrfTokensValidationAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            IEnumerable<string> headerValues;
            if (!actionContext.Request.Headers.TryGetValues("X-XSRF-TOKEN", out headerValues))
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) { ReasonPhrase = "X-XSRF-TOKEN header is missing." };
                return;
            }

            if (headerValues == null)
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) { ReasonPhrase = "X-XSRF-TOKEN header value is empty." };
                return;
            }

            var xsrfTokensValue = headerValues.FirstOrDefault();
            if (string.IsNullOrEmpty(xsrfTokensValue) || !xsrfTokensValue.Contains(":"))
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) { ReasonPhrase = "X-XSRF-TOKEN header value is null." };
                return;
            }

            var values = xsrfTokensValue.Split(':');
            if (values.Length != 2)
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) { ReasonPhrase = "X-XSRF-TOKEN header value is malformed." };
                return;
            }

            var cookieToken = values[0];
            var formToken = values[1];

            try
            {
                AntiForgery.Validate(cookieToken, formToken);
            }
            catch (HttpAntiForgeryException ex)
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) {  ReasonPhrase = ex.Message };
            }
        }
    }
}
XsrfCookieGeneratorAttribute کار تولید کوکی مخصوص Angular را انجام می‌دهد (می‌تواند به عنوان فیلتر سراسری معرفی شود و یا فقط یکبار پس از لاگین، کوکی آن‌را به روشی که عنوان شده، تولید کنید؛ بدون نیاز به تولید هرباره‌ی آن با هر درخواستی) و XsrfTokensValidationAttribute بجای ValidateAntiForgeryToken اصلی بکار خواهد رفت.
نظرات مطالب
EF Code First #12
اگر از VS 2013 و EF 6 استفاده می‌کنید، حالت DB First آن در حقیقت مهندسی معکوس دیتابیس موجود به حالت Code first است. برای مثال اگر این فایل DbModel.edmx را تولید کند، ذیل آن فایل DbModel.Context.tt مشخص است که حاصل آن تولید خودکار یک چنین کلاسی است:
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from a template.
//
//     Manual changes to this file may cause unexpected behavior in your application.
//     Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace EFDbFirstDependencyInjection.DataLayer
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    
    public partial class TestDbIdentityEntities : DbContext
    {
        public TestDbIdentityEntities()
            : base("name=TestDbIdentityEntities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public virtual DbSet<Category> Categories { get; set; }
        public virtual DbSet<Product> Products { get; set; }
    }
}
این کلاس دقیقا از DbContext حالت Code first استفاده می‌کند و کلا ObjectContext قدیمی را کنار گذاشته‌اند (حتی برای حالت DB First).


بنابراین تمام نکات مطلب جاری در مورد حالت DB First موجود در VS 2013 صادق است. فقط باید فایل DbModel.Context.tt آن‌را اصلاح کنید تا IUnitOfWork را به صورت خودکار به انتهای تعریف کلاس Context اضافه کند. مابقی مسایل آن یکی است.
مطالب
ویژگی Batching در EF Core
در EF 6.x به ازای هر عبارت insert/update/delete یکبار رفت و برگشت به بانک اطلاعاتی صورت می‌گیرد. به همین جهت کارآیی تعداد بالای ثبت، به روز رسانی و حذف رکوردها توسط آن پایین است. برای رفع این مشکل ویژگی Batching به EF Core اضافه شده‌است که توسط آن اینبار دسته‌ای از عبارات را به صورت یکجا و در طی یک رفت و برگشت، به سمت بانک اطلاعاتی ارسال می‌کند. به این ترتیب کارآیی و سرعت insert/update/delete آن به شدت افزایش خواهد یافت.


نحوه‌ی فعالسازی Batching در EF Core

Batching به صورت پیش فرض در EF Core بدون نیاز به هیچگونه تنظیم اضافه‌تری فعال است. اما اگر خواستید برای مثال، حالت پیش فرض EF 6.x را توسط آن شبیه سازی کنید، می‌توانید مقدار MaxBatchSize را به عدد 1 تنظیم نمائید (تا غیرفعال شود):
optionsBuilder.UseSqlServer(
   @"Server=(localdb)\mssqllocaldb;Database=Demo.Batching;Trusted_Connection=True;",
   options => options.MaxBatchSize(1)
);

مقدار پیش فرض MaxBatchSize را در کلاس SqlServerModificationCommandBatch می‌توانید مشاهده کنید:
public class SqlServerModificationCommandBatch : AffectedCountModificationCommandBatch
    {
        private const int DefaultNetworkPacketSizeBytes = 4096;
        private const int MaxScriptLength = 65536 * DefaultNetworkPacketSizeBytes / 2;
        private const int MaxParameterCount = 2100;
        private const int MaxRowCount = 1000;
در اینجا MaxRowCount همان MaxBatchSize پیش فرض است که به عدد 1000 تنظیم شده‌است. بنابراین اگر تنظیم options => options.MaxBatchSize(1) را ذکر نکنید، به معنای ارسال 1000 تایی دستورات insert/update/delete در طی یک درخواست به سمت سرور است.


آیا محدودیتی هم در مورد عملیات Batching وجود دارد؟

SQL Server به ازای هر batch تنها 2100 پارامتر را پشتیبانی می‌کند. در این حالت EF Core به صورت خودکار یک چنین کوئری‌های حجیمی را به چند Batch جهت تنظیم این محدودیت تقسیم خواهد کرد و در نهایت برنامه به مشکلی بر نمی‌خورد.


یک آزمایش: Batching پیش فرض به چه صورتی کار می‌کند و چه اثری را دارد؟

کدهای کامل این آزمایش را از اینجا می‌توانید دریافت کنید: Batching.zip
در اینجا کلاس Blog را به همراه Context متناظر با آن مشاهده می‌کنید:
    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
    }

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Demo.Batching;Trusted_Connection=True;"/*,
                options => options.MaxBatchSize(2)*/
                );
            optionsBuilder.EnableSensitiveDataLogging();
        }
    }
در ابتدا MaxBatchSize را تنظیم نخواهیم کرد. یعنی از همان عدد 1000 پیش فرض استفاده می‌شود. تنظیم EnableSensitiveDataLogging نیز سبب می‌شود تا لاگ نهایی تهیه شده جهت نمایش، پرمحتواتر شود.
در این حالت اگر به روز رسانی‌ها (2 مورد) و ثبت‌های ذیل (6 مورد) را انجام دهیم:
            using (var db = new BloggingContext())
            {
                db.GetService<ILoggerFactory>().AddProvider(new MyLoggerProvider());

                // Modify some existing blogs
                var existing = db.Blogs.ToArray();
                existing[0].Url = "http://sample.com/blogs/dogs";
                existing[1].Url = "http://sample.com/blogs/cats";

                // Insert some new blogs
                db.Blogs.Add(new Blog { Name = "The Horse Blog", Url = "http://sample.com/blogs/horses" });
                db.Blogs.Add(new Blog { Name = "The Snake Blog", Url = "http://sample.com/blogs/snakes" });
                db.Blogs.Add(new Blog { Name = "The Fish Blog", Url = "http://sample.com/blogs/fish" });
                db.Blogs.Add(new Blog { Name = "The Koala Blog", Url = "http://sample.com/blogs/koalas" });
                db.Blogs.Add(new Blog { Name = "The Parrot Blog", Url = "http://sample.com/blogs/parrots" });
                db.Blogs.Add(new Blog { Name = "The Kangaroo Blog", Url = "http://sample.com/blogs/kangaroos" });

                db.SaveChanges();
            }
یک چنین خروجی SQL ایی تولید می‌شود:
Executed DbCommand (41ms) [Parameters=[@p1='57', @p0='http://sample.com/blogs/dogs' (Size = 4000), @p3='58', @p2='http://sample.com/blogs/cats' (Size = 4000), @p4='The Horse Blog' (Size = 4000), @p5='http://sample.com/blogs/horses' (Size = 4000), @p6='The Snake Blog' (Size = 4000), @p7='http://sample.com/blogs/snakes' (Size = 4000), @p8='The Fish Blog' (Size = 4000), @p9='http://sample.com/blogs/fish' (Size = 4000), @p10='The Koala Blog' (Size = 4000), @p11='http://sample.com/blogs/koalas' (Size = 4000), @p12='The Parrot Blog' (Size = 4000), @p13='http://sample.com/blogs/parrots' (Size = 4000), @p14='The Kangaroo Blog' (Size = 4000), @p15='http://sample.com/blogs/kangaroos' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [Blogs] SET [Url] = @p0
WHERE [BlogId] = @p1;
SELECT @@ROWCOUNT;

UPDATE [Blogs] SET [Url] = @p2
WHERE [BlogId] = @p3;
SELECT @@ROWCOUNT;

DECLARE @inserted2 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p4, @p5, 0),
(@p6, @p7, 1),
(@p8, @p9, 2),
(@p10, @p11, 3),
(@p12, @p13, 4),
(@p14, @p15, 5)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted2;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted2 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];
در این دستورات موارد ذیل قابل توجه هستند:
- فقط یکبار Executed DbCommand مشاهده می‌شود.
- کل دستورات update و insert در طی یک درخواست و یک تراکنش به سمت بانک اطلاعاتی ارسال شده‌اند.
- ثبت دسته‌ای توسط merge using انجام شده‌است.
- در آخر نیز طبق معمول کار EF، شماره Idهای رکوردهای ثبت شده به سمت کلاینت بازگشت داده می‌شود.

در ادامه MaxBatchSize را به عدد 2 تنظیم می‌کنیم:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
   optionsBuilder.UseSqlServer(
     @"Server=(localdb)\mssqllocaldb;Database=Demo.Batching;Trusted_Connection=True;",
     options => options.MaxBatchSize(2)
    );
    optionsBuilder.EnableSensitiveDataLogging();
}
در این حالت اگر برنامه را اجرا کنیم، یک چنین خروجی قابل مشاهده خواهد بود:
Executed DbCommand (17ms) [Parameters=[@p1='65', @p0='http://sample.com/blogs/dogs' (Size = 4000), @p3='66', @p2='http://sample.com/blogs/cats' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [Blogs] SET [Url] = @p0
WHERE [BlogId] = @p1;
SELECT @@ROWCOUNT;

UPDATE [Blogs] SET [Url] = @p2
WHERE [BlogId] = @p3;
SELECT @@ROWCOUNT;

Executed DbCommand (18ms) [Parameters=[@p0='The Horse Blog' (Size = 4000), @p1='http://sample.com/blogs/horses' (Size = 4000), @p2='The Snake Blog' (Size = 4000), @p3='http://sample.com/blogs/snakes' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted0;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted0 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];

Executed DbCommand (34ms) [Parameters=[@p0='The Fish Blog' (Size = 4000), @p1='http://sample.com/blogs/fish' (Size = 4000), @p2='The Koala Blog' (Size = 4000), @p3='http://sample.com/blogs/koalas' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted0;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted0 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];

Executed DbCommand (15ms) [Parameters=[@p0='The Parrot Blog' (Size = 4000), @p1='http://sample.com/blogs/parrots' (Size = 4000), @p2='The Kangaroo Blog' (Size = 4000), @p3='http://sample.com/blogs/kangaroos' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([BlogId] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p0, @p1, 0),
(@p2, @p3, 1)) AS i ([Name], [Url], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name], [Url])
VALUES (i.[Name], i.[Url])
OUTPUT INSERTED.[BlogId], i._Position
INTO @inserted0;

SELECT [t].[BlogId] FROM [Blogs] t
INNER JOIN @inserted0 i ON ([t].[BlogId] = [i].[BlogId])
ORDER BY [i].[_Position];
در این دستورات موارد ذیل قابل توجه هستند:
- اینبار تعداد 4 دستور Executed DbCommand مشاهده می‌شود ( برای انجام 2 به روز رسانی و 6 ثبت).
- هر batch بر اساس تنظیم MaxBatchSize به 2 دستور T-SQL محدود شده‌است که البته در انتها در حالت‌های insert، یک select هم برای بازگشت Idها به سمت کلاینت وجود دارد.
بنابراین اینبار بجای یکبار رفت و برگشت حالت قبل (استفاده از مقدار پیش فرض 1000 برای MaxBatchSize)، 4 بار رفت و برگشت به سمت بانک اطلاعاتی صورت گرفته‌است.

زمان کل انجام عملیات در حالت اول 41 میلی ثانیه و در حالت دوم 84 میلی ثانیه است که سرعت آن 51 درصد نسبت به حالت اول کاهش یافته‌است.