مطالب دوره‌ها
تبدیل روش‌های قدیمی کدنویسی غیرهمزمان به async سی شارپ 5
در قسمت اول این سری، با مدل برنامه نویسی Event based asynchronous pattern ارائه شده از دات نت 2 و همچنین APM یا Asynchronous programming model موجود از نگارش یک دات نت، آشنا شدیم (به آن الگوی IAsyncResult هم گفته می‌شود). نکته‌ی مهم این الگوها، استفاده‌ی گسترده از آن‌ها در کدهای کلاس‌های مختلف دات نت فریم ورک است و برای بسیاری از آن‌ها هنوز async API سازگار با نگارش مبتنی بر Taskهای سی‌شارپ 5 ارائه نشده‌است. هرچند دات نت 4.5 سعی کرده‌است این خلاء را پوشش دهد، برای مثال متد الحاقی DownloadStringTaskAsync را به کلاس WebClient اضافه کرده‌است و امثال آن، اما هنوز بسیاری از کلاس‌های دیگر دات نتی هستند که معادل Task based API ایی برای آن‌ها طراحی نشده‌است. در ادامه قصد داریم بررسی کنیم چگونه می‌توان این الگوهای مختلف قدیمی برنامه نویسی غیرهمزمان را با استفاده از روش‌های جدیدتر ارائه شده بکار برد.



نگاشت APM به یک Task

در قسمت اول، نمونه مثالی را از APM، که در آن کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجه‌ی عملیات به دست می‌آید، مشاهده کردید. در ادامه می‌خواهیم یک محصور کننده‌ی جدید را برای این نوع API قدیمی تهیه کنیم، تا آن‌را به صورت یک Task ارائه دهد.
    public static class ApmWrapper
    {
        public static Task<int> ReadAsync(this Stream stream, byte[] data, int offset, int count)
        {
            return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, data, offset, count, null);
        }
    }
همانطور که در این مثال مشاهده می‌کنید، یک چنین سناریوهایی در TPL یا کتابخانه‌ی Task parallel library پیش بینی شده‌اند. در اینجا یک محصور کننده برای متدهای BeginRead و EndRead کلاس Stream دات نت ارائه شده‌است. به عمد نیز به صورت یک متد الحاقی تهیه شده‌است تا در حین استفاده از آن اینطور به نظر برسد که واقعا کلاس Stream دارای یک چنین متد Async ایی است. مابقی کار توسط متد Task.Factory.FromAsync انجام می‌شود. متد FromAsync دارای امضاهای متعددی است تا اکثر حالات APM را پوشش دهد.
در مثال فوق BeginRead و EndRead استفاده شده از نوع delegate هستند. چون خروجی EndRead از نوع int است، خروجی متد نیز از نوع Task of int تعیین شده‌است. همچنین سه پارامتر ابتدایی BeginRead ، دقیقا data، offset و count هستند. دو پارامتر آخر آن callback و state نام دارند. پارامتر callback توسط متد FromAsync فراهم می‌شود و state نیز در اینجا null درنظر گرفته شده‌است.
یک مثال استفاده از آن‌را در ادامه مشاهده می‌کنید:
using System;
using System.IO;
using System.Threading.Tasks;

namespace Async06
{
    public static class ApmWrapper
    {
        public static Task<int> ReadAsync(this Stream stream, byte[] data, int offset, int count)
        {
            return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, data, offset, count, null);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var stream = File.OpenRead(@"..\..\program.cs"))
            {
                var data = new byte[10000];
                var task = stream.ReadAsync(data, 0, data.Length);
                Console.WriteLine("Read bytes: {0}", task.Result);
            }
        }
    }
}
File.OpenRead، خروجی از نوع استریم دارد. سپس متد الحاقی ReadAsync بر روی آن فراخوانی شده‌است و نهایتا تعداد بایت خوانده شده نمایش داده می‌شود.
البته همانطور که پیشتر نیز عنوان شد، استفاده از خاصیت Result، اجرای کد را بجای غیرهمزمان بودن، به حالت همزمان تبدیل می‌کند.
در اینجا چون خروجی متد ReadAsync یک Task است، می‌توان از متد ContinueWith نیز بر روی آن جهت دریافت نتیجه استفاده کرد:
using (var stream = File.OpenRead(@"..\..\program.cs"))
{
    var data = new byte[10000];
    var task = stream.ReadAsync(data, 0, data.Length);
    task.ContinueWith(t => Console.WriteLine("Read bytes: {0}", t.Result)).Wait();
}


یک نکته
پروژه‌ی سورس بازی به نام Async Generator در GitHub، سعی کرده‌است برای ساده سازی نوشتن محصور کننده‌های مبتنی بر Task روش APM، یک Code generator تولید کند. فایل‌های آن‌را از آدرس ذیل می‌توانید دریافت کنید:

نگاشت EAP به یک Task

نمونه‌ای از Event based asynchronous pattern یا EAP را در قسمت اول، زمانیکه روال رخدادگردان webClient.DownloadStringCompleted را بررسی کردیم، مشاهده نمودید. کار کردن با آن نسبت به APM بسیار ساده‌تر است و نتیجه‌ی نهایی عملیات غیرهمزمان را در یک روال رخدادگران، در اختیار استفاده کننده قرار می‌دهد. همچنین در روش EAP، اطلاعات در همان Synchronization Context ایی که عملیات شروع شده‌است، بازگشت داده می‌شود. به این ترتیب اگر آغاز کار در ترد UI باشد، نتیجه نیز در همان ترد دریافت خواهد شد. به این ترتیب دیگر نگران دسترسی به مقدار آن در کارهای UI نخواهیم بود؛ اما در APM چنین ضمانتی وجود ندارد.
متاسفانه TPL همانند روش FromAsync معرفی شده در ابتدای بحث، راه حل توکاری را برای محصور سازی متدهای روش EAP ارائه نداده‌است. اما با استفاده از امکانات TaskCompletionSource آن می‌توان چنین کاری را انجام داد. در ادامه سعی خواهیم کرد همان متد الحاقی توکار DownloadStringTaskAsync ارائه شده در دات نت 4.5 را از صفر بازنویسی کنیم.
    public static class WebClientExtensions
    {
        public static Task<string> DownloadTextTaskAsync(this WebClient web, string url)
        {
            var tcs = new TaskCompletionSource<string>();

            DownloadStringCompletedEventHandler handler = null;
            handler = (sender, args) =>
            {
                web.DownloadStringCompleted -= handler;

                if (args.Cancelled)
                {
                    tcs.SetCanceled();
                }
                else if(args.Error!=null)
                {
                    tcs.SetException(args.Error);
                }
                else
                {
                    tcs.SetResult(args.Result);
                }
            };

            web.DownloadStringCompleted += handler;
            web.DownloadStringAsync(new Uri(url));

            return tcs.Task;
        }
    }
روش انجام کار را در اینجا ملاحظه می‌کنید. ابتدا باید تعاریف delaget مرتبط با رخدادگردان Completed اضافه شوند. یکبار += را ملاحظه می‌کنید و بار دوم -= را. مورد دوم جهت آزاد سازی منابع و جلوگیری از نشتی حافظه‌ی ‌روال رخدادگردان هنوز متصل، ضروری است.
سپس از TaskCompletionSource برای تبدیل این عملیات به یک Task کمک می‌گیریم. اگر args.Cancelled مساوی true باشد، یعنی عملیات دریافت فایل لغو شده‌است. بنابراین متد SetCanceled منبع Task ایجاد شده را فراخوانی خواهیم کرد. این مورد استثنایی را در کدهای فراخوان سبب می‌شود. به همین دلیل بررسی خطا با یک if else پس از آن انجام شده‌است. برای بازگشت خطای دریافت شده از متد SetException و برای بازگشت نتیجه‌ی واقعی دریافتی، از متد SetResult می‌توان استفاده کرد.
به این ترتیب متد الحاقی غیرهمزمان جدیدی را به نام DownloadTextTaskAsync برای محصور سازی متد EAP ایی به نام DownloadStringAsync و همچنین رخدادگران آن تهیه کردیم.
مطالب
آموزش Linq - بخش ششم : عملگرهای پرس و جو قسمت دوم
در ادامه‌ی سری آموزشی LINQ، عملگر‌های پرس و جوی مرتب سازی، گروه بندی و مجموعه را بررسی خواهیم کرد.

عملگرهای مرتب سازی  Ordering Operators
این عملگر‌ها عناصر توالی ورودی را به خروجی ارسال می‌کنند؛ با این تفاوت که توالی خروجی مرتب شده، توالی ورودی است.

عملگر OrderBy

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

پیاده سازی توسط عبارت‌های جستجو
در عبارت‌های پرس و جو، کلمه کلیدی orderby برای مرتب سازی توالی استفاده می‌شود:
مثال:
var query = from i in ingredients
orderby i.Calories
select i;

عملگر ThenBy

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

پیاده سازی توسط عبارت‌های جستجو
var query = from i in ingredients
orderby i.Calories, i.Name
select i;
همانطور که مشاهده می‌کنید برای مرتب سازی بر اساس خصوصیات دیگر کافیست نام خصوصیت را بعد از اولین عنصر، به وسیله‌ی کاما (,) قید کنید.

عملگر OrderByDescending
این عملگر همچون عملگر OrderBy عمل می‌کند؛ اما توالی ورودی را بر اساس کلید داده شده، به صورت نزولی مرتب می‌کند.
مثال:
Ingredient[] ingredients =
{
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 150},
   new Ingredient {Name = "Flour", Calories = 50},
   new Ingredient {Name = "Butter", Calories = 200},
};
var query = ingredients.OrderByDescending(x => x.Calories);
foreach (var item in query)
{
   Console.WriteLine(item.Name + " " + item.Calories);
}
خروجی مثال بالا:
Sugar 500
Flour 500
Butter 200
Milk 100
Egg 100

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

جایگزین عملگر OrderByDescending در عبارت‌های جستجو، کلمه‌ی کلیدی descending می‌باشد:
var query = from i in ingredients
orderby i.Calories descending
select i;

عملگر ThenByDescending

این عملگر بصورت زنجیره‌ای بعد از عملگر‌های دیگر می‌آید و مرتب سازی را به‌صورت نزولی انجام می‌دهد.
مثال:
Ingredient[] ingredients =
{
   new Ingredient {Name = "Flour", Calories = 500},
   new Ingredient {Name = "Sugar", Calories = 500},
   new Ingredient {Name = "Egg", Calories = 100},
   new Ingredient {Name = "Milk", Calories = 100},
   new Ingredient {Name = "Butter", Calories = 200}
};
var query = ingredients
                    .OrderBy(x => x.Calories)
                    .ThenByDescending(x => x.Name);
foreach (var item in query)
{
   Console.WriteLine(item.Name + " " + item.Calories);
}
خروجی مثال بالا:
Milk 100
Egg 100
Butter 200
Sugar 500
Flour 500
در این مثال در توالی ورودی، Flour قبل از Sugar آمده است. اما به خاطر عملگر ThenOrderByDescending ترتیب قرار گیری آنها در توالی خروجی تغییر کرده است.

پیاده سازی توسط عبارت‌های جستجو
در عبارت‌های جستجو نیز برای رسیدن به خروجی مشابه کد بالا از کلمه‌ی کلیدی descending استفاده می‌کنیم.
var query = from i in ingredients
orderby i.Calories, i.Name descending
select i;

عملگر Reverse

عملگر Reveres توالی ورودی را دریافت کرده و آن را معکوس و سپس توالی خروجی را تولید می‌کند. اولین عنصر توالی ورودی، آخرین عنصر توالی خروجی می‌باشد.
مثال:
char[] letters = { 'A', 'B', 'C' };
var query = letters.Reverse();
foreach (var item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا:
C
B
A

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

معادل این عملگر، کلمه‌ی کلیدی دیگری در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.


عملگر‌های گروه بندی Grouping Operator

عملگر GroupBy

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

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


عملگرهای مجموعه Set Operators
این عملگر‌ها شامل موارد زیر می‌باشند:
• Concat
• Union
• Distinct
• Intersect
• Except

عملگر Concat

این عملگر دو توالی را با هم ادغام می‌کند؛ بطوریکه عناصر توالی دوم، بعد از عناصر توالی اول به توالی خروجی اضافه می‌شوند.
مثال:
string[] applePie = { "Apple", "Sugar", "Pastry", "Cinnamon" };
string[] cherryPie = { "Cherry", "Sugar", "Pastry", "Kirsch" };
IEnumerable<string> query = applePie.Concat(cherryPie);
foreach (string item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا :
Apple
Sugar
Pastry
Cinnamon
Cherry
Sugar
Pastry
Kirsch
توجه داشته باشید که در این حالت عناصر تکراری حذف نخواهند شد.

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

عملگر Union
این عملگر همچون عملگر Concat رفتار می‌کند؛ با این تفاوت که عناصر تکراری در توالی خروجی حضور نخواهند داشت.
مثال:
string[] applePie = { "Apple", "Sugar", "Pastry", "Cinnamon" };
string[] cherryPie = { "Cherry", "Sugar", "Pastry", "Kirsch" };
IEnumerable<string> query = applePie.Union(cherryPie);
foreach (string item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا:
Apple
Sugar
Pastry
Cinnamon
Cherry
Kirsch
همانطور که مشاهده می‌کنید، عناصر Sugar و Pastry که در توالی دوم تکرار شده بودند، در خروجی وجود ندارند.

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

عملگر  Distinct
این عملگر عناصر تکراری توالی را حذف می‌کند. این عملگر هم می‌تواند بر روی یک توالی اجرا شود و هم می‌تواند بصورت زنجیره‌ای بعد از عملگر‌های دیگر بکار برود.
مثال: استفاده از این عملگر بر روی یک توالی:
string[] applePie = { "Apple", "Sugar", "Apple", "Sugar", "Pastry", "Cinnamon" };
IEnumerable<string> query = applePie.Distinct();
foreach (string item in query)
{
   Console.WriteLine(item);
}
در مثال بالا، عناصر تکراری در توالی ورودی را، از طریق عملگر Distinct حذف کرده‌ایم.
خروجی مثال بالا:
Apple
Sugar
Pastry
Cinnamon

مثال: بکارگیری عملگر Distinct بهمراه عملگر Concat برای شبیه سازی عملیات Union
string[] applePie = { "Apple", "Sugar", "Pastry", "Cinnamon" };
string[] cherryPie = { "Cherry", "Sugar", "Pastry", "Kirsch" };
IEnumerable<string> query = applePie.Concat(cherryPie).Distinct();

foreach (string item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا:
Apple
Sugar
Pastry
Cinnamon
Cherry
Kirsch
همانطور که مشاهده می‌کنید خروجی این مثال با حالت استفاده از Union تفاوتی ندارد.

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

عملگر Intersect
این عملگر عناصر مشترک بین دو توالی را باز می‌گرداند.
مثال:
string[] applePie = { "Apple", "Sugar", "Pastry", "Cinnamon" };
string[] cherryPie = { "Cherry", "Sugar", "Pastry", "Kirsch" };
IEnumerable<string> query = applePie.Intersect(cherryPie);
foreach (string item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا:
Sugar
Pastry
نکته: عناصر تکراری فقط یکبار در خروجی ظاهر خواهند شد.

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

عملگر Except
این عملگر عناصری از توالی اول را انتخاب می‌کند که در توالی دوم همتایی نداشته باشند.
مثال:
string[] applePie = { "Apple", "Sugar", "Pastry", "Cinnamon" };
string[] cherryPie = { "Cherry", "Sugar", "Pastry", "Kirsch" };
IEnumerable<string> query = applePie.Except(cherryPie);
foreach (string item in query)
{
   Console.WriteLine(item);
}
خروجی مثال فوق:
Apple
Cinnamon
نکته: هیچ عنصری از توالی دوم در خروجی وجود نخواهد داشت.

پیاده سازی توسط عبارت‌های جستجو
معادل این عملگر، کلمه‌ی کلیدی جدیدی در عبارت‌های جستجو وجود ندارد و ترکیب دو روش می‌تواند خروجی دلخواه را تولید کند.
مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت سوم

در قسمت قبلی بروز رسانی موجودیت‌های منفصل با WCF را بررسی کردیم. در این قسمت خواهیم دید چگونه می‌توان تغییرات موجودیت‌ها را تشخیص داد و عملیات CRUD را روی یک Object Graph اجرا کرد.

تشخیص تغییرات با Web API

فرض کنید می‌خواهیم از سرویس‌های Web API برای انجام عملیات CRUD استفاده کنیم، اما بدون آنکه برای هر موجودیت متدهایی مجزا تعریف کنیم. به بیان دیگر می‌خواهیم عملیات مذکور را روی یک Object Graph انجام دهیم. مدیریت داده‌ها هم با مدل Code-First پیاده سازی می‌شود. در مثال جاری یک اپلیکیشن کنسول خواهیم داشت که بعنوان یک کلاینت سرویس را فراخوانی می‌کند. هر پروژه نیز در Solution مجزایی قرار دارد، تا یک محیط n-Tier را شبیه سازی کنیم.

مدل زیر را در نظر بگیرید.

همانطور که می‌بینید مدل ما آژانس‌های مسافرتی و رزرواسیون آنها را ارائه می‌کند. می‌خواهیم مدل و کد دسترسی داده‌ها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید:

  • در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe3.Service تغییر دهید.
  • کنترلر جدیدی بنام TravelAgentController به پروژه اضافه کنید.
  • دو کلاس جدید با نام‌های TravelAgent و Booking بسازید و کد آنها را مطابق لیست زیر تغییر دهید.
public class TravelAgent
{
    public TravelAgent()
    {
        this.Bookings = new HashSet<Booking>();
    }

    public int AgentId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Booking> Bookings { get; set; }
}

public class Booking
{
    public int BookingId { get; set; }
    public int AgentId { get; set; }
    public string Customer { get; set; }
    public DateTime BookingDate { get; set; }
    public bool Paid { get; set; }
    public virtual TravelAgent TravelAgent { get; set; }
}
  • با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
  • کلاس جدیدی بنام Recipe3Context بسازید و کد آن را مطابق لیست زیر تغییر دهید.
public class Recipe3Context : DbContext
{
    public Recipe3Context() : base("Recipe3ConnectionString") { }
    public DbSet<TravelAgent> TravelAgents { get; set; }
    public DbSet<Booking> Bookings { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TravelAgent>().HasKey(x => x.AgentId);
        modelBuilder.Entity<TravelAgent>().ToTable("TravelAgents");
        modelBuilder.Entity<Booking>().ToTable("Bookings");
    }
}

  • فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه کنید.
<connectionStrings>
  <add name="Recipe3ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
</connectionStrings>
  • فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Model Compatibility در EF را غیرفعال می‌کند. همچنین به JSON serializer می‌گوییم که self-referencing loop خاصیت‌های پیمایشی را نادیده بگیرد. این حلقه بدلیل ارتباط bidirectional بین موجودیت‌ها بوجود می‌آید.
protected void Application_Start()
{
    // Disable Entity Framework Model Compatibilty
    Database.SetInitializer<Recipe1Context>(null);

    // The bidirectional navigation properties between related entities
    // create a self-referencing loop that breaks Web API's effort to
    // serialize the objects as JSON. By default, Json.NET is configured
    // to error when a reference loop is detected. To resolve problem,
    // simply configure JSON serializer to ignore self-referencing loops.
    GlobalConfiguration.Configuration.Formatters.JsonFormatter
        .SerializerSettings.ReferenceLoopHandling =
        Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    ...
}
  • فایل RouteConfig.cs را باز کنید و قوانین مسیریابی را مانند لیست زیر تغییر دهید.
public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
      name: "ActionMethodSave",
      routeTemplate: "api/{controller}/{action}/{id}",
      defaults: new { id = RouteParameter.Optional });
}
  • در آخر کنترلر TravelAgent را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
public class TravelAgentController : ApiController
{
    // GET api/travelagent
    [HttpGet]
    public IEnumerable<TravelAgent> Retrieve()
    {
        using (var context = new Recipe3Context())
        {
            return context.TravelAgents.Include(x => x.Bookings).ToList();
        }
    }

    /// <summary>
    /// Update changes to TravelAgent, implementing Action-Based Routing in Web API
    /// </summary>
    public HttpResponseMessage Update(TravelAgent travelAgent)
    {
        using (var context = new Recipe3Context())
        {
            var newParentEntity = true;
            // adding the object graph makes the context aware of entire
            // object graph (parent and child entities) and assigns a state
            // of added to each entity.
            context.TravelAgents.Add(travelAgent);
            if (travelAgent.AgentId > 0)
            {
                // as the Id property has a value greater than 0, we assume
                // that travel agent already exists and set entity state to
                // be updated.
                context.Entry(travelAgent).State = EntityState.Modified;
                newParentEntity = false;
            }

            // iterate through child entities, assigning correct state.
            foreach (var booking in travelAgent.Bookings)
            {
                if (booking.BookingId > 0)
                    // assume booking already exists if ID is greater than zero.
                    // set entity to be updated.
                    context.Entry(booking).State = EntityState.Modified;
            }

            context.SaveChanges();
            HttpResponseMessage response;
            // set Http Status code based on operation type
            response = Request.CreateResponse(newParentEntity ? HttpStatusCode.Created : HttpStatusCode.OK, travelAgent);
            return response;
        }
    }

    [HttpDelete]
    public HttpResponseMessage Cleanup()
    {
        using (var context = new Recipe3Context())
        {
            context.Database.ExecuteSqlCommand("delete from [bookings]");
            context.Database.ExecuteSqlCommand("delete from [travelagents]");
        }
        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

در قدم بعدی کلاینت پروژه را می‌سازیم که از سرویس Web API مان استفاده می‌کند.

  • در ویژوال استودیو پروژه جدیدی از نوع Console application بسازید و نام آن را به Recipe3.Client تغییر دهید.
  • فایل program.cs را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
internal class Program
{
    private HttpClient _client;
    private TravelAgent _agent1, _agent2;
    private Booking _booking1, _booking2, _booking3;
    private HttpResponseMessage _response;

    private static void Main()
    {
        Task t = Run();
        t.Wait();
        Console.WriteLine("\nPress <enter> to continue...");
        Console.ReadLine();
    }

    private static async Task Run()
    {
        var program = new Program();
        program.ServiceSetup();
        // do not proceed until clean-up is completed
        await program.CleanupAsync();
        program.CreateFirstAgent();
        // do not proceed until agent is created
        await program.AddAgentAsync();
        program.CreateSecondAgent();
        // do not proceed until agent is created
        await program.AddSecondAgentAsync();
        program.ModifyAgent();
        // do not proceed until agent is updated
        await program.UpdateAgentAsync();
        // do not proceed until agents are fetched
        await program.FetchAgentsAsync();
    }

    private void ServiceSetup()
    {
        // set up infrastructure for Web API call
        _client = new HttpClient {BaseAddress = new Uri("http://localhost:6687/")};
        // add Accept Header to request Web API content negotiation to return resource in JSON format
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    private async Task CleanupAsync()
    {
        // call cleanup method in service
        _response = await _client.DeleteAsync("api/travelagent/cleanup/");
    }

    private void CreateFirstAgent()
    {
        // create new Travel Agent and booking
        _agent1 = new TravelAgent {Name = "John Tate"};
        _booking1 = new Booking
        {
            Customer = "Karen Stevens",
            Paid = false,
            BookingDate = DateTime.Parse("2/2/2010")
        };

        _booking2 = new Booking
        {
            Customer = "Dolly Parton",
            Paid = true,
            BookingDate = DateTime.Parse("3/10/2010")
        };
  
        _agent1.Bookings.Add(_booking1);
        _agent1.Bookings.Add(_booking2);
    }

    private async Task AddAgentAsync()
    {
        // call generic update method in Web API service to add agent and bookings
        _response = await _client.PostAsync("api/travelagent/update/",
            _agent1, new JsonMediaTypeFormatter());

        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service, which will include
            // database-generated Ids for each entity
            _agent1 = await _response.Content.ReadAsAsync<TravelAgent>();
            _booking1 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Karen Stevens");
            _booking2 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Dolly Parton");

            Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)",
            _agent1.Name, _agent1.Bookings.Count);
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }

    private void CreateSecondAgent()
    {
        // add new agent and booking
        _agent2 = new TravelAgent {Name = "Perry Como"};
        _booking3 = new Booking {
            Customer = "Loretta Lynn",
            Paid = true,
            BookingDate = DateTime.Parse("3/15/2010")};
        _agent2.Bookings.Add(_booking3);
    }

    private async Task AddSecondAgentAsync()
    {
        // call generic update method in Web API service to add agent and booking
        _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter());

        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service
            _agent2 = await _response.Content.ReadAsAsync<TravelAgent>();
            _booking3 = _agent2.Bookings.FirstOrDefault(x => x.Customer == "Loretta Lynn");
            Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)",
                _agent2.Name, _agent2.Bookings.Count);
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }

    private void ModifyAgent()
    {
        // modify agent 2 by changing agent name and assigning booking 1 to him from agent 1
        _agent2.Name = "Perry Como, Jr.";
        _agent2.Bookings.Add(_booking1);
    }

    private async Task UpdateAgentAsync()
    {
        // call generic update method in Web API service to update agent 2
        _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter());
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service, which will include Ids
            _agent1 = _response.Content.ReadAsAsync<TravelAgent>().Result;
            Console.WriteLine("Successfully updated Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count);
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }

    private async Task FetchAgentsAsync()
    {
        // call Get method on service to fetch all Travel Agents and Bookings
        _response = _client.GetAsync("api/travelagent/retrieve").Result;
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service, which will include Ids
            var agents = await _response.Content.ReadAsAsync<IEnumerable<TravelAgent>>();

            foreach (var agent in agents)
            {
                Console.WriteLine("Travel Agent {0} has {1} Booking(s)", agent.Name, agent.Bookings.Count());
            }
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }
}
  • در آخر کلاس‌های TravelAgent و Booking را به پروژه کلاینت اضافه کنید. اینگونه کدها بهتر است در لایه مجزایی قرار گیرند و بین پروژه‌ها به اشتراک گذاشته شوند.

اگر اپلیکیشن کنسول (کلاینت) را اجرا کنید با خروجی زیر مواجه خواهید شد.

(Successfully created Travel Agent John Tate and 2 Booking(s
(Successfully created Travel Agent Perry Como and 1 Booking(s
(Successfully updated Travel Agent Perry Como, Jr. and 2 Booking(s
(Travel Agent John Tate has 1 Booking(s
(Travel Agent Perry Como, Jr. has 2 Booking(s


شرح مثال جاری

با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت می‌کند. در این مرحله سایت در حال اجرا است و سرویس‌ها قابل دسترسی هستند.

سپس اپلیکیشن کنسول را باز کنید، روی خط اول کد فایل program.cs یک breakpoint قرار دهید و آن را اجرا کنید. ابتدا آدرس سرویس Web API را نگاشت می‌کنیم و با تنظیم مقدار خاصیت Accept Header از سرویس درخواست می‌کنیم که اطلاعات را با فرمت JSON بازگرداند.

بعد از آن با استفاده از آبجکت HttpClient متد DeleteAsync را فراخوانی می‌کنیم که روی کنترلر TravelAgent تعریف شده است. این متد تمام داده‌های پیشین را حذف میکند.

در قدم بعدی سه آبجکت جدید می‌سازیم: یک آژانس مسافرتی و دو رزرواسیون. سپس این آبجکت‌ها را با فراخوانی متد PostAsync روی آبجکت HttpClient به سرویس ارسال می‌کنیم. اگر به متد Update در کنترلر TravelAgent یک breakpoint اضافه کنید، خواهید دید که این متد آبجکت آژانس مسافرتی را بعنوان یک پارامتر دریافت می‌کند و آن را به موجودیت TravelAgents در Context جاری اضافه می‌نماید. این کار آبجکت آژانس مسافرتی و تمام آبجکت‌های فرزند آن را در حالت Added اضافه می‌کند و باعث می‌شود که context جاری شروع به ردیابی (tracking) آنها کند.

نکته: قابل ذکر است که اگر موجودیت‌های متعددی با مقداری یکسان در خاصیت کلید اصلی (Primary-key value) دارید باید مجموعه آبجکت‌های خود را Add کنید و نه Attach. در مثال جاری چند آبجکت Booking داریم که مقدار کلید اصلی آنها صفر است (Bookings with Id = 0). اگر از Attach استفاده کنید EF پیغام خطایی صادر می‌کند چرا که چند موجودیت با مقادیر کلید اصلی یکسان به context جاری اضافه کرده اید.

بعد از آن بر اساس مقدار خاصیت Id مشخص می‌کنیم که موجودیت‌ها باید بروز رسانی شوند یا خیر. اگر مقدار این فیلد بزرگتر از صفر باشد، فرض بر این است که این موجودیت در دیتابیس وجود دارد بنابراین خاصیت EntityState را به Modified تغییر می‌دهیم. علاوه بر این فیلدی هم با نام newParentEntity تعریف کرده ایم که توسط آن بتوانیم کد وضعیت مناسبی به کلاینت بازگردانیم. در صورتی که مقدار فیلد Id در موجودیت TravelAgent برابر با یک باشد، مقدار خاصیت EntityState را به همان Added رها می‌کنیم.

سپس تمام آبجکت‌های فرزند آژانس مسافرتی (رزرواسیون ها) را بررسی میکنیم و همین منطق را روی آنها اعمال می‌کنیم. یعنی در صورتی که مقدار فیلد Id آنها بزرگتر از 0 باشد وضعیت EntityState را به Modified تغییر می‌دهیم. در نهایت متد SaveChanges را فراخوانی می‌کنیم. در این مرحله برای موجودیت‌های جدید اسکریپت‌های Insert و برای موجودیت‌های تغییر کرده اسکریپت‌های Update تولید می‌شود. سپس کد وضعیت مناسب را به کلاینت بر می‌گردانیم. برای موجودیت‌های اضافه شده کد وضعیت 201 (Created) و برای موجودیت‌های بروز رسانی شده کد وضعیت 200 (OK) باز می‌گردد. کد 201 به کلاینت اطلاع می‌دهد که رکورد جدید با موفقیت ثبت شده است، و کد 200 از بروز رسانی موفقیت آمیز خبر می‌دهد. هنگام تولید سرویس‌های REST-based بهتر است همیشه کد وضعیت مناسبی تولید کنید.

پس از این مراحل، آژانس مسافرتی و رزرواسیون جدیدی می‌سازیم و آنها را به سرویس ارسال می‌کنیم. سپس نام آژانس مسافرتی دوم را تغییر می‌دهیم، و یکی از رزرواسیون‌ها را از آژانس اولی به آژانس دومی منتقل می‌کنیم. اینبار هنگام فراخوانی متد Update تمام موجودیت‌ها شناسه ای بزرگتر از 1 دارند، بنابراین وضعیت EntityState آنها را به Modified تغییر می‌دهیم تا هنگام ثبت تغییرات دستورات بروز رسانی مناسب تولید و اجرا شوند.

در آخر کلاینت ما متد Retreive را روی سرویس فراخوانی می‌کند. این فراخوانی با کمک متد GetAsync انجام می‌شود که روی آبجکت HttpClient تعریف شده است. فراخوانی این متد تمام آژانس‌های مسافرتی بهمراه رزرواسیون‌های متناظرشان را دریافت می‌کند. در اینجا با استفاده از متد Include تمام رکوردهای فرزند را بهمراه تمام خاصیت هایشان (properties) بارگذاری می‌کنیم.

دقت کنید که مرتب کننده JSON تمام خواص عمومی (public properties) را باز می‌گرداند، حتی اگر در کد خود تعداد مشخصی از آنها را انتخاب کرده باشید.

نکته دیگر آنکه در مثال جاری از قرارداد‌های توکار Web API برای نگاشت درخواست‌های HTTP به اکشن متدها استفاده نکرده ایم. مثلا بصورت پیش فرض درخواست‌های POST به متدهایی نگاشت می‌شوند که نام آنها با "Post" شروع می‌شود. در مثال جاری قواعد مسیریابی را تغییر داده ایم و رویکرد مسیریابی RPC-based را در پیش گرفته ایم. در اپلیکیشن‌های واقعی بهتر است از قواعد پیش فرض استفاده کنید چرا که هدف Web API ارائه سرویس‌های REST-based است. بنابراین بعنوان یک قاعده کلی بهتر است متدهای سرویس شما به درخواست‌های متناظر HTTP نگاشت شوند. و در آخر آنکه بهتر است لایه مجزایی برای میزبانی کدهای دسترسی داده ایجاد کنید و آنها را از سرویس Web API تفکیک نمایید.

اشتراک‌ها
راهنمای نامگذاری متغیرها در JavaScript

Variable Naming Best Practices in JavaScript

Like any other programming language, JavaScript relies heavily on well-structured and understandable code. One of the fundamental building blocks of clean JavaScript code is effective variable naming.

By adhering to certain best practices, you can significantly enhance the readability and maintainability of your JavaScript projects. Let’s dive into 12 sets of JavaScript variable naming guidelines.

راهنمای نامگذاری متغیرها در JavaScript
اشتراک‌ها
دوره مقدماتی #C

C# Beginners Course
This school year, I give a course for young students at the beginning of their programming careers. They have no or very limited prior knowledge regarding coding. Therefore, we start at the very beginning and learn software development fundamentals using C# and .NET. 

دوره مقدماتی #C
اشتراک‌ها
شروع به استفاده از Rust در کرنل ویندوز

Microsoft is working on integrating Rust, a programming language that emphasizes performance, type safety, and concurrency, into the Windows 11 kernel. According to David Weston, Vice President, Enterprise and OS Security at Microsoft, the company plans to boot Windows 11 with Rust code inside in the near future. 

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

Julia is used for a lot of deeply technical applications like Machine Learning and Data Science. But as a general-purpose programming language, Julia can also be used for things like building websites and interactive visualizations. In this article, we will go over 5 Julia packages that can be used to create desktop GUI’s or web applications.

6 فریمورک زبان جولیا برای ایجاد رابط کاربری دسکتاپ و برنامه های تحت وب
اشتراک‌ها
دوره‌های دات نت در Microsoft Learn

Microsoft Learn For .NET
Free. Cross-platform. Open source.
Learn how to build apps across multiple platforms with programming languages like C#, F#, and Visual Basic. Supported on Windows, Linux, and macOS, get started developing your next project with .NET today. 

دوره‌های دات نت در Microsoft Learn
اشتراک‌ها
کدهای Pascal را توسط NET. اجرا کنید

PascalABC.NET is:

  • The new generation Pascal programming language that combines simplicity of classic Pascal, a great number of modern extensions and broad capabilities of Microsoft .NET Framework.
  • Free, simple and powerful IDE.
  • Built-in form designer for rapid development of Windows desktop applications.
  • Free LGPLv3 license. 
کدهای Pascal را توسط NET. اجرا کنید