نظرات مطالب
انجام کارهای پس زمینه در ASP.NET 4.5.2
با سلام؛ من می‌خوام یک کار در پس زمینه انجام بدم به صورتی که یک تابع را با چند آرگمان صدا بزنم و این تابع در یک تسک جدید اجرا بشود، به طوری که اگر من این تابع را هر چند بار که صدا بزنم همه آنها در یک تسک جدید و مجزا از هم اجرا بشود.
من چطوری میتونم این کار را انجام بدهم؟ با تشکر.
مطالب
مقایسه کارآیی روش‌های مختلف جایگزین کردن حروف در یک رشته در برنامه‌های NET.
فرض کنید قصد دارید عملیات نرمال سازی اطلاعات را بر روی یک رشته انجام داده و برای مثال اعداد فارسی و انگلیسی موجود در یک رشته را یک‌دست کنید. اولین روشی که برای اینکار به ذهن می‌رسد، استفاده از متد Replace است:
private static string toPersianNumbersUsingReplace(string data)
{
    if (string.IsNullOrWhiteSpace(data)) return string.Empty;
    return
      data
        .Replace("0", "\u06F0")
        .Replace("1", "\u06F1")
        .Replace("2", "\u06F2")
        .Replace("3", "\u06F3")
        .Replace("4", "\u06F4")
        .Replace("5", "\u06F5")
        .Replace("6", "\u06F6")
        .Replace("7", "\u06F7")
        .Replace("8", "\u06F8")
        .Replace("9", "\u06F9");
}
اما آیا این روش، کارآیی مناسبی را به همراه دارد؟ در ادامه چند روش دیگر را نیز جهت جایگزین کردن حروف، معرفی کرده و کارآیی آن‌ها را با هم مقایسه می‌کنیم.


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

نگارش اصلی تبدیل تمام اعداد موجود در یک رشته به اعداد فارسی، به صورت زیر است که در آن یک دست سازی اعداد عربی هم درنظر گرفته شده‌اند (برای مثال طرز نگارش عدد 4 فارسی و عربی متفاوت است):
        private static string toPersianNumbersUsingReplace(string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return string.Empty;
            return
                toEnglishNumbers(data)
                .Replace("0", "\u06F0")
                .Replace("1", "\u06F1")
                .Replace("2", "\u06F2")
                .Replace("3", "\u06F3")
                .Replace("4", "\u06F4")
                .Replace("5", "\u06F5")
                .Replace("6", "\u06F6")
                .Replace("7", "\u06F7")
                .Replace("8", "\u06F8")
                .Replace("9", "\u06F9");
        }

        private static string toEnglishNumbers(string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return string.Empty;
            return
               data.Replace("\u0660", "0") //٠
                   .Replace("\u06F0", "0") //۰
                   .Replace("\u0661", "1") //١
                   .Replace("\u06F1", "1") //۱
                   .Replace("\u0662", "2") //٢
                   .Replace("\u06F2", "2") //۲
                   .Replace("\u0663", "3") //٣
                   .Replace("\u06F3", "3") //۳
                   .Replace("\u0664", "4") //٤
                   .Replace("\u06F4", "4") //۴
                   .Replace("\u0665", "5") //٥
                   .Replace("\u06F5", "5") //۵
                   .Replace("\u0666", "6") //٦
                   .Replace("\u06F6", "6") //۶
                   .Replace("\u0667", "7") //٧
                   .Replace("\u06F7", "7") //۷
                   .Replace("\u0668", "8") //٨
                   .Replace("\u06F8", "8") //۸
                   .Replace("\u0669", "9") //٩
                   .Replace("\u06F9", "9"); //۹
        }


جایگزین کردن حروف با استفاده از Replace معمولی توسط کاراکترها

اینبار همان حالت قبل را درنظر بگیرید؛ با این تفاوت که بجای رشته‌ها از کاراکترها استفاده شود. برای مثال بجای:
  .Replace("\u0669", "9") //٩
خواهیم داشت:
  .Replace('\u0669', '9') //٩


جایگزین کردن حروف با استفاده از String Builder

در ادامه بجای استفاده از متد Replace متداول، آرایه‌ای از حروف قابل جایگزینی را توسط یک StringBuilder ایجاد کرده و حروف را یکی یکی تبدیل می‌کنیم و به این ترتیب برخلاف متد Replace، هربار برای جایگزینی یک مورد خاص، مجددا از ابتدای رشته شروع به جستجو نمی‌شود:
        private static string toPersianNumbersUsingStringBuilder(string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return string.Empty;

            var strBuilder = new StringBuilder(data);
            for (var i = 0; i < strBuilder.Length; i++)
            {
                switch (strBuilder[i])
                {
                    case '0':
                    case '\u0660':
                        strBuilder[i] = '\u06F0';
                        break;

                    case '1':
                    case '\u0661':
                        strBuilder[i] = '\u06F1';
                        break;

                    case '2':
                    case '\u0662':
                        strBuilder[i] = '\u06F2';
                        break;

                    case '3':
                    case '\u0663':
                        strBuilder[i] = '\u06F3';
                        break;

                    case '4':
                    case '\u0664':
                        strBuilder[i] = '\u06F4';
                        break;

                    case '5':
                    case '\u0665':
                        strBuilder[i] = '\u06F5';
                        break;

                    case '6':
                    case '\u0666':
                        strBuilder[i] = '\u06F6';
                        break;

                    case '7':
                    case '\u0667':
                        strBuilder[i] = '\u06F7';
                        break;

                    case '8':
                    case '\u0668':
                        strBuilder[i] = '\u06F8';
                        break;

                    case '9':
                    case '\u0669':
                        strBuilder[i] = '\u06F9';
                        break;

                    default:
                        strBuilder[i] = strBuilder[i];
                        break;
                }
            }

            return strBuilder.ToString();
        }


جایگزین کردن حروف با استفاده از ToCharArray

متد زیر دقیقا شبیه به حالت استفاده از String Builder است؛ با یک تفاوت مهم: بجای استفاده از String Builder برای تهیه‌ی آرایه‌ای از حروف قابل تغییر، از متد ToCharArray استفاده شده‌است:
        private static string toPersianNumbersUsingToCharArray(string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return string.Empty;

            var letters = data.ToCharArray();
            for (var i = 0; i < letters.Length; i++)
            {
                switch (letters[i])
                {
                    case '0':
                    case '\u0660':
                        letters[i] = '\u06F0';
                        break;

                    // مانند قبل

                }
            }

            return new string(letters);
        }


جایگزین کردن حروف با استفاده از string.Create

string.Create یکی از تازه‌های NET Core. است که امکان تغییر مستقیم یک قطعه string را میسر می‌کند:
        private static string toPersianNumbersUsingStringCreate(string data)
        {
            if (string.IsNullOrWhiteSpace(data)) return string.Empty;

            return string.Create(data.Length, data, (chars, context) =>
            {
                for (var i = 0; i < data.Length; i++)
                {
                    switch (context[i])
                    {
                        case '0':
                        case '\u0660':
                            chars[i] = '\u06F0';
                            break;

                    // مانند قبل

                    }
                }
            });
        }
در کدهای فوق، ابتدا طول رشته‌ی نهایی بازگشتی از string.Create مشخص می‌شود. سپس توسط پارامتر دوم، داده‌هایی که قرار است بر روی آن‌ها کاری صورت گیرد به متد string.Create ارسال می‌شوند. در آخر عملیات نهایی در action delegate تعریف شده رخ می‌دهد. در اینجا chars، به بافر درونی رشته‌ای که بازگشت داده می‌شود، اشاره می‌کند و باید پر شود (این بافر مستقیما در دسترس است). context همان پارامتر دوم متد string.Create است.

توضیحات بیشتر:
در دات نت، رشته‌ها نوع‌های ارجاعی (reference type) غیرقابل تغییر (immutable) هستند. به این معنا که هر زمانیکه ایجاد شدند، دیگر نمی‌توان محتوای آن‌ها را تغییر داد. به همین جهت است که مجبور هستیم آن‌ها را برای مثال توسط ToCharArray به یک آرایه تبدیل کنیم و سپس این آرایه‌ی قابل تغییر را ویرایش کنیم. در حین کار با رشته‌ها، این غیرقابل تغییر بودن، سبب تخصیص حافظه‌های بیش از حدی می‌شوند. اگر بخواهیم قسمتی از یک رشته را جدا و یا جایگزین کنیم و یا تعدادی رشته را با هم جمع بزنیم، نتیجه‌ی آن نیاز به یک تخصیص حافظه‌ی جدید را دارد. راه حل استاندارد مواجه شدن با این مشکل، استفاده از StringBuilder است که از یک بافر داخلی برای انجام کارهای خودش استفاده می‌کند و زمانیکه نتیجه‌ی نهایی را از آن درخواست می‌کنیم، تخصیص حافظه‌ای را برای تولید رشته‌ی حاصل انجام می‌دهد. البته این مورد نیاز به اندازه گیری دارد و ارزش StringBuilder با حجم بالایی از اطلاعات متنی مشخص می‌شود؛ وگرنه همانطور که مشاهده می‌کنید (در نتیجه‌ی نهایی بحث در ادامه)، الزاما کدهای سریعتری را به همراه نخواهد داشت.
هدف از string.Create، ایجاد رشته‌ها از داده‌های موجود است. هدف اصلی آن کاهش تخصیص‌های حافظه و کپی کردن اطلاعات است و امضای آن به صورت زیر می‌باشد:
public static string Create<TState> (int length, TState state, System.Buffers.SpanAction<char,TState> action);
مزیت این متد، عدم نیاز به یک پیش‌بافر است؛ به این معنا که مستقیما بر روی قسمتی از حافظه کار می‌کند که ارجاعی را به رشته‌ی «بازگشتی» دارد. یعنی در حالت کار با string.Create، غیرقابل تغییر بودن رشته‌ها در دات نت دیگر صادق نخواهد بود و برای تغییر آن نیازی به تخصیص بافر، کپی کردن و تخصیص حافظه‌ی نهایی برای بازگشت نتیجه نیست. پارامتر SpanAction آن، امکان دسترسی مستقیم به این ناحیه‌ی از حافظه را میسر می‌کند.
هنگام کار با این متد، chars ای که در اختیار ما قرار می‌گیرد، یک <Span<char اشاره کننده به رشته‌ی نهایی است که قرار است بازگشت داده شود (در ابتدای کار بر اساس اندازه‌ای که مشخص می‌شود، یک رشته‌ی خالی تخصیص داده می‌شود، اما بافر پر کردن آن اینبار در دسترس است و نیازی به تخصیص و کپی جداگانه‌ای را ندارد). بنابراین روش کار با این متد، پر کردن بافر درونی رشته‌ی بازگشتی (همان chars در اینجا) به صورت مستقیم است؛ کاری که با یک رشته‌ی معمولی نمی‌توان انجام داد.
State یا همان پارامتر دوم این متد، هر چیزی می‌تواند باشد. اگر نیاز است چندین رشته را در اینجا دریافت کنید تا بتوان بر اساس آن رشته‌ی نهایی را تشکیل داد، یک struct را تعریف کرده و بجای state به آن ارسال کنید. سپس این state توسط پارامتر context مربوط به SpanAction<char, string> action قابل دریافت و استفاده‌است که در این مثال، context همان data ارسالی به این متد است.

سؤال: در حین کار با string.Create، باید از پارامتر data استفاده کنیم و یا از context دریافتی؟ به نظر در مثال فوق، data و context یکی هستند. اکنون داخل action delegate مهیا که جهت ساخت رشته‌ی نهایی بکار می‌رود، باید از data استفاده کرد و یا از context؟
 return string.Create(data.Length, data, (chars, context) => {});
در اینجا اگر در داخل action delegate، ارجاعی را به data داشته باشیم، یک closure تشکیل می‌شود و در این حالت کامپایلر برای مدیریت آن، نیاز به تولید یک کلاس را در پشت صحنه خواهد داشت که خودش سبب کاهش کارآیی می‌گردد. به همین جهت متد Create، پارامتر state را به صورت معمولی دریافت می‌کند و آن‌را توسط context در اختیار delegate قرار می‌دهد تا نیازی نباشد delegate تعریف شده، یک closure را تشکیل دهد.


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

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: ReplacePerformanceTests.zip


ستون op/s در اینجا، مهم‌ترین ستون گزارش است و به معنای تعداد عملیات قابل انجام در یک ثانیه است. از 670 هزار عملیات در ثانیه با Replace معمولی، به 5 میلیون عملیات در ثانیه رسیده‌ایم که بسیار قابل توجه‌است.
همانطور که مشاهده می‌کنید، string.Create، سریعترین نگارش موجود است. در این بین نگارشی که از ToCharArray استفاده می‌کند، قابلیت انتقال بیشتری را دارد؛ از این جهت که نگارش‌های دیگر NET. هنوز دسترسی به string.Create را ندارند. همچنین نگارش کاراکتری متد Replace، از متد رشته‌ای آن سریعتر عمل کرده‌است.
مطالب
Value Types ارجاعی در C# 7.2
در C# 7.2 می‌توان با value types (مانند structs) همانند reference types (مانند کلاس‌ها) رفتار کرد. جائیکه کارآیی برنامه بسیار حائز اهمیت باشد (مانند بازی‌ها)، استفاده از structs و value types بسیار مرسوم است؛ از این جهت که این نوع‌ها بر روی heap تخصیص داده نمی‌شوند. اما مشکل آن‌ها این است که زمانیکه به متدها ارسال می‌شوند، مقدار آن‌ها ارسال خواهد شد و برای این منظور نیاز به ایجاد یک کپی جدید از آن‌ها می‌باشد. برای رفع این مشکل و کاهش سربار کپی کردن اشیاء، اکنون در C# 7.2 می‌توان value types را همانند reference types به متدها ارسال کرد.


واژه‌ی کلیدی جدید in

C# 7.2‌، واژه‌ی کلیدی جدیدی را به نام in جهت تعریف پارامترها، معرفی کرده‌است. زمانیکه از آن استفاده می‌شود به این معنا است که value type ارسالی به آن، توسط ارجاعی از آن، در اختیار متد قرار می‌گیرد و نه توسط مقدار کپی شده‌ی آن (حالت پیش‌فرض) و همچنین متد استفاده کننده‌ی از آن، مقدار این شیء را تغییر نمی‌دهد.
واژه‌ی کلیدی in مکمل واژه‌های کلیدی ref و out است که پیشتر به همراه زبان #C ارائه شده بودند:
- واژه‌ی کلیدی out: مقدار آرگومان مزین شده‌ی توسط آن، باید درون متد تنظیم شود و صرفا کاربرد ارائه‌ی یک خروجی اضافه‌تر توسط آن متد را دارد.
- واژه‌ی کلیدی ref: مقدار آرگومان مزین شده‌ی توسط آن، ممکن است درون متد تنظیم شود، یا خیر و همچنین توسط ارجاع به آن منتقل می‌شود.
- واژه‌ی کلیدی in: مقدار آرگومان مزین شده‌ی توسط آن، درون متد تغییر نخواهد کرد و همچنین توسط ارجاع به آن منتقل می‌شود.

برای مثال اگر پارامترهای value type متد زیر را از نوع in معرفی کنیم، امکان تغییر مقدار آن‌ها درون متد وجود نخواهد داشت:
public static int Add(in int number1, in int number2)
{
   number1 = 5; // Cannot assign to variable 'in int' because it is a readonly variable
   return number1 + number2;
}
و کامپایلر با صدور خطای readonly بودن پارامتر number1، از انجام اینکار جلوگیری می‌کند


واژه‌ی کلیدی جدید in تا چه اندازه‌ای بر روی کارآیی برنامه تاثیر دارد؟

زمانیکه یک value type را به متدی ارسال می‌کنیم، ابتدا به مکان جدیدی از حافظه کپی شده و سپس مقدار clone شده‌ی آن، به متد ارسال می‌شود. با استفاده از واژه‌ی کلیدی in، دقیقا همان ارجاع به مقدار اولیه، به متد ارسال خواهد شد؛ بدون ایجاد کپی اضافه‌تری از آن. برای بررسی تاثیر این عملیات بر روی کارآیی برنامه، می‌توان از BenchmarkDotNet استفاده کرد. برای این منظور ابتدا ارجاعی را به BenchmarkDotNet اضافه می‌کنیم:
<ItemGroup>
     <PackageReference Include="BenchmarkDotNet" Version="0.10.12" />
</ItemGroup>
سپس متدهایی را که قرار است کارآیی آن‌ها بررسی شوند، با ویژگی Benchmark مزین خواهیم کرد:
using BenchmarkDotNet.Attributes;

namespace CS72Tests
{
    public struct Input
    {
        public decimal Number1;
        public decimal Number2;
    }

    [MemoryDiagnoser]
    public class InBenchmarking
    {
        const int loops = 50000000;
        Input inputInstance = new Input();

        [Benchmark(Baseline = true)]
        public decimal RunNormalLoop_Pass_By_Value()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = Run(inputInstance);
            }
            return result;
        }

        [Benchmark]
        public decimal RunInLoop_Pass_By_Reference()
        {
            decimal result = 0M;
            for (int i = 0; i < loops; i++)
            {
                result = RunIn(in inputInstance);
            }
            return result;
        }

        public decimal Run(Input input)
        {
            return input.Number1;
        }

        public decimal RunIn(in Input input)
        {
            return input.Number1;
        }
    }
}
در آخر برای اجرای آن خواهیم داشت:
static void Main(string[] args)
{
    var summary = BenchmarkRunner.Run<InBenchmarking>();
و در این حالت برنامه را باید توسط دستور «dotnet run -c release» اجرا کرد (اندازه گیری کارآیی در حالت release و نه دیباگ پیش‌فرض)
با این خروجی نهایی:
                      Method |      Mean |    Error |   StdDev | Scaled | Allocated |
---------------------------- |----------:|---------:|---------:|-------:|----------:|
 RunNormalLoop_Pass_By_Value | 280.04 ms | 2.219 ms | 1.733 ms |   1.00 |       0 B |
 RunInLoop_Pass_By_Reference |  91.75 ms | 1.733 ms | 1.780 ms |   0.33 |       0 B |
همانطور که ملاحظه می‌کنید، کارآیی برنامه در حالت استفاده‌ی از پارامترهای in، حداقل 3 برابر شده‌است.


امکان استفاده‌ی از واژه‌ی کلیدی in در حین تعریف متدهای الحاقی

در حین تعریف متدهای الحاقی، واژه‌ی کلیدی in باید پیش از واژه‌ی کلیدی this ذکر شود:
    public static class Factorial
    {
        public static int Calculate(in this int num)
        {
            int result = 1;
            for (int i = num; i > 1; i--)
                result *= i;

            return result;
        }
    }
در این حالت اگر برنامه را به صورت زیر اجرا کنیم (یکبار با ذکر صریح in، بار دیگر بدون in و یکبار هم به صورت فراخوانی متد الحاقی بر روی عدد):
int num = 3;
Console.WriteLine($"(in num) -> {Factorial.Calculate(in num)}");
Console.WriteLine($"(num) -> {Factorial.Calculate(num)}");
Console.WriteLine($"num. -> {num.Calculate()}");
خروجی‌های ذیل را دریافت خواهیم کرد:
(in num) -> 6
(num) -> 6
num. -> 6
به عبارتی حین فراخوانی و استفاده‌ی از متدی که پارامتر آن به صورت in تعریف شده‌است، ذکر in ضروری نیست.

و به طور کلی استفاده‌ی از in در مکان‌های ذیل مجاز است:
• methods
• delegates
• lambdas
• local functions
• indexers
• operators
 

محدودیت‌های استفاده‌ی از پارامترهای in

الف) محدودیت استفاده از پارامترهای in در تعریف overloads
مثال زیر را در نظر بگیرید:
    public class CX
    {
        public void A(Input a)
        {
            Console.WriteLine("int a");
        }

        public void A(in Input a)
        {
            Console.WriteLine("in int a");
        }
    }
در اینجا overloadهای تعریف شده‌ی متد A تنها در ذکر واژه‌ی کلیدی in یا modifier متفاوت هستند.
اگر سعی کنیم وهله‌ای از این کلاس را ایجاد کرده و از متدهای A آن استفاده کنیم:
    public class Y
    {
        public void Test()
        {
            var inputInstance = new Input();
            var cx = new CX();
            cx.A(inputInstance); // The call is ambiguous between the following methods or properties: 'CX.A(Input)' and 'CX.A(in Input)'
        }
    }
خطای کامپایلر مبهم بودن متد A مورد استفاده صادر خواهد شد. یعنی نمی‌توان overload ایی را تعریف کرد که تنها در modifier از نوع in با دیگری متفاوت باشد؛ چون ذکر in در حین فراخوانی متد، اختیاری است.

ب) پارامترهای از نوع in را در متدهای iterator نمی‌توان استفاده کرد:
public IEnumerable<int> B(in int a) // Iterators cannot have ref or out parameters
{
   Console.WriteLine("in int a");
   yield return 1;
}

ج) پارامترهای از نوع in را در متدهای async نمی‌توان استفاده کرد:
public async Task C(in int a) // Async methods cannot have ref or out parameters
{
   await Task.Delay(1000);
}


تاثیر کار با متدهای داخلی تغییر دهنده‌ی وضعیت یک struct

مثال زیر را درنظر بگیرید. به نظر شما خروجی آن چیست؟
using System;

namespace CS72Tests
{
    struct MyStruct
    {
        public int MyValue { get; set; }

        public void UpdateMyValue(int value)
        {
            MyValue = value;
        }
    }

    public static class TestInStructs
    {
        public static void Run()
        {
            var myStruct = new MyStruct();
            myStruct.UpdateMyValue(1);

            UpdateMyValue(myStruct);

            Console.WriteLine(myStruct.MyValue);
        }

        static void UpdateMyValue(in MyStruct myStruct)
        {
            myStruct.UpdateMyValue(5);
        }
    }
}
در اینجا اگر متد TestInStructs.Run را اجرا کنیم، خروجی آن، نمایش عدد 1 خواهد بود.
در ابتدا مقدار struct را به 1 تنظیم و سپس ارجاع آن‌را به متدی دیگر که مقدار آن‌را به 5 تنظیم می‌کند، ارسال کردیم. در این حالت برنامه بدون مشکل کامپایل و اجرا می‌شود.  علت اینجا است که کامپایلر #C زمانیکه متدی را در داخل یک struct فراخوانی می‌کند، یک clone از آن struct را ایجاد کرده و متد را بر روی آن clone اجرا می‌کند؛ چون نمی‌داند که آیا این متد وضعیت و مقدار این struct را تغییر می‌دهد یا خیر. در این حالت کپی اصلی بدون تغییر باقی می‌ماند (در نهایت عدد 1 را مشاهده خواهیم کرد)، اما در آخر فراخوان، ارجاعی از struct را دریافت نکرده و بر روی کپی آن کار می‌کند. بنابراین مزیت بهبود کارآیی، از دست خواهد رفت.

البته در اینجا اگر می‌خواستیم مقدار MyValue را مستقیما تغییر دهیم، کامپایلر از آن جلوگیری می‌کرد و این کد هیچگاه کامپایل نمی‌شد:
static void UpdateMyValue(in MyStruct myStruct)
{
   myStruct.MyValue = 5; // Cannot assign to a member of variable 'in MyStruct' because it is a readonly variable
   myStruct.UpdateMyValue(5);
}
نظرات مطالب
آشنایی با Window Function ها در SQL Server بخش دوم
سلام
جواب سوال اول: در Syntax ‌تابع Row_Number عملیات order by اجباری است، بنابراین عملیات سورت در ابتدا انجام می‌شود و سپس Row_Number (اعداد ترتیبی) روی رکوردها اعمال می‌گردد.
در سا‌یت مایکروسافت به خوبی اشاره شده است که هیچ تضمینی وجود ندارد، خروجی یک Query با استفاده از Row_number در هر بار اجرا، با اجرای قبلی یکی باشد مگر آنکه موارد زیر را رعایت کرده باشید:
1- مقادیر ستونی که برای قسمت Partition در نظر گرفته اید، منحصربفرد باشد.
2- مقادیری که برای قسمت Order by در نظر گرفته اید منحصربفرد باشد.
3- ترکیب مقادیر Partition و Order by نیز مقدار منحصربفردی را ایجاد نماید.
جواب سوال دوم: جای ستون Row_number در زمان نمایش اهمیتی ندارد.
پیشنهاد دوستانه:
1- تاجایی که امکان دارد از OR در Query‌های خود استفاده ننمایید، باعث افزایش زمان اجرای Query شما می‌شود و هزینه بالایی دارد.
2- از Like نیز در نوشتن Query‌های خود اجتناب کنید.
برای اطلاعات بیشتر در مورد Row_Number به آدرس زیر مراجعه نمایید:
موفق باشید.
مطالب
ساخت یک بلاگ ساده با Ember.js، قسمت پنجم
مقدمات ساخت بلاگ مبتنی بر ember.js در قسمت قبل به پایان رسید. در این قسمت صرفا قصد داریم بجای استفاده از HTML 5 local storage از یک REST web service مانند یک ASP.NET Web API Controller و یا یک ASP.NET MVC Controller استفاده کنیم و اطلاعات نهایی را به سرور ارسال و یا از آن دریافت کنیم.


تنظیم Ember data برای کار با سرور

Ember data به صورت پیش فرض و در پشت صحنه با استفاده از Ajax برای کار با یک REST Web Service طراحی شده‌است و کلیه تبادلات آن نیز با فرمت JSON انجام می‌شود. بنابراین تمام کدهای سمت کاربر قسمت قبل نیز در این حالت کار خواهند کرد. تنها کاری که باید انجام شود، حذف تنظیمات ابتدایی آن برای کار با HTML 5 local storage است.
برای این منظور ابتدا فایل index.html را گشوده و سپس مدخل localstorage_adapter.js را از آن حذف کنید:
 <!--<script src="Scripts/Libs/localstorage_adapter.js" type="text/javascript"></script>-->
همچنین دیگر نیازی به store.js نیز نمی‌باشد:
 <!--<script src="Scripts/App/store.js" type="text/javascript"></script>-->

اکنون برنامه را اجرا کنید، چنین پیام خطایی را مشاهده خواهید کرد:

همانطور که عنوان شد، ember data به صورت پیش فرض با سرور کار می‌کند و در اینجا به صورت خودکار، یک درخواست Get را به آدرس http://localhost:25918/posts جهت دریافت آخرین مطالب ثبت شده، ارسال کرده‌است و چون هنوز وب سرویسی در برنامه تعریف نشده، با خطای 404 و یا یافت نشد، مواجه شده‌است.
این درخواست نیز بر اساس تعاریف موجود در فایل Scripts\Routes\posts.js، به سرور ارسال شده‌است:
Blogger.PostsRoute = Ember.Route.extend({
    model: function () {
        return this.store.find('post');
    }
});
Ember data شبیه به یک ORM عمل می‌کند. تنظیمات ابتدایی آن‌را تغییر دهید، بدون نیازی به تغییر در کدهای اصلی برنامه، می‌تواند با یک منبع داده جدید کار کند.


تغییر تنظیمات پیش فرض آغازین Ember data

آدرس درخواستی http://localhost:25918/posts به این معنا است که کلیه درخواست‌ها، به همان آدرس و پورت ریشه‌ی اصلی سایت ارسال می‌شوند. اما اگر یک ASP.NET Web API Controller را تعریف کنیم، نیاز است این درخواست‌ها، برای مثال به آدرس api/posts ارسال شوند؛ بجای /posts.
برای این منظور پوشه‌ی جدید Scripts\Adapters را ایجاد کرده و فایل web_api_adapter.js را با این محتوا به آن اضافه کنید:
 DS.RESTAdapter.reopen({
      namespace: 'api'
});
سپس تعریف مدخل آن‌را نیز به فایل index.html اضافه نمائید:
 <script src="Scripts/Adapters/web_api_adapter.js" type="text/javascript"></script>
تعریف فضای نام در اینجا سبب خواهد شد تا درخواست‌های جدید به آدرس api/posts ارسال شوند.


تغییر تنظیمات پیش فرض ASP.NET Web API

در سمت سرور، بنابر اصول نامگذاری خواص، نام‌ها با حروف بزرگ شروع می‌شوند:
namespace EmberJS03.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }
}
اما در سمت کاربر و کدهای اسکریپتی، عکس آن صادق است. به همین جهت نیاز است که CamelCasePropertyNamesContractResolver را در JSON.NET تنظیم کرد تا به صورت خودکار اطلاعات ارسالی به کلاینت‌ها را به صورت camel case تولید کند:
using System;
using System.Web.Http;
using System.Web.Routing;
using Newtonsoft.Json.Serialization;
 
namespace EmberJS03
{
    public class Global : System.Web.HttpApplication
    {
 
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{id}",
               defaults: new { id = RouteParameter.Optional }
               );
 
            var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}


نحوه‌ی صحیح بازگشت اطلاعات از یک ASP.NET Web API جهت استفاده در Ember data

با تنظیمات فوق، اگر کنترلر جدیدی را به صورت ذیل جهت بازگشت لیست مطالب تهیه کنیم:
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        public IEnumerable<Post> Get()
        {
            return DataSource.PostsList;
        } 
    }
}
با یک چنین خطایی در سمت کاربر مواجه خواهیم شد:
  WARNING: Encountered "0" in payload, but no model was found for model name "0" (resolved model name using DS.RESTSerializer.typeForRoot("0"))
این خطا از آنجا ناشی می‌شود که Ember data، اطلاعات دریافتی از سرور را بر اساس قرارداد JSON API دریافت می‌کند. برای حل این مشکل راه‌حل‌های زیادی مطرح شده‌اند که تعدادی از آن‌ها را در لینک‌های زیر می‌توانید مطالعه کنید:
http://jsonapi.codeplex.com
https://github.com/xqiu/MVCSPAWithEmberjs
https://github.com/rmichela/EmberDataAdapter
https://github.com/MilkyWayJoe/Ember-WebAPI-Adapter
http://blog.yodersolutions.com/using-ember-data-with-asp-net-web-api
http://emadibrahim.com/2014/04/09/emberjs-and-asp-net-web-api-and-json-serialization

و خلاصه‌ی آن‌ها به این صورت است:
خروجی JSON تولیدی توسط ASP.NET Web API چنین شکلی را دارد:
[
  {
    Id: 1,
    Title: 'First Post'
  }, {
    Id: 2,
    Title: 'Second Post'
  }
]
اما Ember data نیاز به یک چنین خروجی دارد:
{
  posts: [{
    id: 1,
    title: 'First Post'
  }, {
    id: 2,
    title: 'Second Post'
  }]
}
به عبارتی آرایه‌ی مطالب را از ریشه‌ی posts باید دریافت کند (مطابق فرمت JSON API). برای انجام اینکار یا از لینک‌های معرفی شده استفاده کنید و یا راه حل ساده‌ی ذیل هم پاسخگو است:
using System.Web.Http;
using EmberJS03.Models;
 
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        public object Get()
        {
            return new { posts = DataSource.PostsList };
        }
    }
}
در اینجا ریشه‌ی posts را توسط یک anonymous object ایجاد کرده‌ایم.
اکنون اگر برنامه را اجرا کنید، در صفحه‌ی اول آن، لیست عناوین مطالب را مشاهده خواهید کرد.


تاثیر قرارداد JSON API در حین ارسال اطلاعات به سرور توسط Ember data

در تکمیل کنترلرهای Web API مورد نیاز (کنترلرهای مطالب و نظرات)، نیاز به متدهای Post، Update و Delete هم خواهد بود. دقیقا فرامین ارسالی توسط Ember data توسط همین HTTP Verbs به سمت سرور ارسال می‌شوند. در این حالت اگر متد Post کنترلر نظرات را به این شکل طراحی کنیم:
 public HttpResponseMessage Post(Comment comment)
کار نخواهد کرد؛ چون مطابق فرمت JSON API ارسالی توسط Ember data، یک چنین شیء JSON ایی را دریافت خواهیم کرد:
{"comment":{"text":"data...","post":"3"}}
بنابراین Ember data چه در حین دریافت اطلاعات از سرور و چه در زمان ارسال اطلاعات به آن، اشیاء جاوا اسکریپتی را در یک ریشه‌ی هم نام آن شیء قرار می‌دهد.
برای پردازش آن، یا باید از راه حل‌های ثالث مطرح شده در ابتدای بحث استفاده کنید و یا می‌توان مطابق کدهای ذیل، کل اطلاعات JSON ارسالی را توسط کتابخانه‌ی JSON.NET نیز پردازش کرد:
namespace EmberJS03.Controllers
{
    public class CommentsController : ApiController
    {
        public HttpResponseMessage Post(HttpRequestMessage requestMessage)
        {
            var jsonContent = requestMessage.Content.ReadAsStringAsync().Result;
            // {"comment":{"text":"data...","post":"3"}}
            var jObj = JObject.Parse(jsonContent);
            var comment = jObj.SelectToken("comment", false).ToObject<Comment>();


            var id = 1;
            var lastItem = DataSource.CommentsList.LastOrDefault();
            if (lastItem != null)
            {
                id = lastItem.Id + 1;
            }
            comment.Id = id;
            DataSource.CommentsList.Add(comment);

            // ارسال آی دی با فرمت خاص مهم است
            return Request.CreateResponse(HttpStatusCode.Created, new { comment = comment });
        }
    }
}
در اینجا توسط requestMessage به محتوای ارسال شده‌ی به سرور که همان شیء JSON ارسالی است، دسترسی خواهیم داشت. سپس متد JObject.Parse، آن‌را به صورت عمومی تبدیل به یک شیء JSON می‌کند و نهایتا با استفاده از متد SelectToken آن می‌توان ریشه‌ی comment و یا در کنترلر مطالب، ریشه‌ی post را انتخاب و سپس تبدیل به شیء Comment و یا Post کرد.
همچنین فرمت return نهایی هم مهم است. در این حالت خروجی ارسالی به سمت کاربر، باید مجددا با فرمت JSON API باشد؛ یعنی باید comment اصلاح شده را به همراه ریشه‌ی comment ارسال کرد. در اینجا نیز anonymous object تهیه شده، چنین کاری را انجام می‌دهد.


Lazy loading در Ember data

تا اینجا اگر برنامه را اجرا کنید، لیست مطالب صفحه‌ی اول را مشاهده خواهید کرد، اما لیست نظرات آن‌ها را خیر؛ از این جهت که ضرورتی نداشت تا در بار اول ارسال لیست مطالب به سمت کاربر، تمام نظرات متناظر با آن‌ها را هم ارسال کرد. بهتر است زمانیکه کاربر یک مطلب خاص را مشاهده می‌کند، نظرات خاص آن‌را به سمت کاربر ارسال کنیم.
در تعاریف سمت کاربر Ember data، پارامتر دوم رابطه‌ی hasMany که با async:true مشخص شده‌است، دقیقا معنای lazy loading را دارد.
Blogger.Post = DS.Model.extend({
   title: DS.attr(),
   body: DS.attr(),
   comments: DS.hasMany('comment', { async: true } /* lazy loading */)
});
در سمت سرور، دو راه برای فعال سازی این lazy loading تعریف شده در سمت کاربر وجود دارد:
الف) Idهای نظرات هر مطلب را به صورت یک آرایه، در بار اول ارسال لیست نظرات به سمت کاربر، تهیه و ارسال کنیم:
namespace EmberJS03.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
 
        // lazy loading via an array of IDs
        public int[] Comments { set; get; } 
    }
}
در اینجا خاصیت Comments، تنها کافی است لیستی از Idهای نظرات مرتبط با مطلب جاری باشد. در این حالت در سمت کاربر اگر مطلب خاصی جهت مشاهده‌ی جزئیات آن انتخاب شود، به ازای هر Id ذکر شده، یکبار دستور Get صادر خواهد شد.
ب) این روش به علت تعداد رفت و برگشت بیش از حد به سرور، کارآیی آنچنانی ندارد. بهتر است جهت مشاهده‌ی جزئیات یک مطلب، تنها یکبار درخواست Get کلیه نظرات آن صادر شود.
برای اینکار باید مدل برنامه را به شکل زیر تغییر دهیم:
namespace EmberJS03.Models
{
    public class Post
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
 
        // load related models via URLs instead of an array of IDs
        // ref. https://github.com/emberjs/data/pull/1371
        public object Links { set; get; }
 
        public Post()
        {
            Links = new { comments = "comments" }; // api/posts/id/comments
        }
    }
}
در اینجا یک خاصیت جدید به نام Links ارائه شده‌است. نام Links در Ember data استاندارد است و از آن برای دریافت کلیه اطلاعات لینک شده‌ی به یک مطلب استفاده می‌شود. با تعریف این خاصیت به نحوی که ملاحظه می‌کنید، اینبار Ember data تنها یکبار درخواست ویژه‌ای را با فرمت api/posts/id/comments، به سمت سرور ارسال می‌کند. برای مدیریت آن، قالب مسیریابی پیش فرض {api/{controller}/{id را می‌توان به صورت {api/{controller}/{id}/{name اصلاح کرد:
namespace EmberJS03
{
    public class Global : System.Web.HttpApplication
    {
 
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{id}/{name}",
               defaults: new { id = RouteParameter.Optional, name = RouteParameter.Optional }
               );
 
            var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}
اکنون دیگر درخواست جدید api/posts/3/comments با پیام 404 یا یافت نشد مواجه نمی‌شود.
در این حالت در طی یک درخواست می‌توان کلیه نظرات را به سمت کاربر ارسال کرد. در اینجا نیز ذکر ریشه‌ی comments همانند ریشه posts، الزامی است:
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        //GET api/posts/id
        public object Get(int id)
        {
            return
                new
                {
                    posts = DataSource.PostsList.FirstOrDefault(post => post.Id == id),
                    comments = DataSource.CommentsList.Where(comment => comment.Post == id).ToList()
                };
        }
    }
}


پردازش‌های async و متد transitionToRoute در Ember.js

اگر متد حذف مطالب را نیز به کنترلر Posts اضافه کنیم:
namespace EmberJS03.Controllers
{
    public class PostsController : ApiController
    {
        public HttpResponseMessage Delete(int id)
        {
            var item = DataSource.PostsList.FirstOrDefault(x => x.Id == id);
            if (item == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            DataSource.PostsList.Remove(item);

            //حذف کامنت‌های مرتبط
            var relatedComments = DataSource.CommentsList.Where(comment => comment.Post == id).ToList();
            relatedComments.ForEach(comment => DataSource.CommentsList.Remove(comment));

            return Request.CreateResponse(HttpStatusCode.OK, new { post = item });
        }
    }
}
قسمت سمت سرور کار تکمیل شده‌است. اما در سمت کاربر، چنین خطایی را دریافت خواهیم کرد:
 Attempted to handle event `pushedData` on  while in state root.deleted.inFlight.
منظور از حالت inFlight در اینجا این است که هنوز کار حذف سمت سرور تمام نشده‌است که متد transitionToRoute را صادر کرده‌اید. برای اصلاح آن، فایل Scripts\Controllers\post.js را باز کرده و پس از متد destroyRecord، متد then را قرار دهید:
Blogger.PostController = Ember.ObjectController.extend({
    isEditing: false,
    actions: {
        edit: function () {
            this.set('isEditing', true);
        },
        save: function () {
            var post = this.get('model');
            post.save();
 
            this.set('isEditing', false);
        },
        delete: function () {
            if (confirm('Do you want to delete this post?')) {
                var thisController = this;
                var post = this.get('model');
                post.destroyRecord().then(function () {
                    thisController.transitionToRoute('posts');
                });
            }
        }
    }
});
به این ترتیب پس از پایان عملیات حذف سمت سرور، قسمت then اجرا خواهد شد. همچنین باید دقت داشت که this اشاره کننده به کنترلر جاری را باید پیش از فراخوانی then ذخیره و استفاده کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
EmberJS03_05.zip
نظرات مطالب
استفاده از توابع Scalar بجای case
در نسخه 2012 جهت سهولت در مهاجرت پایگاه داده‌های Access به SQL Server از توابع CHOOSE و IIF حمایت شده.
 
منتها تابع IIF چندان انعطاف پذیر نیست. مثلا اگر بخواهید به ازای چند حالت مشخص از داده‌های یک فیلد یک مقدار را برگردانید مجبورید چند تابع IIF تودرتو بنویسید. تودرتو بودن این تابع هم به 10 سطح محدود میشه.
اما CASE قابلیت‌ها و انعطاف پذیری بیشتری داره.
سوال میشه گاها کدام یک از این دو Performance یا کارایی بهتر دارد، در جواب میشه گفت هر دو برابر اند. در واقع IIF هنگام اجرا تبدیل به فرم CASE خواهد شد.
فرض کنید یک نظر سنجی تلوزیونی تنظیم کردیم که مردم از طریق پیامک نظر خودشان را به ما اعلام میکنند. شش گزینه هم داریم. برای انتخاب هر گزینه کافیست از اعداد 1 تا 6 استفاده کنیم. حال هنگام نمایش می‌خواهیم به جای اعداد مقدار متناظر ظاهر شود:

Use Tempdb
Go

CREATE TABLE [Sample] (value int);
INSERT INTO [Sample] VALUES (1),(2),(3),(4),(5),(6);
Go

--simple CASE Expression
SELECT value,
       CASE Value 
        WHEN 1 THEN 'Very Bad'
        WHEN 2 THEN 'Bad'
        WHEN 3 THEN 'Not Bad'
        WHEN 4 THEN 'Good'
        WHEN 5 THEN 'Very Good'
        WHEN 6 THEN 'Excellent'
       ELSE NULL
END AS [Result]
FROM [Sample];

--CHOOSE Scalar Function
SELECT value,
       CHOOSE(value,'Very Bad','Bad','Not Bad','Good','Very Good','Excellent')
FROM [Sample];


--nested IIF Scalr Function
SELECT value, 
IIF(value = 1, 'Very Bad',
  IIF(value = 2, 'Bad',
    IIF(value = 3, 'Not Bad',
      IIF(value = 4, 'Good',
        IIF(value = 5, 'Very Good', 'Excellent'
           )
         )
       )
     )
   )
FROM [Sample];
مطالب
روشی برای DeSerialize کردن QueryString به یک کلاس
چند روز پیش در حال استفاده از افزونه‌ی jQuery Bootgrid بودم که داده‌های خود را در قالب زیر به صورت کوئری استرینگ ارسال می‌کند.
current=1&rowCount=10&sort[sender]=asc&searchPhrase=&id=b0df282a-0d67-40e5-8558-c9e93b7befed

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

دوست دارم چیزی شبیه به DeSerialize کردن فرمت json توسط کتابخانه Json.net داشته باشم؛ پس در اولین قدم یک attribute با مشخصات زیر می‌سازیم:
    [AttributeUsage(AttributeTargets.Property,Inherited = true)]
    public class RequestBodyField:Attribute
    {
        public string Field;
        public RequestBodyField(string field)
        {
            this.Field = field;
        }
    }
سپس در کلاس اصلی، ما این خصوصیت‌ها را در بالای propertyها تعریف کرده و با کلید‌های موجود در کوئری استرینگ برابر می‌کنیم:
    public class EmployeesRequestBody
    {
        [RequestBodyField("current")]
        public  int CurrentPage { get; set; }

        [RequestBodyField("rowcount")]
        public int RowCount { get; set; }

        [RequestBodyField("searchPhrase")]
        public string SearchPhrase { get; set; }

        [RequestBodyField("sort")]
        public NameValueCollection SortDictionary { get; set; }
    }
سپس کلاس زیر را می‌نویسیم که وظیفه دارد کلاس‌های از جنس بالا را با query string‌ها رسیده در درخواست مطابقت دهد:
 public T GetFromQueryString<T>() where T : new()
        {
            var obj = new T();

            var queryString = HttpContext.Current.Request.QueryString;
            var queries = HttpUtility.ParseQueryString(queryString.ToString());

            var properties = typeof(T).GetProperties();
            foreach (var property in properties)
            {
                foreach (Attribute attribute in property.GetCustomAttributes(true))
                {
                    var requestBodyField = attribute as RequestBodyField; 
                    if (requestBodyField == null) continue;

                    //get value of query string
                    var valueAsString = queries[requestBodyField.Field];

                    var converter = TypeDescriptor.GetConverter(property.PropertyType);
                    var value = converter.ConvertFrom(valueAsString);

                    if (value == null)
                        continue;

                    property.SetValue(obj, value, null);
                }
            }
            return obj;
        }
این متد یک تعریف کلاس را دریافت می‌کند. سپس رشته‌ی کوئری استرینگ موجود در بدنه درخواست را دریافت کرده و با استفاده از کد زیر اشیا را به صورت nameValueCollection دریافت می‌کنیم.
HttpContext.Current.Request.QueryString
نکته: همچنین متد httputility.parseQueryString یک رشته کوئری استرینگ دریافت می‌کند و کوئری استرینگ را به زوج نام و مقدار nameValueCollection تبدیل میکند.

 سپس در مرحله‌ی بعدی با استفاده از Reflection پراپرتی‌هایی را که دارای attribute تعریف شده هستند، پیدا می‌کنیم.
مقدار داده شده به attribute را در nameValueCollection بررسی می‌کنیم و در صورت موجود بودن، مقدار آن را می‌گیریم. از آنجا که این مقدار از نوع رشته است و ممکن است مقدار داخل آن عددی یا هر نوع دیگری باشد، باید آن را به نوع صحیح تبدیل کنیم که خطوط زیر کار تبدیل را انجام می‌دهند:
   var converter = TypeDescriptor.GetConverter(property.PropertyType);
   var value = converter.ConvertFrom(valueAsString);

در خط اول بر اساس نوع property کلاس، یک converter دریافت می‌کنیم و سپس مقدار ارسال شده را به آن می‌دهیم تا مقدار جدید را با نوع صحیح خود، دریافت کنیم.
سپس در صورتی که مقدار صحیح دریافت شود و برابر null نباشد، مقدار را در پراپرتی مربوطه جا می‌دهیم.

نکته‌ای که در اینجا نیاز به تلاش بیشتر دارد، کلید sort در کوئری استرینگ است. با نگاهی دقیق‌تر متوجه می‌شوید که خود کلید دو مقدار دارد که یکی از مقادیرش با کلید ترکیب شده است. این حالت روش ارسال آرایه‌ها با نام کلیدی متفاوت در کوئری استرینگ است. این حالت ارسال باعث می‌شود که گرید بتواند حالت multi sort را نیز پیاده سازی کند.
پس برای دریافت این نوع مقادیر کمی کد به آن اضافه می‌کنیم. برای دریافت مقادیر آرایه‌ای کد زیر را به سیستم اضافه می‌کنیم:
if (valueAsString == null)
                    {
                        var keys = from key in queries.AllKeys where key.StartsWith(requestBodyField.Field) select key;

                        var collection = new NameValueCollection();

                        foreach (var key in keys)
                        {
                            var openBraketIndex = key.IndexOf("[", StringComparison.Ordinal);
                            var closeBraketIndex = key.IndexOf("]", StringComparison.Ordinal);

                            if (openBraketIndex < 0 || closeBraketIndex < 0)
                                throw new Exception("query string is corrupted.");

                            openBraketIndex++;
                            //get key in [...]
                            var fieldName = key.Substring(openBraketIndex, closeBraketIndex - openBraketIndex);
                            collection.Add(fieldName, queries[key] );
                        }
                        property.SetValue(obj, collection, null);
                        continue;
                    }
در صورتیکه شما کلید sort را درخواست کنید و از آنجا که کلید اصلی با نام [sort[sender است، مقدار null بازگشت می‌دهد. پس ما می‌توانیم به این مقدار شک کنیم که شاید این کلید حاوی مقدار مورد نظر ماست؛ پس این حالت را بررسی میکنیم.  برای بررسی، با استفاده از linq بررسی می‌کنیم که اگر کلید‌های namValueCollection با این کلید (در اینجا sort) آغاز می‌شوند، پس به احتمال زیاد همان حالت مورد نظر ما رخ داه است. پس اندیس‌های [ و ] را می‌گیریم و اگر اندیس هر دو بزرگتر از صفر بود مقدار ما بین آن را به عنوان کلید بیرون می‌کشیم و در یک namValueCollection جدید قرار می‌دهیم و در نهایت به پراپرتی پاس می‌دهیم. کد نهایی این متد به شکل زیر است:
        public T GetFromQueryString<T>() where T : new()
        {
            var obj = new T();
            var properties = typeof(T).GetProperties();

            var queryString = HttpContext.Current.Request.QueryString;
            var queries = HttpUtility.ParseQueryString(queryString.ToString());

            foreach (var property in properties)
            {
                foreach (Attribute attribute in property.GetCustomAttributes(true))
                {
                    var requestBodyField = attribute as RequestBodyField; 
                    if (requestBodyField == null) continue;

                    //get value of query string
                    var valueAsString = queries[requestBodyField.Field];

                    if (valueAsString == null)
                    {
                        var keys = from key in queries.AllKeys where key.StartsWith(requestBodyField.Field) select key;

                        var collection = new NameValueCollection();

                        foreach (var key in keys)
                        {
                            var openBraketIndex = key.IndexOf("[", StringComparison.Ordinal);
                            var closeBraketIndex = key.IndexOf("]", StringComparison.Ordinal);

                            if (openBraketIndex < 0 || closeBraketIndex < 0)
                                throw new Exception("query string is corrupted.");

                            openBraketIndex++;
                            //get key in [...]
                            var fieldName = key.Substring(openBraketIndex, closeBraketIndex - openBraketIndex);
                            collection.Add(fieldName, queries[key]);
                        }
                        property.SetValue(obj, collection, null);
                        continue;
                    }

                    var converter = TypeDescriptor.GetConverter(property.PropertyType);
                    var value = converter.ConvertFrom(valueAsString);

                    if (value == null)
                        continue;

                    property.SetValue(obj, value, null);
                }
            }
            return obj;
        }

حال  به صورت زیر این متد را صدا می‌زنیم:
public virtual ActionResult GetEmployees()
{
     var request = new Requests().GetFromQueryString<EmployeesRequestBody>();
}
 
مطالب
بررسی اجمالی Redis
نام Redis از Remote Dictionary server گرفته شده‌است. Redis یکی از محبوب‌ترین key-value store‌ها می‌باشد و هم چنین توسط برند‌های بزرگ IT جهان استفاده می‌شود. لازم به ذکر است  Amazon Elastic Cache از Redis پشتیبانی می‌کند. Redis یک دیتابیس No SQL است و بر روی مفهوم زوج  کلید-مقدار (key-value ) کار می‌کند. key-value store امکانی را برای ذخیره داده‌ها که Value  نامیده میشود، در یک Key فراهم می‌کند. شما می‌توانید بعدا این داد‌ه‌ها را دریافت کنید، تنها اگر نام دقیق کلیدی را که برای ذخیره داده استفاده کرده‌اید، بدانید.

What Is In-Memory, Key-Value Store  

Key-Value store یک سیستم ذخیره سازی است؛ جایی که داده‌ها به صورت زوج کلید-مقدار ذخیره می‌شوند. وقتی که میگوییم in-memory key-value store (زوج کلید-مقدار مقیم در حافظه)، منظور این است که زوج کلید-مقدار در حافظه اصلی RAM ذخیره می‌شوند. بنابراین می‌توانیم بگوییم Redis داده‌ها را در حافظه به شکل زوج کلید-مقدار ذخیره کرده است. 
در Redis کلید‌ها باید string باشند؛ ولی value ‌ها می‌توانند یک string ، list ، set ، sorted set یا hash باشند. 
 
Advantage And Disadvantage of Redis over DBMS  

Database Management systems همه چیز را در حافظه ثانویه ذخیره می‌کند که باعث می‌شود خواندن و نوشتن عملیات، تا اندازه‌ای کند باشد. این در حالی است که Redis  همه چیز را در حافظه اصلی ذخیره می‌کند و همین موضوع باعث می‌شود که خواندن و نوشتن داده‌ها توسط آن خیلی سریع باشند. 
حافظه اصلی محدود است. بنابراین Redis نمی‌تواند فایل‌های بزرگ یا binary data را ذخیره کند و تنها اطلاعات متنی کوچک را ذخیره می‌کند که نیاز است قابل دسترسی و اصلاح باشند و با نرخ خیلی سریعی قابل درج باشند. اگر تلاش کنیم که داده‌های بیشتری را نسبت به حافظه موجود بنویسیم، در این حالت خطا دریافت خواهیم کرد.

 Redis  RDBMS
Redis  همه چیز را در حافظه اصلی ذخیره می‌کند. RDBMS همه چیز را در حافظه ثانویه ذخیره می‌کند.
در Redis بخاطر ذخیره سازی داده‌ها در حافظه اصلی، خواندن و نوشتن عملیات به شدت سریع می‌باشد. در RDBMS بخاطر ذخیره سازی داده‌ها در حافظه ثانویه، خواندن و نوشتن
عملیات کند است.
حافظه اصلی از نظر size کوچکتر و از لحاظ قیمت نسبت به حافظه ثانویه گرانتر می‌باشد. Redis نمی‌تواند داده‌های بزرگ یا binary data را ذخیره کند.    حافظه ثانویه از نظر size  بزرگتر و از لحاظ قیمت نسبت به حافظه اصلی ارزان‌تر می‌باشد. RDBMS به آسانی می‌تواند با انواع فایل‌ها کار کند.   


Redis Advantages

  • Redis  : Exceptionally fast خیلی سریع است و می‌تواند حدود 110000  ، SET   و 81000 ،  GET را به ازای هر ثانیه انجام دهد.
  • Redis : Supports rich data type بیشتر دیتا تایپ‌ها را  که توسعه دهندگان قبلا آن‌ها را شناخته‌اند، پشتیبانی می‌کند؛ از قبیل string ، list ، set ، sorted set یا hash .
  •  Operations are atomic  : تمام عملیات Redis اتمیک می‌باشند که این اطمینان خاطر را میدهد اگر دو کلاینت به صورت همزمان به آن دسترسی داشته باشند، Redis server مقدار update شده را دریافت خواهد کرد. 
  • Redis : Multi-utility tool یک ابزار چند منظوره است که می‌تواند در برخی از سناریو‌ها استفاده شود از قبیل:  Redis ) messaging-queues , caching   به صورت بومی از Publish/Subscribe پشتیبانی می‌کند ) , هر داده ای با طول عمر کوتاه در Application مانند web application sessions , ... .
 

Redis Single Instance Architecture 

معماری Redis شامل دو پروسه اصلی است: 
1- Redis client
2- Redis Server


Redis client و Redis Server هر دو می‌توانند در یک کامپیوتر یا کامپیوتر‌های متفاوت باشند. Redis server مسئول ذخیره سازی داده‌ها در حافظه می‌باشد. همانطور که متوجه هستیم، Redis همه چیز را در حافظه اصلی ذخیره می‌کند و حافظه اصلی فرار است؛ از این رو زمانیکه Redis server یا کامپیوتر را راه اندازی مجدد (restart) می‌کنیم، همه داده‌های ذخیره شده را از دست خواهیم داد. بنابراین نیازمند یک راه‌حل، جهت ماندگاری datastore می‌باشیم. 


Redis Persistance 
 
سه راه متفاوت وجود دارد که Redis را پایدار می‌کند : RDB ، AOF و دستور SAVE

1-  RDB : RDB Mechanism یک نمونه از تمام داده‌های در حافظه را تهیه و آن‌ها را در حافظه ثانویه ذخیره می‌کند (ذخیره سازی ماندگار) که در یک وقفه مشخص اتفاق می‌افتد. بنابراین این شانس وجود دارد که شما داده‌هایی را از دست بدهید که بعد از آخرین Set , RDB’s snapshot  شده‌اند . 

2-AOF : AOF همه عملیات نوشتن دریافت شده توسط سرور را ثبت می‌کند. بنابراین همه چیز پایدار است. مشکل استفاده از AOF  این است که برای هر عملیات، شروع به نوشتن در دیسک می‌کند و این یک کار هزینه‌بر است و هم چنین اندازه فایل AOF بزرگتر از RDB می‌باشد. 

3-SAVE Command : شما می‌توانید Redis server را مجبور کنید که یک RDB snapshot را ایجاد کند؛ هر زمانکه Redis console client از دستور SAVE استفاده می‌کند.

در ضمن می‌توانید از AOF  و RDB با هم استفاده کنید تا بهترین نتیجه ماندگاری را داشته باشید. 
 
Redis Replication 

Replication یک تکنیک است که کامپیوتر‌ها را درگیر می‌کند تا دسترسی پذیری داده‌ها و تحمل خطا را با ضریب بیشتری امکان پذیر کنند. در یک محیط Replication، کامپیوتر‌ها، داده‌های یکسانی را با یکدیگر به اشتراک می‌گذارند؛ حتی اگر چندین کامپیوتر دچار مشکل شوند، باز هم، همه داده‌ها در دسترس خواهند بود که به صورت Master/Slaves  می‌باشند.


تمام slave‌ها شامل داده‌های یکسانی همانند master می‌باشند. وقتی‌که یک slave جدید در محیط Replication ایجاد می‌شود، master به صورت خودکار همه داده‌ها را با sync ، slave می‌کند.
تمام Query ‌ها به سرور master هدایت می‌شوند و سپس سرور master عملیات را اجرا می‌کند. وقتی‌که یک عملیات نوشتن اتفاق می‌افتد، سرور master داده‌هایی را که به‌تازگی نوشته شده‌اند، در تمام slave‌ها تکثیر می‌کند. 
 اگر اتفاقی در سرور master رخ دهد، تمام داده‌ها از بین می‌روند؛ در این حالت باید یک slave را به master تبدیل کنیم. 

Clustering In Redis 

Clustering یک تکنیک می‌باشد که توسط آن می‌توان داده‌ها را در چندین کامپیوتر تقسیم بندی کرد. فرض کنید که یک سرور Redis را با 64GB حافظه در اختیار داریم. در این حالت می‌توانیم 64GB داده داشته باشیم. اگر  10 تا clustered computer را که هر کدام 64GB حافظه اصلی دارند، داشته باشیم، در این حالت می‌توان 640GB  داده را ذخیره کرد. 
 

در تصویر بالا می‌توانیم ببینیم که داده‌ها در چهار node، ذخیره شده‌اند. هر node یک Redis Server پیکربندی شده می‌باشد؛ به عنوان یک cluster node. اگر یکی از node ‌ها دچار مشکل شوند، سپس کل cluster متوقف می‌شود. 

Redis Client 

وب سایت Try Redis ، یک Redis console client  آنلاین است و به شما کمک می‌کند تا یاد بگیرید چگونه از Redis console client  استفاده کنید.


در قسمت بعد در رابطه با نصب Redis  بر روی سیستم عامل ویندوز و دیتا تایپ‌ها در Redis صحبت خواهیم کرد.
نظرات مطالب
استفاده از چند فرم در کنار هم در ASP.NET MVC
ممنون از اینکه پاسخ دادید.

خیر از Httpget و NonAction استفاده نکردم ولی از ChildActionOnly بالای اکشنها استفاده کردم
در کنترلر به صورت زیر اکشنها رو نوشتم:
        [Authorize(Roles = "Administrator")]
        public ActionResult AddUserRole()
        {
            return View();
        }
       
         //////////////////////////////////////////////////////////

        //اضافه کردن نقش////////////
        [Authorize(Roles = "Administrator")]
        [HttpGet]
        [ChildActionOnly]
        
        public ActionResult AddRole()
        {
          
            return PartialView("PartialRole");
        }

        [ChildActionOnly]
        [HttpPost]
        //[ValidateAntiForgeryToken]
        public ActionResult AddRole(tblRole rl, string submitValue)
        {
            if (submitValue == "ذخیره نقش")
            {
                //تبدیل تاریخ میلادی به شمسی
                rl.RoleDateCreate = Utility.ToPersianDate(DateTime.Now);

                UserRepository user = new UserRepository();
                //برای وارد کردن خودکار شناسه کاربر وارد شده
                rl.UserIDCreate_FK = Convert.ToInt64(user.FindUserID(User.Identity.Name));

                if (ModelState.IsValid)
                {
                    RoleRepository acr = new RoleRepository();
                    var model = acr.Add(rl);
                    ViewBag.Message = "نقش با موفقیت ثبت شد";
                    ModelState.Clear();
                }
            }
            else
            {
                ModelState.Clear();
            }
            return PartialView("PartialRole", rl);

        }
        //////////////////////////////////////////////////////////

        //اضافه کردن کاربر//////////
        [Authorize(Roles = "Administrator")]
        [HttpGet]
        [ChildActionOnly]
       
        public ActionResult AddUser()
        {
            return PartialView("PartialUser");
        }
        
        [ChildActionOnly]
        [HttpPost]
     
        public ActionResult AddUser(tblUser rl, string submitValue)
        {
            if (submitValue == "ثبت نام")
            {
                if (ur.ExistUserName(rl.UserName))//برای اینکه نام کاربری تکراری نباشد
                {
                    ViewBag.Warning = "این نام کاربری تکراری میباشد";
                 
                }
                else
                {
                   
                    //برای وارد کردن خودکار شناسه کاربر وارد شده
                    rl.UserIDCreate = Convert.ToInt64(ur.FindUserID(User.Identity.Name));
                   
                    //تبدیل میلادی به شمسی
                    rl.UserDateCreate = Utility.ToPersianDate(DateTime.Now);

                    if (ModelState.IsValid)
                    {
                        UserRepository acr = new UserRepository();
                        var model = acr.Add(rl);
                        ViewBag.Message = "کاربر با موفقیت ثبت شد";
                        ModelState.Clear();
                    }
                }
            }
            else
            {
                ModelState.Clear();
            }
            return PartialView("PartialUser", rl);

        }

        //////////////////////////////////////////////////////////

        // دادن نقش به کاربر///////

        [ChildActionOnly]
        
        [HttpGet]
        [Authorize(Roles = "Administrator")]
        public ActionResult RoleOfUser()
        {
            var blUser = new UserRepository();
            var blRole = new RoleRepository();
            var model = new RoleUserViewModel();

            model.username = blUser.Select().ToList();
            model.rolename = blRole.Select().ToList();
           
            return PartialView("PartialRoleOfUser", model);
        }

        [ChildActionOnly]
        [HttpPost]      
        public ActionResult RoleOfUser(tblRoleUser rl, string submitValue)
        {
            if (submitValue == "ذخیره نقش کاربر")
            {
                //برای وارد کردن خودکار شناسه کاربر وارد شده
                rl.UserIDCreate_FK = Convert.ToInt64(ur.FindUserID(User.Identity.Name));

                //تبدیل میلادی به شمسی
                rl.RoleUserDateCreate = Utility.ToPersianDate(DateTime.Now);

                if (ModelState.IsValid)
                {
                    RoleUserRepository acr = new RoleUserRepository();
                    var model = acr.Add(rl);
                    ViewBag.Message = "نقش کاربر با موفقیت ثبت شد";

                    ModelState.Clear();
                }
            }
            else
            {
                ModelState.Clear();
            }
            return PartialView("PartialRoleOfUser", rl);

        }
در پارشیال ویو Role
@model MeterControl.ViewModels.RoleUserViewModel
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

@using (Ajax.BeginForm("AddUserRole", "Home", new AjaxOptions { HttpMethod = "Post", Url = "/Home/AddUserRole" })){ 
    <fieldset>
        <legend>tblRole</legend>

        

        <div>
            @Html.LabelFor(model => model.role.RoleName)
        </div>
        <div>
            @Html.EditorFor(model => model.role.RoleName)
            @Html.ValidationMessageFor(model => model.role.RoleName)
        </div>

       

        @ViewBag.Message
        <p>
            <input type="submit" value="ذخیره نقش" name="submitValue" />
        </p>
    </fieldset>
}

و در ویو کلی هم به صورت زیر اکشنها صدا زده شده اند:
@{
    ViewBag.Title = "AddUserRole";
}

<h2>AddUserRole</h2>
<div>
    <div>
        @Html.Action("AddRole", "Home")
    </div>
    <div>
        @Html.Action("AddUser", "Home")
        @Html.Partial("PartialUser")
    </div>
    <div>
        @Html.Action("RoleOfUser","Home")
    </div>
</div>

به خط زیر خطا میدهد
@Html.Action("AddRole", "Home")
مطالب
jQuery Tips #1
چگونگی تغییر سایز فونت  صفحه با استفاده از jQuery

کد زیر را در نظر بگیرید
 $(function () {
            // اندازه واقعی فونت
            var originalFontSize = $('#test').css('font-size');
            $(".resetFont").click(function () {
                $('#test').css('font-size', originalFontSize);
            });
            // افزایش اندازه فونت
            $(".increaseFont").click(function () {
                var currentFontSize = $('#test').css('font-size');
                var currentFontSizeNum = parseFloat(currentFontSize);
                var newFontSize = currentFontSizeNum + 5;
                $('#test').css('font-size', newFontSize);
                return false;
            });
            // کاهش اندازه فونت
            $(".decreaseFont").click(function () {
                var currentFontSize = $('#test').css('font-size');
                var currentFontSizeNum = parseFloat(currentFontSize);
                var newFontSize = currentFontSizeNum - 5;
                $('#test').css('font-size', newFontSize);
                return false;
            });
        });
       

  و کد HTML زیر را
 <div id="test">
        jQuery Tips By Mohsen Bahrzadeh
    </div>
    <a href="#">decreaseFont</a>
    <a href="#">Increase</a>
    <a href="#">resetFont</a>
 در این کد ابتدا اندازه فونت را درون متغیر originalFontSize ذخیره و سپس 3 متد تعریف کرده‌ایم، که اولین متد اندازه فونت را به اندازه فونت اولیه بر می‌گرداند و در متد دوم می‌خواهیم اندازه فونت تگی با ID=test را افزایش دهیم.
برای این کار ابتدا اندازه جاری تگ را گرفته و درون متغیر currentFontSize قرار داده سپس مقدار متغیر currentFontSize را به Float تبدیل کرده و 5 واحد به آن اضافه  کرده ایم و سپس عدد بدست آمده را به عنوان اندازه فونت جدید در نظر گرفته ایم. برای متد سوم دقیقاً همین اتفاق می‌افتد فقط به جای + از - استفاده شده است