مطالب
خلاصه‌ای کوتاه در مورد WinRT

WinRT چیست؟

مایکروسافت جهت سهولت تولید برنامه‌های جدید Metro-style ، لمسی (touch-centric) و tablets ویندوز 8 ، اقدام به بازنویسی مجدد Windows-API کرده و نام آن‌را WinRT گذاشته است. بنابراین همانند آنچه که در مورد API‌ ویندوز از روز اول پیدایش آن مرسوم بوده، این API جدید، از نوع native (نوشته شده با CPP) می‌باشد و با کمک دات نت فریم ورک تهیه نشده است. این API جدید مبتنی بر فناوری قدیمی COM است، بنابراین مطابق معمول توسط هر نوع برنامه‌ و سیستمی در ویندوز قابل دسترسی است. تفاوتی نمی‌کند که CPP یا دلفی باشد یا دات نت. به صورت خلاصه ویندوز 8 دو طراحی جدید (WinRT) و قدیم (Win32 API) را با هم پشتیبانی می‌کند. اگر آن‌را صحیح‌تر بخواهیم معرفی کنیم، WinRT درحقیقت محصور کننده‌ی (Wrapper) همان Win32 API سابق است (در پشت صحنه همان dll های سابق ویندوز را بارگذاری و استفاده می‌کند) جهت تطابق با نیازهای دهه اخیر و سال‌های پیش رو.


سازگاری دات نت فریم ورک با WinRT چگونه است؟

اینبار WinRT برخلاف Win32 API (که در زمان ارائه آن اصلا دات نتی در کار نبود)، جهت سازگاری با دات نت طراحی شده است. این طراحی جدید ILDasm metadata را در اختیار برنامه نویس‌های دات نت قرار می‌دهد و به این ترتیب IntelliSense و قابلیت‌های Debugging ویژوال استودیو همانند کدهای مدیریت شده‌ی دات نت جهت برنامه نویسی مبتنی بر WinRT در اختیار برنامه نویس‌ها خواهد بود (فرمت ارائه شده، ECMA 335 metadata format می‌باشد). همچنین اشیاء COM متعلق به WinRT به خوبی توسط GC (آشغال جمع کن) دات نت جهت مدیریت بهتر حافظه، تحت نظر می‌باشند.
بنابراین از دیدگاه یک برنامه نویس دات نت، کل WinRT به صورت managed assemblies مشاهده می‌شود، اینترفیس‌های آن همان اینترفیس‌های دات نتی خواهند بود و کلاس‌های آن نیز به همین ترتیب. این مشکلی بود/هست که با Win32 API در دات نت وجود دارد و دسترسی به آن به این سادگی و یکپارچگی میسر نیست (هر چند تا الان کل اینترفیس آن جهت استفاده در دات نت نیز ترجمه شده است). در اینجا شما ارجاعاتی را به فایل‌هایی با پسوند winmd یا windows metadata، به پروژه‌ی دات نتی خود اضافه می‌کنید و سپس CLR قادر خواهد بود تا کلیه اطلاعات لازم جهت کار با WinRT را از آن‌ها استخراج کند (این فایل‌ها در پوشه C:\Program Files (x86)\Windows Kits\8.0\Windows Metadata و C:\Windows\system32\winmetadat ویندوز 8 قابل مشاهده و دسترسی هستند).


تفاوت‌های مهم امکانات نمایشی WinRT با Win32 API کدامند؟

تفاوت مهم WinRT با Win32 API از دیدگاه برنامه نویس‌ها، امکان دسترسی بیشتر به آن از طریق زبان‌های مختلف می‌باشد. WinRT همانند Win32 API توسط CPP ، دات نت و سایر روش‌های مرسوم دیگر قابل دسترسی و توسعه است. اما اینبار WinRT برخلاف Win32 API ، از طریق HTML و جاوا اسکریپت هم قابل توسعه است. در این حالت کدهای شما توسط Chakra JavaScript engine که از اینترنت اکسپلورر 9 به بعد ارائه شده، اجرا خواهد شد. بنابراین «برفراز» WinRT دو لایه نمایشی (presentation layer) قابل طراحی و دسترسی است. XAML و زبان‌های متداول برنامه نویسی موجود مانند سی شارپ و وی بی دات نت و غیره. همچنین HTML/CSS هم مجال ابراز وجود یافته است. البته XAML تنها لایه نمایشی کلیه زبان‌های قدیمی موجود مانند سی شارپ، وی بی دات نت، CPP و غیره خواهد بود. به همین جهت Expression Blend جدید نیز از HTML 5 پشتیبانی می‌کند.
همچنین در WinRT ، قسمت‌های GDI و Message loop متداول Win32 API حذف شده است و از DirectX استفاده می‌کند. برای نمونه کدهای XAML شما توسط DirectX رندر می‌شود. البته این مطلب جدیدی نیست و از زمان ارائه WPF شاهد این مساله بوده‌ایم.


وضعیت توسعه پذیری WinRT  چگونه است؟

علاوه بر این‌ها، برخلاف Win32 API ، اینبار WinRT قابل توسعه است و Extensions SDK برای آن ارائه شده است.


آیا WinRT شاهد تغییرات امنیتی خاصی هم بوده است؟

نکته‌ی مهمی که در طراحی WinRT به آن توجه شده است، امنیت می‌باشد. برنامه‌های WinRT شبیه به برنامه‌های سیلورلایت در یک Sandbox اجرا می‌شوند. به این معنا که جهت ذخیره سازی اطلاعات خود از یک isolated storage استفاده می‌کنند. برای کار با file system نیاز به تائید کاربر دارند و خلاصه دیگر به سادگی نمی‌توان از مرزهای این نوع برنامه‌ها رد شد و سیستم عاملی را root کرد. برای نمونه برنامه نویس‌های دات نت دسترسی به فضای نام System.IO.FileStream را دیگر نخواهند داشت و تنها قسمتی از دات نت «برفراز» WinRT و مدل امنیتی جدید آن معنا پیدا می‌کند. همچنین برفراز این API جدید، تولید مثلا Device drivers هم دیگر معنا پیدا نمی‌کند. این محدودیت‌های امنیتی برای برنامه‌ نویس‌های native هم وجود دارد و تفاوتی نمی‌کند. کلا برنامه‌های جدید Metro-style در یک قرنطینه‌ی کامل امنیتی اجرا می‌شوند. برای مثال اگر برنامه‌ای نیاز به دسترسی به یک WebCam را داشته باشد، همانند برنامه‌های سیلورلایت ابتدا باید کاربر تائید کرده و سپس برنامه مجوز امنیتی کار با مثلا یک WebCam را خواهد یافت. همچنین تمام برنامه‌های جدید Metro-style باید جهت ارائه در فروشگاه جدید ویندوز 8، دارای امضای دیجیتال معتبر نیز باشند.


آیا جهت توسعه‌ی برنامه‌های چندریسمانی و غیرهمزمان تمهیدات خاصی در WinRT پیش‌بینی شده است؟

در طراحی جدید WinRT به اعمال asynchronous به شدت توجه شده است. هر عملی که بیش از 50 میلی ثانیه طول بکشد به صورت خودکار تبدیل به یک عمل asynchronous خواهد شد تا برنامه‌ها مرتبا در حین اجرای اعمال زمانبر هنگ نکرده و ترد اصلی برنامه را بلاک نکنند. علاوه بر این‌ها WinRT از طریق IAsyncOperation interface خود، امکان استفاده از واژه‌های جدید کلیدی async/await سی شارپ 5 را نیز مهیا می‌سازد.


آیا WinRT آمده است تا جایگزینی برای دات نت و سیلورلایت و امثال آن باشد؟

خیر. WinRT نگارش دوم Win32 API است با هدف توسعه پذیری، استفاده از دایرکت ایکس و فناوری‌های جدید که عموما از شتاب دهنده‌های سخت افزاری هم بهره‌مند هستند بجای GDI سابق، استفاده ساده‌تر در زبان‌های دیگر به کمک فایل‌های استاندارد Windows Meta data آن می‌باشد. همچنین این API جدید دسترسی به امکانات ویندوز را هم توسط HTML و جاوا اسکریپت، علاوه بر امکانات مهیای سابق میسر ساخته است. هم اکنون نگارش 4 و نیم دات نت در ویندوز 8 ارائه شده است و توسط هر دو سیستم سابق و جدید قابل استفاده می‌باشد. البته باید در نظر داشت که جهت استفاده از WinRT به دلایل محدودیت‌های امنیتی اعمال شده به آن و همچنین استفاده از XAML به تنها عنوان لایه نمایشی سیستم‌های متداول غیر HTML ایی، دات نت فریم ورک به امکانات و کلاس‌های کمتری نسبت به حالت متداول کار با آن، دسترسی دارد (جهت درک بهتر این محدودیت‌ها می‌توان به طراحی سیلورلایت مراجعه کرد). این را هم باید اضافه کرد که ویندوز 8 توانایی اجرای هر دو نوع برنامه‌های سبک جدید مترو و متداول دسکتاپ قدیمی را دارا است.


جهت آشنایی بیشتر با WinRT می‌توان به مجموعه‌ای از ویدیوهای مرتبط آن مراجعه کرد:
http://channel9.msdn.com/Events/BUILD/BUILD2011?t=windows%2Bruntime


مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟

سؤالی از طریق ایمیل از من پرسیده شده که ترجیح می‌دهم آن‌را به صورت باز در اینجا پاسخ دهم. اگر فرض کنیم همین فردا مجبور شویم برای عمده‌ی کارهای خود لایسنس تهیه کنیم، آیا می‌توان از ابزارهای موجود دات نت در یک شرکت تازه تاسیس (startup) استفاده کرد؟ آیا هزینه‌ی کار با ویندوز واقعا بالا است؟ آیا ...
همچنین عموم تازه واردان به این جمع هم از لیست امکانات رایگان مهیا که فقط پس از خرید یک لایسنس اولیه ویندوز در اختیار آن‌ها خواهند بود، بی‌اطلاع هستند. بنابراین بد نیست این‌ها را با هم لیست کنیم.

سؤال: هزینه استفاده از دات نت فریم ورک چقدر است؟
پاسخ: رایگان است! از زمان ارائه ویندوز سرور 2003 به بعد، دات نت فریم ورک به عنوان یکی از کامپوننت‌های اصلی ویندوز عرضه می‌شود و هر شخصی که مجوز اصلی استفاده از ویندوز را خریده باشد، به صورت خودکار مجوز استفاده از دات نت فریم ورک را هم خواهد داشت و نیازی نیست بابت آن هزینه‌ی خاصی را متقبل شود. برای مثال ویندوز سرور 2003 ، دات نت 1.1 سرخود است و ویندوز 7 و ویندوز سرور 2008 ، دات نت سه و نیم سرخود هستند.

سؤال: آیا برای توسعه‌ی دات نت حتما نیاز است تا ویژوال استودیوی چند هزار دلاری را خرید؟!
پاسخ: خیر! ویژوال استودیو، نسخه‌های مختلفی دارد و حتما نیازی نیست تا از نسخه‌ی ultimate آن استفاده کنید. برای مثال نسخه‌ی Express آن که توسط خود مایکروسافت ارائه شده، رایگان است (+). مهم‌ترین تفاوت آن با نسخه‌ی ultimate در این است که افزونه پذیر نیست و این مورد شاید برای خیلی‌ها اصلا اهمیتی نداشته باشد چون عموما افزونه‌های بد نوشته شده، باعث ناپایداری IDE می‌شوند یا مثلا نسخه‌ی ultimate به همراه MSTests جهت انجام ساده‌تر unit testing‌ ارائه شده که در نسخه‌ی Express وجود ندارد، این هم مهم نیست چون فریم ورک‌های سورس باز آزمون واحد دیگری مانند Nunit ، MBUnit و غیره هم وجود دارند که اصلا نیازی به هیچ IDE‌ خاصی جهت کار ندارند و مسایلی از این دست. یا برای سورس کنترل می‌شود از SVN ، Git ، مرکوریال و غیره هم همیشه استفاده کرد. این‌ها هم مستقل هستند از نوع IDE مورد استفاده.

همچنین یک گروه مستقل، IDE دیگری را به نام SharpDevelop تهیه کر‌ده‌اند که بسیار با کیفیت بوده و از همه مهم‌تر سورس باز است و رایگان. خیلی‌ها از کنترل‌های این IDE در پروژه‌های خودشون استفاده می‌کنند (مثل syntax highlighting همراه آن و غیره)

در کنار تمام این‌ها، هیچ وقت دقت کرده‌اید که در پوشه دات نت نصب شده در ویندوز چه چیزهایی یافت می‌شوند؟! مثلا به آدرس C:\Windows\Microsoft.NET\Framework\v4.0.30319 مراجعه کنید. فایل csc.exe همان کامپایلر خط فرمان سی شارپ است. کاری هم که IDE ها انجام می دهند این است که لیست فایل‌های شما رو به صورت آرگومان به این برنامه ارسال می‌کنند. این اتوماسیون به صورت دستی هم میسر است. IDE شما می‌شود مثلا برنامه رایگان notepad++ ایی که syntax highlighting تمام زبان‌های دات نتی را پشتیبانی می‌کند. ساده‌ترین سیستم build شما یک فایل bat خواهد بود که csc.exe را مدیریت می‌کند.

علاوه بر تمام این‌ها، MonoDevelop را هم اضافه کنید. این IDE ، نسخه‌ی ویندوزی هم دارد.

سؤال: من شنیدم دات نت فقط با SQL Server کار می‌کنه. این هم که گرونه؟!
پاسخ: خیر! نسخه‌ی رایگان SQL server به نام SQL Server express هم موجود است و در نسخه‌ی 2008 R2 آن محدودیت حجم بانک اطلاعاتی آن به 10 گیگ رسیده که برای اکثر کارها تا چند سال کافی است (+).
ضمنا قبلا در مورد لیست بانک‌های اطلاعاتی قابل استفاده توسط دات نت فریم ورک مطلب نوشتم و در سایت موجود است. بنابراین دات نت اصلا به SQL Server محدود نیست (+).

سؤال: دات نت سورس بسته است! نه این اصلا خوب نیست؛ آینده نداره!
پاسخ: خیر. سورس کامل دات نت فریم ورک تحت مجوز‌های MS-PL و MS-RSL چندسالی هست که منتشر شده و این مجوز (MS-PL) جزو مجوزهای معتبر و پذیرفته شده سورس باز است (+).
لینک دریافت

خبر مرتبط

ضمنا کارهای سورس باز مایکروسافت به این یک قلم ختم نمی‌شود. برای مثال به سایت CodePlex مراجعه کنید تا سورس کامل ASP.NET MVC نگارش 3 را دریافت کنید(+)، همینطور مجموعه کنترل‌های WPF و Silverlight و غیره‌ای که تهیه‌ کرده‌اند (+) و بسیاری موارد دیگر.

سؤال: دات نت که فقط روی ویندوز اجرا میشه. نه؛ این خوب نیست!
پاسخ: خیر! پروژه‌ای سابقا تحت عنوان Mono وجود دارد/داشت (+) که توسط شرکت ناول اداره می‌شد و کار آن انتقال دات نت به لینوکس و سایر سکوهای کاری بود. پس از فروخته شدن ناول، این پروژه به ظاهر متوقف شد، اما تیم اصلی آن تحت نام دیگری به نام زاماریان (+) دوباره شروع به کار کرده و جالب اینجا است که تا دات نت 4 را هم تحت لینوکس پشتیبانی می‌کند(+). جهت اطلاع ASP.NET رو به صورت کامل می‌تونید تحت لینوکس اجرا کنید (+). همچنین سایر موارد پشتیبانی شده رو.


سؤال: وضعیت بازار کار دات نت در ایران چطور است؟
پاسخ: آقای محبی برنامه‌ای رو درست کرده‌اند به نام کارویس که من مدتی است مشترک فید آن هستم تا حدودا از نیازهای عمومی بازار کار برنامه نویسی ایران اطلاع کسب کنم (+). نکته‌ی جالبی که در فید این برنامه مشخص است، درخواست بالای نیاز به برنامه نویس دات نت در کشور است. بالای 80 درصد آگهی‌ها نیاز به برنامه نویس دات نت دارند.


در کنار تمام این مباحث، بحث «هزینه یادگیری و آموزش» را هم اضافه کنید. شاید یکی بیاد بگه CPP فلان، اون یکی بهمان! ولی این سؤالات هم برای من نوعی که تیم گوگل کروم نوشته شده با CPP رو مدیریت نمی‌کنم (و به قول اونطرف آبی‌ها یک Average Joe هستم!) مهم هستند:
- چندتا کتاب فارسی آموزشی خوب در این زمینه در کشور موجود است. من یادم میاد اولین کتاب VC منتشر شده در کشور پس از 450 صفحه به شما یاد می‌داد چطور با MFC دکمه به صفحه اضافه کنید، چطور منو درست کنید و همین! این شد برنامه نویسی CPP!
- چند عدد انجمن رفع اشکال فعال را که تاریخ آخرین به روز رسانی و پرسش و پاسخ آن‌ها مثلا دیروز بوده را در مورد زبان xyz می‌توان یافت؟
- اگر به مشکل برخوردم، کسی هست به داد من برسه؟ چه زمانی؟! مثلا هفته بعد یا تا آخر امروز؟
- چندتا وبلاگ فعال در این زمینه موجود است؟ آیا هستند کسانی که در این زمینه‌ها فعالانه مطلب منتشر کنند؟ اطلاع رسانی کنند؟
- اگر کارمند برنامه نویس شرکتی امروز قهر کرد، مدیر بخش توسعه تا چه زمانی می‌تونه یک نفر رو جایگزین اون کنه؟ مثلا تا آخر ماه یا تا آخر سال؟! اون هم با چه کیفیتی؟ با چه دستمزدی؟
و اینجا است که وضعیت دات نت در ایران بسیار مطلوب‌تر از موارد مشابه به نظر می‌رسد. از نظر تعداد کتاب فارسی مهیا، تعداد انجمن، تعداد وبلاگ فعال و غیره.

مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟

سؤالی از طریق ایمیل از من پرسیده شده که ترجیح می‌دهم آن‌را به صورت باز در اینجا پاسخ دهم. اگر فرض کنیم همین فردا مجبور شویم برای عمده‌ی کارهای خود لایسنس تهیه کنیم، آیا می‌توان از ابزارهای موجود دات نت در یک شرکت تازه تاسیس (startup) استفاده کرد؟ آیا هزینه‌ی کار با ویندوز واقعا بالا است؟ آیا ...
همچنین عموم تازه واردان به این جمع هم از لیست امکانات رایگان مهیا که فقط پس از خرید یک لایسنس اولیه ویندوز در اختیار آن‌ها خواهند بود، بی‌اطلاع هستند. بنابراین بد نیست این‌ها را با هم لیست کنیم.

سؤال: هزینه استفاده از دات نت فریم ورک چقدر است؟
پاسخ: رایگان است! از زمان ارائه ویندوز سرور 2003 به بعد، دات نت فریم ورک به عنوان یکی از کامپوننت‌های اصلی ویندوز عرضه می‌شود و هر شخصی که مجوز اصلی استفاده از ویندوز را خریده باشد، به صورت خودکار مجوز استفاده از دات نت فریم ورک را هم خواهد داشت و نیازی نیست بابت آن هزینه‌ی خاصی را متقبل شود. برای مثال ویندوز سرور 2003 ، دات نت 1.1 سرخود است و ویندوز 7 و ویندوز سرور 2008 ، دات نت سه و نیم سرخود هستند.

سؤال: آیا برای توسعه‌ی دات نت حتما نیاز است تا ویژوال استودیوی چند هزار دلاری را خرید؟!
پاسخ: خیر! ویژوال استودیو، نسخه‌های مختلفی دارد و حتما نیازی نیست تا از نسخه‌ی ultimate آن استفاده کنید. برای مثال نسخه‌ی Express آن که توسط خود مایکروسافت ارائه شده، رایگان است (+). مهم‌ترین تفاوت آن با نسخه‌ی ultimate در این است که افزونه پذیر نیست و این مورد شاید برای خیلی‌ها اصلا اهمیتی نداشته باشد چون عموما افزونه‌های بد نوشته شده، باعث ناپایداری IDE می‌شوند یا مثلا نسخه‌ی ultimate به همراه MSTests جهت انجام ساده‌تر unit testing‌ ارائه شده که در نسخه‌ی Express وجود ندارد، این هم مهم نیست چون فریم ورک‌های سورس باز آزمون واحد دیگری مانند Nunit ، MBUnit و غیره هم وجود دارند که اصلا نیازی به هیچ IDE‌ خاصی جهت کار ندارند و مسایلی از این دست. یا برای سورس کنترل می‌شود از SVN ، Git ، مرکوریال و غیره هم همیشه استفاده کرد. این‌ها هم مستقل هستند از نوع IDE مورد استفاده.
همچنین یک گروه مستقل، IDE دیگری را به نام SharpDevelop تهیه کر‌ده‌اند که بسیار با کیفیت بوده و از همه مهم‌تر سورس باز است و رایگان. خیلی‌ها از کنترل‌های این IDE در پروژه‌های خودشون استفاده می‌کنند (مثل syntax highlighting همراه آن و غیره)

در کنار تمام این‌ها، هیچ وقت دقت کرده‌اید که در پوشه دات نت نصب شده در ویندوز چه چیزهایی یافت می‌شوند؟! مثلا به آدرس C:\Windows\Microsoft.NET\Framework\v4.0.30319 مراجعه کنید. فایل csc.exe همان کامپایلر خط فرمان سی شارپ است. کاری هم که IDE ها انجام می دهند این است که لیست فایل‌های شما رو به صورت آرگومان به این برنامه ارسال می‌کنند. این اتوماسیون به صورت دستی هم میسر است. IDE شما می‌شود مثلا برنامه رایگان notepad++ ایی که syntax highlighting تمام زبان‌های دات نتی را پشتیبانی می‌کند. ساده‌ترین سیستم build شما یک فایل bat خواهد بود که csc.exe را مدیریت می‌کند.

سؤال: من شنیدم دات نت فقط با SQL Server کار می‌کنه. این هم که گرونه؟!
پاسخ: خیر! نسخه‌ی رایگان SQL server به نام SQL Server express هم موجود است و در نسخه‌ی 2008 R2 آن محدودیت حجم بانک اطلاعاتی آن به 10 گیگ رسیده که برای اکثر کارها تا چند سال کافی است (+).
ضمنا قبلا در مورد لیست بانک‌های اطلاعاتی قابل استفاده توسط دات نت فریم ورک مطلب نوشتم و در سایت موجود است. بنابراین دات نت اصلا به SQL Server محدود نیست (+).

سؤال: دات نت سورس بسته است! نه این اصلا خوب نیست؛ آینده نداره!
پاسخ: خیر. سورس کامل دات نت فریم ورک تحت مجوز MS-PL چندسالی هست که منتشر شده و این مجوز جزو مجوزهای معتبر و پذیرفته شده سورس باز است (+).
لینک دریافت

خبر مرتبط

ضمنا کارهای سورس باز مایکروسافت به این یک قلم ختم نمی‌شود. برای مثال به سایت CodePlex مراجعه کنید تا سورس کامل ASP.NET MVC نگارش 3 را دریافت کنید(+)، همینطور مجموعه کنترل‌های WPF و Silverlight و غیره‌ای که تهیه‌ کرده‌اند (+) و بسیاری موارد دیگر.

سؤال: دات نت که فقط روی ویندوز اجرا میشه. نه؛ این خوب نیست!
پاسخ: خیر! پروژه‌ای سابقا تحت عنوان Mono وجود دارد/داشت (+) که توسط شرکت ناول اداره می‌شد و کار آن انتقال دات نت به لینوکس و سایر سکوهای کاری بود. پس از فروخته شدن ناول، این پروژه به ظاهر متوقف شد، اما تیم اصلی آن تحت نام دیگری به نام زاماریان (+) دوباره شروع به کار کرده و جالب اینجا است که تا دات نت 4 را هم تحت لینوکس پشتیبانی می‌کند(+). جهت اطلاع ASP.NET رو به صورت کامل می‌تونید تحت لینوکس اجرا کنید (+). همچنین سایر موارد پشتیبانی شده رو.


سؤال: وضعیت بازار کار دات نت در ایران چطور است؟
پاسخ: آقای محبی برنامه‌ای رو درست کرده‌اند به نام کارویس که من مدتی است مشترک فید آن هستم تا حدودا از نیازهای عمومی بازار کار برنامه نویسی ایران اطلاع کسب کنم (+). نکته‌ی جالبی که در فید این برنامه مشخص است، درخواست بالای نیاز به برنامه نویس دات نت در کشور است. بالای 80 درصد آگهی‌ها نیاز به برنامه نویس دات نت دارند.


در کنار تمام این مباحث، بحث «هزینه یادگیری و آموزش» را هم اضافه کنید. شاید یکی بیاد بگه CPP فلان، اون یکی بهمان! ولی این سؤالات هم برای من نوعی که تیم گوگل کروم نوشته شده با CPP رو مدیریت نمی‌کنم (و به قول اونطرف آبی‌ها یک Average Joe هستم!) مهم هستند:
- چندتا کتاب فارسی آموزشی خوب در این زمینه در کشور موجود است. من یادم میاد اولین کتاب VC منتشر شده در کشور پس از 450 صفحه به شما یاد می‌داد چطور با MFC دکمه به صفحه اضافه کنید، چطور منو درست کنید و همین! این شد برنامه نویسی CPP!
- چند عدد انجمن رفع اشکال فعال را که تاریخ آخرین به روز رسانی و پرسش و پاسخ آن‌ها مثلا دیروز بوده را در مورد زبان xyz می‌توان یافت؟
- اگر به مشکل برخوردم، کسی هست به داد من برسه؟ چه زمانی؟! مثلا هفته بعد یا تا آخر امروز؟
- چندتا وبلاگ فعال در این زمینه موجود است؟ آیا هستند کسانی که در این زمینه‌ها فعالانه مطلب منتشر کنند؟ اطلاع رسانی کنند؟
- اگر کارمند برنامه نویس شرکتی امروز قهر کرد، مدیر بخش توسعه تا چه زمانی می‌تونه یک نفر رو جایگزین اون کنه؟ مثلا تا آخر ماه یا تا آخر سال؟! اون هم با چه کیفیتی؟ با چه دستمزدی؟
و اینجا است که وضعیت دات نت در ایران بسیار مطلوب‌تر از موارد مشابه به نظر می‌رسد. از نظر تعداد کتاب فارسی مهیا، تعداد انجمن، تعداد وبلاگ فعال و غیره.

مطالب
فقط به خاطر یک نیم فاصله!
نسخه‌ی 64 بیتی ویندوز 7 را نصب کرده‌ام و اولین مشکل، نبود صفحه کلید مطابق استاندارد 2091 برای نسخه‌های 64 بیتی ویندوز است. پروژه وب فارسی دانشگاه صنعتی شریف، سال‌ها قبل فقط یک نسخه‌ی 32 بیتی از آن را تهیه کرده و نسخه‌های 64 بیتی موجود، مطابق استاندارد 9147 هستند و من به دلایل ذیل حاضر به استفاده از آن نیستم!
  1. در استاندارد 9147 ، جای ژ و پ مطابق صفحه کلیدی که در بازار ایران فروخته می‌شود نیست (و باید به همه پاسخگو باشید که چرا اینطوری است).
  2. ی و ک در آن اصلاح شده و دیگر عربی نیست که چقدر هم خوب، اما من تعداد زیادی برنامه و همچنین تعداد قابل توجهی فایل word دارم که مطابق استاندارد 2091 تهیه شده‌اند. مشکلات ی و ک فارسی و عربی را هم در اینجا ذکر کرده‌ام و به دنبال باگ‌های جدید در برنامه‌ها نمی‌گردم.
  3. با ی و ک فارسی اگر در گوگل جستجو کنید تعداد پاسخ‌های مرتبط یافت شده بسیار بسیار کمتر از حالتی خواهد بود که با ی و ک عربی جستجو کنید.
  4. در یک سازمان باید رویه‌ا‌ی واحد در این مورد برقرار باشد. یا همه باید از 2091 استفاده کنند و یا همه از 9147.

نسخه‌ی کامل استاندارد 9147 را از سایت آقای اخگری می‌توانید دریافت نمائید:

به همین دلایل خصوصا قسمت دوم (هر چند ممکن است با آن موافق نباشید)، نیاز به صفحه کلید مطابق استاندارد 2091 نسخه‌ی 64 بیتی داشتم.

برای تهیه صفحه کلید فارسی از برنامه Microsoft Keyboard Layout Creator استفاده می‌شود که نسخه‌ی 1.4 آن‌را از آدرس زیر می‌توانید دریافت نمائید. این نسخه قابلیت تولید فایل‌های dll مرتبط 64 بیتی را هم دارد:


یکی از قابلیت‌های این برنامه بارگذاری صفحه کلید فارسی جاری سیستم است:



صفحه کلید استاندارد 2091 پروژه وب فارسی را اگر با آن باز کرده و سپس از منوی Project گزینه‌ی Build DLL and setup package را انتخاب نمائید، با خطای زیر متوقف خواهید شد:
ERROR: 'VK_SPACE' in Shift State 'Shift' must be made up of white space character(s), but is defined as '‌' (U+200c) instead.
که دقیقا مربوطه به این سطر در تعاریف صفحه کلید است (صفحه کلید را می‌شود به صورت فایلی با پسوند klc هم save as کرد):
39 SPACE  0 0020 200c 0020 -1  // SPACE, ZERO WIDTH NON-JOINER, SPACE, <none>
مفاهیم این ستون‌ها هم به شرح زیر هستند:
0 //Column 4
1 //Column 5 : Shft
2 //Column 6 :       Ctrl
3 //Column 7 : Shft  Ctrl
یعنی جهت نمایش نیم فاصله حاصل از ترکیب shift space مطابق استاندارد تایپ ایران، بجای 0x0020 یا همان فاصله، از 0x200C استفاده شده است (مطابق استاندارد تایپ ایران باید نوشت "می‌روم" و نه "می روم". به نیم فاصله و فاصله دقت بفرمائید).
اما این برنامه‌ی محترم دقیقا همین مورد را غلط گرفته و فایل dll نهایی را تولید نمی‌کند (مطابق خطایی که ذکر شد).
برنامه‌ی Microsoft Keyboard Layout Creator هم با دات نت فریم ورک نوشته شده است. اگر کمی با reflector به آنالیز آن بپردازیم به کلاس Accept و متد زیر خواهیم رسید:
private bool VerifySpaceBarIntegrity(ShiftState ss, bool fMustBeDefined)
در این متد جایی که خطای ذکر شده صادر می‌شود، مربوط به چند سطر زیر است:
if (!Utilities.IsSpacing(ss.Characters))
{
    this.WriteErrorToLogFile("SpaceKeyNeedsWhiteSpaceCharacters", new string[] { "VK_" + Utilities.VkStringOfIvk(ss.VK), this.m_stStateLabel[ss.State], ss.Characters, Utilities.FromCharacterToUPlusForm(ss.Characters) });
    flag = false; /*اینجا باید اصلاح شود!*/
}
خوب! به نظر شما ایرادی دارد که این flag همیشه true باشد؟! برای همین منظور، فایل MSKLC.exe را پچ کرده‌ام که از آدرس زیر قابل دریافت است:

  دریافت پچ شده نگارش 1.4.6000.2 : MSKLC.Patched.7z  

با استفاده از این نسخه (ابتدا برنامه اصلی را نصب کرده و سپس فایل exe را جایگزین نمائید)، به راحتی می‌توان نسخه‌ی استاندارد و اصلی 2091 را بارگذاری و مجددا کامپایل کرد (بدون توقف کامپایل به خاطر فقط یک نیم فاصله‌ی غیرمعتبر از دیدگاه نویسندگان اصلی برنامه). به این صورت فایل‌های 64 بیتی لازم هم تولید می‌شوند ( ممکن است در حین کار با برنامه‌ی patch شده، نمایش خطای کار با نیم فاصله را مجددا دریافت کنید؛ اما مهم نیست . نمونه‌ی وصله شده، بدون مشکل حاصل را کامپایل می‌کند که در نمونه‌ی اصلی اینطور نیست. یعنی پس از overwrite کردن فایل exe موجود، با نمونه patch شده، برنامه کار کامپایل را کامل می‌کند) که حاصل نهایی را از آدرس زیر می‌توانید دریافت نمائید:


این فایل‌ها دقیقا بر مبنای همان فایل‌های پروژه وب فارسی استاندارد 2091 هستند؛ با این تفاوت که نسخه‌‌های غیر 32 بیتی هم در آن لحاظ شده‌اند.

نصب این dll‌ها هم از ویندوز ویستا به بعد داستان خودش را پیدا کرده؛ ابتدا باید take ownership شود، سپس می‌توان فایل اصلی را به سادگی جایگزین کرد و سپس reboot .
برای این منظور ابتدا به پوشه‌ی system32 ویندوز مراجعه کرده و فایل KBDFA.DLL را پیدا کنید.
در ادامه به پنجره خواص آن (کلیک راست و انتخاب properties) مراجعه و برگه‌ی Security آن‌را انتخاب کنید.
در این قسمت بر روی دکمه‌ی Advanced کلیک نمائید.
در صفحه‌ی باز شده به برگه‌ی Owner مراجعه کنید.
در این قسمت بر روی دکمه‌ی Edit کلیک نموده و کاربر خودتان را اضافه نمائید.
پس از طی این مرحله (یا همان take ownership) به برگه security مراجعه کرده و به کاربر خودتان دسترسی full control بدهید.
اکنون مجوز تغییر این فایل را بدون هیچ مشکلی خواهید یافت.
در پایان reboot را فراموش نکنید.

راه دوم:
یا برنامه نصاب حاصل از برنامه‌ی Microsoft Keyboard Layout Creator، مدخل جدیدی را به صفحه کلیدهای قابل انتخاب ویندوز در کنترل پنل اضافه می‌کند که نیازی به reboot ندارد.
مسیرراه‌ها
Entity framework code-first
شروع به کار با EF Code first

برای تکمیل بحث نیاز است تغییرات انجام شده از نگارش 4 به 6 را نیز مد نظر داشته باشید:


آشنایی با مباحث Migrations



آشنایی با تنظیمات نگاشت‌ها به دو روش استفاده از ویژگی‌ها و Fluent API



اعتبارسنجی و بررسی استثناءها



ردیابی تغییرات



استفاده از SQL خام و بانک‌های اطلاعاتی متفاوت

      نکات مهم کوئری نویسی در EF



      استفاده از EF در WPF


      لایه بندی پروژه‌های EF Code first



      پروژ‌ه‌های انجام شده با EF Code first

       
      مطالب دوره‌ها
      استفاده از XQuery - قسمت دوم
      در ادامه‌ی مباحث XQuery، سایر قابلیت‌های توکار SQL Server را برای کار با اسناد XML بررسی خواهیم کرد.

      کوئری گرفتن از اسناد XML دارای فضای نام، توسط XQuery

      در مثال زیر، تمام المان‌های سند XML، در فضای نام http://www.people.com تعریف شده‌اند.
      DECLARE @doc XML 
      SET @doc ='
      <p:people xmlns:p="http://www.people.com">
       <p:person name="Vahid" /> 
       <p:person name="Farid" />
      </p:people>
      '
      SELECT @doc.query('/people/person')
      اگر کوئری فوق را برای یافتن اشخاص اجرا کنیم، خروجی آن خالی خواهد بود (و یا یک empty sequence)؛ زیرا کوئری نوشته شده به دنبال اشخاصی است که در فضای نام خاصی تعریف نشده‌اند.
      سعی دوم احتمالا روش ذیل خواهد بود
       SELECT @doc.query('/p:people/p:person')
      که به خطای زیر منتهی می‌شود:
       XQuery [query()]: The name "p" does not denote a namespace.
      برای حل این مشکل باید از مفهومی به نام prolog استفاده کرد. هر XQuery از دو قسمت prolog و body تشکیل می‌شود. قسمت prolog می‌تواند شامل تعاریف فضاهای نام، متغیرها، متدها و غیره باشد و قسمت body، همان کوئری تهیه شده‌است. البته SQL Server از قسمت prolog استاندارد XQuery، فقط تعریف فضاهای نام آن‌را مطابق مثال ذیل پشتیبانی می‌کند:
       SELECT @doc.query('
      declare default element namespace "http://www.people.com";
      /people/person
      ')
      یک سند XML ممکن است با بیش از یک فضای نام تعریف شود. در این حالت خواهیم داشت:
       SELECT @doc.query('
      declare namespace aa="http://www.people.com";
      /aa:people/aa:person
      ')
      در اینجا در قسمت prolog، برای فضای نام تعریف شده در سند XML، یک پیشوند را تعریف کرده و سپس، استفاده از آن مجاز خواهد بود.
      روش دیگر تعریف فضای نام، استفاده از WITH XMLNAMESPACES، پیش از تعریف کوئری است:
       WITH XMLNAMESPACES(DEFAULT 'http://www.people.com')
      SELECT @doc.query('/people/person')
      البته باید دقت داشت، زمانیکه WITH XMLNAMESPACES تعریف می‌شود، عبارت T-SQL پیش از آن باید با یک سمی‌کالن خاتمه یابد؛ و گرنه یک خطای دستوری خواهید گرفت.
      در اینجا نیز امکان کار با چندین فضای نام وجود دارد و برای این منظور تنها کافی است از تعریف Alias استفاده شود. فضاهای نام بعدی با یک کاما از هم مجزا خواهند شد.
       WITH XMLNAMESPACES('http://www.people.com' AS aa)
      SELECT @doc.query('/aa:people/aa:person')


      عبارات XPath و FLOWR

      XQuery از دو نوع عبارت XPath و FLOWR می‌تواند استفاده کند. XQuery همیشه از XPath برای انتخاب داده‌ها و نودها استفاده می‌کند. در اینجا هر نوع XPath سازگار با استاندارد 2 آن، یک XQuery نیز خواهد بود. برای انجام اعمالی بجز انتخاب داده‌ها، باید از عبارات FLOWR استفاده کرد؛ برای مثال برای ایجاد حلقه، مرتب سازی و یا ایجاد نودهای جدید.
      در مثال زیر که data آن در قسمت قبل تعریف شد، دو کوئری نوشته شده یکی هستند:
       SELECT @data.query('
       (: FLOWE :)
       for $p in /people/person
       where $p/age > 30
       return $p
       ')
      
      SELECT @data.query('
      (: XPath :)
      /people/person[age>30]
      ')
      اولین کوئری به روش FLOWR تهیه شده‌است و دومین کوئری از استاندارد XPath استفاده می‌کند. از دیدگاه SQL Server این دو یکی بوده و حتی Query Plan یکسانی نیز دارند.

       XPath بسیار شبیه به مسیر دهی‌های یونیکسی است. بسیار فشرده بوده و همچنین مناسب است برای کار با ساختارهای تو در تو و سلسله مراتبی. مثال زیر را درنظر بگیرید:
       /books/book[1]/title/chapter
      در اینجا books، المان ریشه است. سپس به اولین کتاب این ریشه اشاره می‌شود. سپس به المان عنوان و مسیر نهایی، به فصل ختم می‌شود. البته همانطور که در قسمت‌های پیشین نیز ذکر شد، حالت content، پیش فرض بوده و یک فیلد XML می‌تواند دارای چندین ریشه باشد.

      در XPath توسط قابلیتی به نام محور می‌توان به المان‌های قبلی یا بعدی دسترسی پیدا کرد. این محورهای پشتیبانی شده در SQL Server عبارتند از self (خود نود)، child (فرزند نود)، parent (والد نود)، decedent (فرزند فرزند فرزند ...)و attribute (دسترسی به ویژگی‌ها). محورهای استانداردی مانند preceding-sibling و following-sibling در SQL Server با عملگرهایی مانند >> و << پشتیبانی می‌شوند.



      مثال‌هایی از نحوه‌ی استفاده از محورهای XPath

      اینبار قصد داریم یک سند XML نسبتا پیچیده را بررسی کرده و اجزای مختلف آن‌را به کمک XPath بدست بیاوریم.
      DECLARE @doc XML 
      SET @doc='
      <Team name="Project 1" xmlns:a="urn:annotations">
        <Employee id="544" years="6.5">
          <Name>User 1</Name>
      <Title>Architect</Title>
      <Expertise>Games</Expertise>
      <Expertise>Puzzles</Expertise>
      <Employee id="101" years="7.1" a:assigned-to="C1">
       <Name>User 2</Name>
       <Title>Dev lead</Title>
       <Expertise>Video Games</Expertise>
       <Employee id="50" years="2.3" a:assigned-to="C2">
       <Name>User 3</Name>
       <Title>Developer</Title>
       <Expertise>Hardware</Expertise>
       <Expertise>Entertainment</Expertise>
      </Employee>
      </Employee> 
        </Employee>
      </Team>
      '
      در این سند، کارمند و کارمندانی را که باید به یک کارمند گزارش دهند، ملاحظه می‌کنید.
      در XPath، محور پیش فرض، child است (اگر مانند کوئری زیر مورد خاصی ذکر نشود):
       SELECT @doc.query('/Team/Employee/Name')
      و اگر بخواهیم این محور را به صورت صریح ذکر کنیم، به نحو ذیل خواهد بود:
       SELECT @doc.query('/Team/Employee/child::Name')
      خروجی آن User1 است.
       <Name>User 1</Name>
      برای ذکر محور decedent-or-self می‌توان از // نیز استفاده کرد:
       SELECT @doc.query('//Employee/Name')
      با خروجی
       <Name>User 1</Name>
      <Name>User 2</Name>
      <Name>User 3</Name>
      در این حالت به تمام نودهای سند، در سطوح مختلف آن مراجعه شده و به دنبال نام کارمند خواهیم گشت.

      برای کار با ویژگی‌ها و attributes از [] به همراه علامت @ استفاده می‌شود:
       SELECT @doc.query('
      declare namespace a = "urn:annotations";
      //Employee[@a:assigned-to]/Name
      ')
      در این کوئری، تمام کارمندانی که دارای ویژگی assigned-to واقع در فضای نام urn:annotations هستند، یافت خواهند شد. با خروجی:
       <Name>User 2</Name>
      <Name>User 3</Name>
      معادل طولانی‌تر آن ذکر کامل محور attribute است بجای @
       SELECT @doc.query('
      declare namespace a = "urn:annotations";
      //Employee[attribute::a:assigned-to]/Name
      ')
      و برای یافتن کارمندانی که دارای ویژگی assigned-to نیستند، می‌توان از عملگر not استفاده کرد:
       SELECT @doc.query('
      declare namespace a = "urn:annotations";
      //Employee[not(@a:assigned-to)]/Name
      ')
      با خروجی
       <Name>User 1</Name>
      و اگر بخواهیم تعداد کارمندانی را که به user 1 مستقیما گزارش می‌دهند را بیابیم، می‌توان از count به نحو ذیل استفاده کرد:
       SELECT @doc.query('count(//Employee[Name="User 1"]/Employee)')

      در XPath برای یافتن والد از .. استفاده می‌شود:
       SELECT @doc.query('//Employee[../Name="User 1"]')
      برای مثال در کوئری فوق، کارمندانی که والد آن‌ها user 1 هستند، یافت می‌شوند.
      استفاده از .. در SQL Server به دلایل کارآیی پایین توصیه نمی‌شود. بهتر است از همان روش قبلی کوئری تعداد کارمندانی که به user 1 مستقیما گزارش می‌دهند، استفاده شود.



      عبارات FLOWR

      FLOWR هسته‌ی XQuery را تشکیل داده و قابلیت توسعه XPath را دارد. FLOWR مخفف for، let، order by، where و retrun است. از for برای تشکیل حلقه، از let برای انتساب، از where و order by برای فیلتر و مرتب سازی اطلاعات و از return برای بازگشت نتایج کمک گرفته می‌شود. FLOWR بسیار شبیه به ساختار SQL عمل می‌کند.
      معادل عبارت SQL
       Select p.name, p.job
      from people as p
      where p.age > 30
      order by p.age
      با عبارات FLOWR، به صورت زیر است:
       for $p in /people/person
      where $p.age > 30
      order by $p.age[1]
      return ($p/name, $p/job)
      همانطور که مشاهده می‌کنید علت انتخاب FLOWR در اینجا عمدی بوده‌است؛ زیرا افرادی که SQL می‌دانند به سادگی می‌توانند شروع به کار با عبارات FLOWR کنند.
      تنها تفاوت مهم، در اینجا است که در عبارات SQL، خروجی کار توسط select، در ابتدای کوئری ذکر می‌شود، اما در عبارات FLOWR در انتهای آن‌ها.

      از let برای انتساب مجموعه‌ای از نودها استفاده می‌شود:
       let $p := /people/person
      return $p
      تفاوت آن با for در این است که در هر بار اجرای حلقه‌ی for، تنها با یک نود کار خواهد شد، اما در let با مجموعه‌ای از نودها سر و کار داریم. همچنین let از نگارش 2008 اس کیوال سرور به بعد قابل استفاده‌است.

      یک نکته
      اگر به order by  دقت کنید، به اولین سن اشاره می‌کند. Order by در اینجا با تک مقدارها کار می‌کند و امکان کار با مجموعه‌ای از نودها را ندارد. به همین جهت باید طوری آن‌را تنظیم کرد که هربار فقط به یک مقدار اشاره کند.
      هر زمانیکه به خطای requires a singleton برخوردید، یعنی دستورات مورد استفاده با یک سری از نودها کار نکرده و نیاز است دقیقا مشخص کنید، کدام مقدار مدنظر است.


      مثال‌هایی از عبارات FLOWR

      دو کوئری ذیل یک خروجی 1 2 3 را تولید می‌کنند
       DECLARE @x XML = '';
      SELECT @x.query('
      for $i in (1,2,3)
      return $i
      ');
      
      SELECT @x.query('
      let $i := (1,2,3)
      return $i
      ');
      در کوئری اول، هر بار که حلقه اجرا می‌شود، به یکی از اعضای توالی دسترسی خواهیم داشت. در کوئری دوم، یکبار توالی تعریف شده و کار با آن در یک مرحله صورت می‌گیرد.
      در ادامه اگر سعی کنیم به این کوئری‌ها یک order by را اضافه کنیم، کوئری اول با موفقیت اجرا شده،
       DECLARE @x XML = '';
      SELECT @x.query('
      for $i in (1,2,3)
      order by $i descending
      return $i
      ');
      
      SELECT @x.query('
      let $i := (1,2,3)
      order by $i descending
      return $i
      ');
      اما کوئری دوم با خطای ذیل متوقف می‌شود:
       XQuery [query()]: 'order by' requires a singleton (or empty sequence), found operand of type 'xs:integer +'
      در خطا عنوان شده‌است که مطابق تعریف، order by با یک مجموعه از نودها، مانند حاصل let کار نمی‌کند و همانند حلقه for نیاز به singleton یا atomic values دارد.


      ساخت المان‌های جدید XML توسط عبارات FLOWR

      ابتدا همان سند XML قسمت قبل را درنظر بگیرید:
      DECLARE @doc XML  =' 
      <people>
       <person>
        <name>
      <givenName>name1</givenName>
      <familyName>lname1</familyName>
        </name>
        <age>33</age>
        <height>short</height>
       </person>
       <person>
        <name>
      <givenName>name2</givenName>
      <familyName>lname2</familyName>
        </name>
        <age>40</age>
        <height>short</height>
       </person>
       <person>
        <name>
      <givenName>name3</givenName>
      <familyName>lname3</familyName>
        </name>
        <age>30</age>
        <height>medium</height>
       </person>
      </people>
      '
      در ادامه قصد داریم، المان‌های اشخاص را صرفا بر اساس مقدار givenName آن‌ها بازگشت دهیم:
       SELECT @doc.query('
      for $p in /people/person
      return <person>
      {$p/name[1]/givenName[1]/text()}
      </person>
      ');
      در اینجا نحوه‌ی تولید پویای تگ‌های XML را توسط FLOWR مشاهده می‌کنید. عبارات داخل {} به صورت خودکار محاسبه و جایگزین می‌شوند و خروجی آن به شرح زیر است:
       <person>name1</person>
      <person>name2</person>
      <person>name3</person>

      سؤال: اگر به این خروجی بخواهیم یک root element اضافه کنیم، چه باید کرد؟ اگر المان root دلخواهی را در return قرار دهیم، به ازای هر آیتم یافت شده، یکبار تکرار می‌شود که مدنظر ما نیست.
       SELECT @doc.query('
      <root>
      {
      for $p in /people/person
      return <person>
      {$p/name[1]/givenName[1]/text()}
      </person>
      }
      </root>
      ');
      بله. در این حالت نیز می‌توان از همان روشی که در return استفاده کردیم، برای کل حلقه و return آن استفاده کنیم. المان root به صورت استاتیک محاسبه می‌شود و هر آنچه که داخل {} باشد، به صورت پویا. با این خروجی:
       <root>
        <person>name1</person>
        <person>name2</person>
        <person>name3</person>
      </root>


      مفهوم quantification در FLOWR

      همان سند Team name=Project 1 ابتدای بحث جاری را درنظر بگیرید.
       SELECT @doc.query('some $emp in //Employee satisfies $emp/@years >5')
      -- true
      
      SELECT @doc.query('every $emp in //Employee satisfies $emp/@years >5')
      -- false
      به عبارات some و every در اینجا quantification گفته می‌شود. در کوئری اول، می‌خواهیم بررسی کنیم، آیا در بین کارمندان، بعضی از آن‌ها دارای ویژگی (با @ شروع شده) years بیشتر از 5 هستند. در کوئری دوم، عبارت «بعضی» به «هر» تغییر یافته است. 
      مطالب دوره‌ها
      شی گرایی در #F
      برنامه نویسی شی گرای سومین نسل از الگوهای اصلی برنامه نویسی است. در توضیحات فصل اول گفته شد که #F یک زبان تابع گرا است ولی این بدان معنی نیست که #F از مفاهیمی نظیر کلاس و یا interface پشتیبانی نکند. برعکس در #F امکان تعریف کلاس و interface و هم چنین پیاده سازی مفاهیم شی گرایی وجود دارد.

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

      Classes
      کلاس چارچوبی از اشیا است برای نگهداری خواص(Properties) و رفتار ها(Methods) و رخدادها(Events). کلاس پایه ای‌ترین مفهوم در برنامه نویسی شی گراست. ساختار کلی تعربف کلاس در #F به صورت زیر است:
      type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
         [ class ]
           [ inherit base-type-name(base-constructor-args) ]
           [ let-bindings ]
           [ do-bindings ]
           member-list
            ...
         [ end ]
      
      type [access-modifier] type-name1 ...
      and [access-modifier] type-name2 ...
      ...
      همان طور که در ساختار بالا می‌بینید مفاهیم access-modifier و inherit و constructor هم در #F وجود دارد.

      انواع access-modifier در #F
      • public : دسترسی برای تمام فراخوان‌ها امکان پذیر است
      • internal : دسترسی برای تمام فراخوان هایی که در همین assembly هستند امکان پذیر است
      • private : دسترسی فقط برای فراخوان‌های موجود در همین ماژول امکان پذیر است

      نکته : protected access modifier در #F پشتیبانی نمی‌شود.

      مثالی از تعریف کلاس:

      type Account(number : int, name : string) = class
          let mutable amount = 0m
         
      end
      کلاس بالا دارای یک سازنده است که دو پارامتر ورودی می‌گیرد. کلمه end به معنای انتهای کلاس است. برای استفاده کلاس باید به صورت زیر عمل کنید:
      let myAccount = new Account(123456, "Masoud")
      توابع و خواص در کلاس ها
      برای تعریف خاصیت در #F باید از کلمه کلیدی member استفاده کنید. در مثال بعدی برای کلاس بالا تابع و خاصیت تعریف خواهیم کرد.
      type Account(number : int, name: string) = class
          let mutable amount = 0m
       
          member x.Number = number
          member x.Name= name
          member x.Amount = amount
       
          member x.Deposit(value) = amount <- amount + value
          member x.Withdraw(value) = amount <- amount - value
      end
      کلاس بالا دارای سه خاصیت به نام‌های Number و Name و Amount است و دو تابع به نام‌های Deposit و Withdraw دارد. اما x استفاده شده قبل از هر member به معنی this در #C  است. در #F شما برای اشاره به شناسه‌های یک محدوده خودتون باید یک نام رو برای اشاره گر مربوطه تعیین کنید.
      open System
       
      type Account(number : int, name: string) = class
          let mutable amount = 0m
       
          member x.Number = number
          member x.Name= name
          member x.Amount = amount
       
          member x.Deposit(value) = amount <- amount + value
          member x.Withdraw(value) = amount <- amount - value
      end
       let masoud= new Account(12345, "Masoud") let saeed = new Account(67890, "Saeed") let transfer amount (source : Account) (target : Account) = source.Withdraw amount target.Deposit amount let printAccount (x : Account) = printfn "x.Number: %i, x.Name: %s, x.Amount: %M" x.Number x.Name x.Amount let main() = let printAccounts() = [masoud; saeed] |> Seq.iter printAccount printfn "\nInializing account" homer.Deposit 50M marge.Deposit 100M printAccounts() printfn "\nTransferring $30 from Masoud to Saeed" transfer 30M masoud saeed
       printAccounts() printfn "\nTransferring $75 from Saeed to Masoud" transfer 75M saeed masoud printAccounts() main()
      استفاده از کلمه do
      در #F زمانی که قصد داشته باشیم در بعد از وهله سازی از کلاس و فراخوانی سازنده، عملیات خاصی انجام شود(مثل انجام برخی عملیات متداول در سازنده‌های کلاس‌های دات نت) باید از کلمه کلیدی do به همراه یک بلاک از کد استفاده کنیم.
      open System
      open System.Net
       
      type Stock(symbol : string) = class
      
          let mutable _symbol = String.Empty
          do
           //کد مورد نظر در این جا نوشته  میشود
      end
      
      یک مثال در این زمینه:

      open System
      
      type MyType(a:int, b:int) as this =
          inherit Object()
          let x = 2*a
          let y = 2*b
          do printfn "Initializing object %d %d %d %d %d %d"
                     a b x y (this.Prop1) (this.Prop2)
          static do printfn "Initializing MyType." 
          member this.Prop1 = 4*x
          member this.Prop2 = 4*y
          override this.ToString() = System.String.Format("{0} {1}", this.Prop1, this.Prop2)
      
      let obj1 = new MyType(1, 2)
      در مثال بالا دو عبارت do  یکی به صورت static و دیگری به صورت غیر static تعریف شده اند. استفاده از do  به صورت غیر static این امکان را به ما می‌دهد که بتوانیم به تمام شناسه‌ها و توابع تعریف شده در کلاس استفاده کنیم ولی do به صورت static فقط به خواص و توابع از نوع static در کلاس دسترسی دارد.
      خروجی مثال بالا:
      Initializing MyType.
      Initializing object 1 2 2 4 8 16
      خواص static:
      برای تعریف خواص به صورت استاتیک مانند #C از کلمه کلیدی static استفاده کنید.مثالی در این زمینه:
      type SomeClass(prop : int) = class
          member x.Prop = prop
          static member SomeStaticMethod = "This is a static method"
      end
      SomeStaticMethod به صورت استاتیک تعریف شده در حالی که x.Prop به صورت غیر استاتیک. دسترسی به متد‌ها یا خواص static باید بدون وهله سازی از کلاس انجام بگیرد در غیر این صورت با خطای کامپایلر روبرو خواهید شد.
      let instance = new SomeClass(5);;
      instance.SomeStaticMethod;; 
      
      output:
      stdin(81,1): error FS0191: property 'SomeStaticMethod' is static.
      روش استفاده درست:
      SomeClass.SomeStaticMethod;; (* invoking static method *)
      متد‌های get , set در خاصیت ها:
      همانند #C و سایر زبان‌های دات نت امکان تعریف متد‌های get و set برای خاصیت‌های یک کلاس وجود دارد.
      ساختار کلی:
       member alias.PropertyName
              with get() = some-value
              and set(value) = some-assignment
      مثالی در این زمینه:
      type MyClass() = class
         let mutable num = 0 
          member x.Num
              with get() = num
              and set(value) = num <- value
      end;;
      کد متناظر در #C:
      public int Num
      {
         get{return num;}
         set{num=value;}
      }
      یا به صورت:
      type MyClass() = class
          let mutable num = 0
       
          member x.Num
              with get() = num
              and set(value) =
                  if value > 10 || value < 0 then
                      raise (new Exception("Values must be between 0 and 10"))
                  else
                      num <- value
      end

      Interface ها
      اینترفیس به تمامی خواص و توابع عمومی اشئایی که آن را پیاده سازی کرده اند اشاره می‌کند. (توضیحات بیشتر (^ ) و (^ ))ساختار کلی برای تعریف آن به صورت زیر است:
      type type-name = 
         interface
             inherits-decl 
             member-defns 
         end
      مثال:
      type IPrintable =
         abstract member Print : unit -> unit
      
      استفاده از حرف I برای شروع نام اینترفیس طبق قوانین تعریف شده (اختیاری) برای نام گذاری است.
      نکته: در هنگام تعریف توابع و خاصیت در interface‌ها باید از کلمه abstract استفاده کنیم. هر کلاسی که از یک یا چند تا اینترفیس ارث ببرد باید تمام خواص و توابع اینتریس‌ها را پیاده سازی کند. در مثال بعدی کلاس SomeClass1 اینترفیس بالا را پیاده سازی می‌کند. دقت کنید که کلمه this توسط من به عنوان اشاره گر به اشیای کلاس تعیین شده و شما می‌تونید از هر کلمه یا حرف دیگری استفاده کنید.
      type SomeClass1(x: int, y: float) =
         interface IPrintable with 
            member this.Print() = printfn "%d %f" x y
      نکته مهم: اگر قصد فراخوانی متد Print را در کلاس بالا دارید نمی‌تونید به صورت مستقیم متد بالا را فراخوانی کنید. بلکه حتما باید کلاس به اینترفیس مربوطه cast شود.
      روش نادرست:
      let instance = new SomeClass1(10,20)
      instance.Print//فراخوانی این متد باعث ایجاد خطای کامپایلری می‌شود.
      
      روش درست:
      let instance = new SomeClass1(10,20) 
      let instanceCast = instance :> IPrintable// استفاده از (<:)  برای عملیات تبدیل کلاس به اینترفیس
      instanceCast.Print
      برای عملیات cast ازاستفاده کنید.
      در مثال بعدی کلاسی خواهیم داشت که از سه اینترفیس ارث می‌برد. در نتیجه باید تمام متد‌های هر سه اینترفیس را پیاده سازی کند.
      type Interface1 =
          abstract member Method1 : int -> int
      
      type Interface2 =
          abstract member Method2 : int -> int
      
      type Interface3 =
          inherit Interface1
          inherit Interface2
          abstract member Method3 : int -> int
      
      type MyClass() =
          interface Interface3 with 
              member this.Method1(n) = 2 * n
              member this.Method2(n) = n + 100
              member this.Method3(n) = n / 10
      فراخوانی این متد‌ها نیز به صورت زیر خواهد بود:
      let instance = new MyClass()
      let instanceToCast = instance :> Interface3
      instanceToCast.Method3 10
      کلاس‌های Abstract
      #F از کلاس‌های abstract هم پشتیبانی می‌کند. اگر با کلاس‌های abstract در #C آشنایی ندارید می‌تونید مطالب مورد نظر رو در  (^ ) و (^ ) مطالعه کنید. به صورت خلاصه کلاس‌های abstract به عنوان کلاس‌های پایه در برنامه نویسی شی گرا استفاده می‌شوند. این کلاس‌ها دارای خواص و متد‌های پیاده سازی شده و نشده هستند. خواص و متد هایی که در کلاس پایه abstract پیاده سازی نشده اند باید توسط کلاس هایی که از این کلاس پایه ارث می‌برند حتما پیاده سازی شوند.
      ساختار کلی تعریف کلاس‌های abstract:
      [<AbstractClass>]
      type [ accessibility-modifier ] abstract-class-name =
          [ inherit base-class-or-interface-name ]
          [ abstract-member-declarations-and-member-definitions ]
      
          abstract member member-name : type-signature
      در #F برای این که مشخص کنیم که یک کلاس abstract است حتما باید [<AbstractClass>] در بالای کلاس تعریف شود.
      [<AbstractClass>]
      type Shape(x0 : float, y0 : float) =
          let mutable x, y = x0, y0
          let mutable rotAngle = 0.0
      
          abstract Area : float with get
          abstract Perimeter : float  with get
          abstract Name : string with get
      کلاس بالا تعریفی از کلاس abstract است که سه خصوصیت abstract دارد (برای تعیین خصوصیت‌ها و متد هایی که در کلاس پایه پیاده سازی نمی‌شوند از کلمه کلیدی abstract در هنگام تعریف آن‌ها استفاده می‌کنیم). حال دو کلاس ایجاد می‌کنیم که این کلاس پایه را پیاده سازی کنند.

      #1 کلاس اول
      type Square(x, y,SideLength) =
          inherit Shape(x, y)
        override this.Area = this.SideLength * this.SideLength override this.Perimeter = this.SideLength * 4. override this.Name = "Square"
      #2 کلاس دوم
      type Circle(x, y, radius) =
          inherit Shape(x, y)
       let PI = 3.141592654 member this.Radius = radius override this.Area = PI * this.Radius * this.Radius override this.Perimeter = 2. * PI * this.Radius
      Structures
      structure‌ها در #F دقیقا معال struct در #C هستند. توضیحات بیشتر درباره struct در #C (^ ) و (^ )). اما به طور خلاصه باید ذکر کنم که strucure‌ها تقریبا دارای مفهوم کلاس هستند با اندکی تفاوت که شامل موارد زیر است:
      • structure‌ها از نوع مقداری هستند و این بدین معنی است مستقیما درون پشته ذخیره می‌شوند.
      • ارجاع به structure‌ها از نوع ارجاع با مقدار است بر خلاف کلاس‌ها که از نوع ارجاع به منبع هستند.(^ )
      • structure‌ها دارای خواص ارث بری نیستند.
      • عموما از structure برای ذخیره مجموعه ای از داده‌ها با حجم و اندازه کم استفاده می‌شود.

      ساختار کلی تعریف structure

      [ attributes ]
      type [accessibility-modifier] type-name =
         struct
            type-definition-elements
         end
      
      //یا به صورت زیر
      
      [ attributes ]
      [<StructAttribute>]
      type [accessibility-modifier] type-name =
         type-definition-elements
      یک نکته مهم هنگام کار با struct‌ها در #F این است که امکان استفاده از let و Binding در struct‌ها وجود ندارد. به جای آن باید از val استفاده کنید.
      type Point3D =
         struct 
            val x: float
            val y: float
            val z: float
         end
      تفاوت اصلی بین val و let در این است که هنگام تعریف شناسه با val امکان مقدار دهی اولیه به شناسه وجود ندارد. در مثال بالا مقادیر برای x و y و z برابر 0.0 است که توسط کامپایلر انجام می‌شود. در ادامه یک struct به همراه سازنده تعریف می‌کنیم:
      type Point2D =
         struct 
            val X: float
            val Y: float
            new(x: float, y: float) = { X = x; Y = y }
         end
      توسط سازنده struct بالا مقادیر اولیه x و y دریافت می‌شود به متغیر‌های متناظر انتساب می‌شود.

        در پایان یک مثال مشترک رو در #C و #F پیاده سازی می‌کنیم:


      مطالب
      دریافت و نمایش فایل‌های PDF در برنامه‌های Blazor WASM
      زمانیکه قرار است با فایل‌های باینری واقع در سمت سرور کار کنیم، اگر اکشن متدهای ارائه دهنده‌ی آن‌ها محافظت شده نباشند، برای نمایش و یا دریافت آن‌ها تنها کافی است از آدرس مستقیم این منابع استفاده کرد و در این حالت نیازی به رعایت هیچ نکته‌ی خاصی نیست. اما اگر اکشن متدی در سمت سرور توسط فیلتر Authorize محافظت شده باشد و روش محافظت نیز مبتنی بر کوکی‌ها نباشد، یعنی این کوکی‌ها در طی درخواست‌های مختلف، به صورت خودکار توسط مرورگر به سمت سرور ارسال نشوند، آنگاه نیاز است با استفاده از HttpClient برنامه‌های Blazor WASM، درخواست دسترسی به منبعی را به همراه برای مثال JSON Web Tokens کاربر به سمت سرور ارسال کرد و سپس فایل باینری نهایی را به صورت آرایه‌ای از بایت‌ها دریافت نمود. در این حالت با توجه به ماهیت Ajax ای این این عملیات، برای نمایش و یا دریافت این فایل‌های محافظت شده در مرورگر، نیاز به دانستن نکات ویژه‌ای است که در این مطلب به آن‌ها خواهیم پرداخت.



      کدهای سمت سرور دریافت فایل PDF

      در اینجا کدهای سمت سرور برنامه، نکته‌ی خاصی را به همراه نداشته و صرفا یک فایل PDF ساده (محتوای باینری) را بازگشت می‌دهد:
      using Microsoft.AspNetCore.Mvc;
      
      namespace BlazorWasmShowBinaryFiles.Server.Controllers
      {
          [ApiController]
          [Route("api/[controller]")]
          public class ReportsController : ControllerBase
          {
              [HttpGet("[action]")]
              public IActionResult GetPdfReport()
              {
                  //TODO: create the `sample.pdf` report file on the server
      
                  return File(virtualPath: "~/app_data/sample.pdf",
                              contentType: "application/pdf",
                              fileDownloadName: "sample.pdf");
              }
          }
      }
      که در نهایت با آدرس api/Reports/GetPdfReport در سمت کلاینت قابل دسترسی خواهد بود.

      یک نکته: استفاده مستقیم از کتابخانه‌های تولید PDF در برنامه‌های سمت کاربر Blazor منطقی نیست؛ چون به ازای هر کاربر، گاهی از اوقات مجبور به ارسال بیش از 8 مگابایت اضافی مختص به فایل‌های dll. آن کتابخانه‌ی تولید PDF خواهیم شد. بنابراین بهتر است تولید PDF را در سمت سرور و در اکشن متدهای Web API انجام داد و سپس فایل نهایی تولیدی را در برنامه‌ی سمت کلاینت، دریافت و یا نمایش داد. به همین جهت در این مثال خروجی نهایی یک چنین عملیات فرضی را توسط یک اکشن متد Web API ارائه داده‌ایم که در بسیاری از موارد حتی می‌تواند توسط فیلتر Authorize نیز محافظت شده باشد.


      ساخت URL برای دسترسی به اطلاعات باینری

      تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی می‌کنند. این متد، شیء URL را از شیء window جاری دریافت می‌کند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید می‌کند. حاصل آن، یک URL ویژه‌است مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
      در برنامه‌های Blazor نیاز است اینکار را توسط JS Interop آن انجام داد؛ از این جهت که API تولید یک Blob URL، صرفا توسط کدهای جاوا اسکریپتی قابل دسترسی است. به همین جهت فایل جدید Client\wwwroot\site.js را با محتوای زیر ایجاد کرده و همچنین مدخل آن‌را در به انتهای فایل Client\wwwroot\index.html، پیش از بسته شدن تگ body، اضافه می‌کنیم:
      window.JsBinaryFilesUtils = {
        createBlobUrl: function (byteArray, contentType) {
          // The byte array in .NET is encoded to base64 string when it passes to JavaScript.
          const numArray = atob(byteArray)
            .split("")
            .map((c) => c.charCodeAt(0));
          const uint8Array = new Uint8Array(numArray);
          const blob = new Blob([uint8Array], { type: contentType });
          return URL.createObjectURL(blob);
        },
        downloadFromUrl: function (fileName, url) {
          const anchor = document.createElement("a");
          anchor.style.display = "none";
          anchor.href = url;
          anchor.download = fileName;
          document.body.appendChild(anchor);
          anchor.click();
          document.body.removeChild(anchor);
        },
        downloadBlazorByteArray: function (fileName, byteArray, contentType) {
          const blobUrl = this.createBlobUrl(byteArray, contentType);
          this.downloadFromUrl(fileName, blobUrl);
          URL.revokeObjectURL(blobUrl);
        },
        printFromUrl: function (url) {
          const iframe = document.createElement("iframe");
          iframe.style.display = "none";
          iframe.src = url;
          document.body.appendChild(iframe);
          if (iframe.contentWindow) {
            iframe.contentWindow.print();
          }
        },
        printBlazorByteArray: function (byteArray, contentType) {
          const blobUrl = this.createBlobUrl(byteArray, contentType);
          this.printFromUrl(blobUrl);
          URL.revokeObjectURL(blobUrl);
        },
        showUrlInNewTab: function (url) {
          window.open(url);
        },
        showBlazorByteArrayInNewTab: function (byteArray, contentType) {
          const blobUrl = this.createBlobUrl(byteArray, contentType);
          this.showUrlInNewTab(blobUrl);
          URL.revokeObjectURL(blobUrl);
        },
      };
      توضیحات:
      - زمانیکه در برنامه‌های Blazor با استفاده از متد ()HttpClient.GetByteArrayAsync آرایه‌ای از بایت‌های یک فایل باینری را دریافت می‌کنیم، ارسال آن به کدهای جاوااسکریپتی به صورت یک رشته‌ی base64 شده صورت می‌گیرد (JS Interop اینکار را به صورت خودکار انجام می‌دهد). به همین جهت در متد createBlobUrl روش تبدیل این رشته‌ی base64 دریافتی را به آرایه‌ای از بایت‌ها، سپس به یک Blob و در آخر به یک Blob URL، مشاهده می‌کنید.  این Blob Url اکنون آدرس موقتی دسترسی به آرایه‌ای از بایت‌های دریافتی توسط مرورگر است. به همین جهت می‌توان از آن به عنوان src بسیاری از اشیاء HTML استفاده کرد.
      - متد downloadFromUrl، کار دریافت یک Url و سپس دانلود خودکار آن‌را انجام می‌دهد. اگر به یک anchor استاندارد HTML، ویژگی download را نیز اضافه کنیم، با کلیک بر روی آن، بجای گشوده شدن این Url، مرورگر آن‌را دریافت خواهد کرد. متد downloadFromUrl کار ساخت لینک و تنظیم ویژگی‌های آن و سپس کلیک بر روی آن‌را به صورت خودکار انجام می‌دهد. از متد downloadFromUrl زمانی استفاده کنید که منبع مدنظر، محافظت شده نباشد و Url آن به سادگی در مرورگر قابل گشودن باشد.
      - متد downloadBlazorByteArray همان کار متد downloadFromUrl را انجام می‌دهد؛ با این تفاوت که Url مورد نیاز توسط متد downloadFromUrl را از طریق یک Blob Url تامین می‌کند.
      - متد printFromUrl که جهت دسترسی به منابع محافظت نشده طراحی شده‌است، Url یک منبع را دریافت کرده، آن‌را به یک iframe اضافه می‌کند و سپس متد print را بر روی این iframe به صورت خودکار فراخوانی خواهد کرد تا سبب ظاهر شدن صفحه‌ی پیش‌نمایش چاپ شود.
      - printBlazorByteArray همان کار متد printFromUrl را انجام می‌دهد؛  با این تفاوت که Url مورد نیاز توسط متد printFromUrl را از طریق یک Blob Url تامین می‌کند.


      تهیه‌ی متدهایی الحاقی جهت کار ساده‌تر با JsBinaryFilesUtils

      پس از تهیه‌ی JsBinaryFilesUtils فوق، می‌توان با استفاده از کلاس زیر که به همراه متدهایی الحاقی جهت دسترسی به امکانات آن است، کار با متدهای دریافت، نمایش و چاپ فایل‌های باینری را ساده‌تر کرد و از تکرار کدها جلوگیری نمود:
      using System.Threading.Tasks;
      using Microsoft.JSInterop;
      
      namespace BlazorWasmShowBinaryFiles.Client.Utils
      {
          public static class JsBinaryFilesUtils
          {
              public static ValueTask<string> CreateBlobUrlAsync(
                  this IJSRuntime JSRuntime,
                  byte[] byteArray, string contentType)
              {
                  return JSRuntime.InvokeAsync<string>("JsBinaryFilesUtils.createBlobUrl", byteArray, contentType);
              }
      
              public static ValueTask DownloadFromUrlAsync(this IJSRuntime JSRuntime, string fileName, string url)
              {
                  return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.downloadFromUrl", fileName, url);
              }
      
              public static ValueTask DownloadBlazorByteArrayAsync(
                  this IJSRuntime JSRuntime,
                  string fileName, byte[] byteArray, string contentType)
              {
                  return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.downloadBlazorByteArray",
                          fileName, byteArray, contentType);
              }
      
              public static ValueTask PrintFromUrlAsync(this IJSRuntime JSRuntime, string url)
              {
                  return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.printFromUrl", url);
              }
      
              public static ValueTask PrintBlazorByteArrayAsync(
                  this IJSRuntime JSRuntime,
                  byte[] byteArray, string contentType)
              {
                  return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.printBlazorByteArray", byteArray, contentType);
              }
      
              public static ValueTask ShowUrlInNewTabAsync(this IJSRuntime JSRuntime, string url)
              {
                  return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.showUrlInNewTab", url);
              }
      
              public static ValueTask ShowBlazorByteArrayInNewTabAsync(
                  this IJSRuntime JSRuntime,
                  byte[] byteArray, string contentType)
              {
                  return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.showBlazorByteArrayInNewTab", byteArray, contentType);
              }
          }
      }


      اصلاح Content Security Policy سمت سرور جهت ارائه‌ی محتوای blob

      پس از دریافت فایل PDF به صورت یک blob، با استفاده از متد URL.createObjectURL می‌توان آدرس موقت محلی را برای دسترسی به آن تولید کرد و یک چنین آدرس‌هایی به صورت blob:http تولید می‌شوند. در این حالت در Content Security Policy سمت سرور، نیاز است امکان دسترسی به تصاویر و همچنین اشیاء از نوع blob را نیز آزاد معرفی کنید:
      img-src 'self' data: blob:
      default-src 'self' blob:
      object-src 'self' blob:
      در غیراینصورت مرورگر، نمایش یک چنین تصاویر و یا اشیایی را سد خواهد کرد.


      نمایش فایل PDF دریافتی از سرور، به همراه دکمه‌های دریافت، چاپ و نمایش آن در صفحه‌ی جاری

      در ادامه کدهای کامل مرتبط با تصویری را که در ابتدای بحث مشاهده کردید، ملاحظه می‌کنید:
      @page "/"
      
      @using BlazorWasmShowBinaryFiles.Client.Utils
      
      @inject IJSRuntime JSRuntime
      @inject HttpClient HttpClient
      
      <h1>Display PDF Files</h1>
      <button class="btn btn-info" @onclick="handlePrintPdf">Print PDF</button>
      <button class="btn btn-primary ml-2" @onclick="handleShowPdf">Show PDF</button>
      <button class="btn btn-success ml-2" @onclick="handleDownloadPdf">Download PDF</button>
      
      @if(!string.IsNullOrWhiteSpace(PdfBlobUrl))
      {
          <section class="card mb-5 mt-3">
              <div class="card-header">
                  <h4>using iframe</h4>
              </div>
              <div class="card-body">
                  <iframe title="PDF Report" width="100%" height="600" src="@PdfBlobUrl" type="@PdfContentType"></iframe>
              </div>
          </section>
      
          <section class="card mb-5">
              <div class="card-header">
                  <h4>using object</h4>
              </div>
              <div class="card-body">
                  <object data="@PdfBlobUrl" aria-label="PDF Report" type="@PdfContentType" width="100%" height="100%"></object>
              </div>
          </section>
      
          <section class="card mb-5">
              <div class="card-header">
                  <h4>using embed</h4>
              </div>
              <div class="card-body">
                  <embed aria-label="PDF Report" src="@PdfBlobUrl" type="@PdfContentType" width="100%" height="100%">
              </div>
          </section>
      }
      
      @code
      {
          private const string ReportUrl = "/api/Reports/GetPdfReport";
          private const string PdfContentType = "application/pdf";
      
          private string PdfBlobUrl;
      
          private async Task handlePrintPdf()
          {
              // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization).
              // Otherwise just use the `PrintFromUrlAsync` method.
              var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl);
              await JSRuntime.PrintBlazorByteArrayAsync(byteArray, PdfContentType);
          }
      
          private async Task handleDownloadPdf()
          {
              // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization).
              // Otherwise just use the `DownloadFromUrlAsync` method.
              var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl);
              await JSRuntime.DownloadBlazorByteArrayAsync("report.pdf", byteArray, PdfContentType);
          }
      
          private async Task handleShowPdf()
          {
              // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization).
              // Otherwise just use the `ReportUrl` as the `src` of the `iframe` directly.
              var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl);
              PdfBlobUrl = await JSRuntime.CreateBlobUrlAsync(byteArray, PdfContentType);
          }
      
          // Tips:
          // 1- How do I enable/disable the built-in pdf viewer of FireFox
          // https://support.mozilla.org/en-US/kb/disable-built-pdf-viewer-and-use-another-viewer
          // 2- How to configure browsers to use the Adobe PDF plug-in to open PDF files
          // https://helpx.adobe.com/acrobat/kb/pdf-browser-plugin-configuration.html
          // https://helpx.adobe.com/acrobat/using/display-pdf-in-browser.html
          // 3- Microsoft Edge is gaining new PDF reader features within the Windows 10 Fall Creator’s Update (version 1709).
      }
      توضیحات:
      - پس از تهیه‌ی JsBinaryFilesUtils و متدهای الحاقی متناظر با آن، اکنون تنها کافی است با  استفاده از متد ()HttpClient.GetByteArrayAsync، فایل PDF ارائه شده‌ی توسط یک اکشن متد را به صورت آرایه‌ای از بایت‌ها دریافت و سپس به متدهای چاپ (PrintBlazorByteArrayAsync) و دریافت (DownloadBlazorByteArrayAsync) آن ارسال کنیم.
      - در مورد نمایش آرایه‌ای از بایت‌های دریافتی، وضعیت کمی متفاوت است. ابتدا باید توسط متد CreateBlobUrlAsync، آدرس موقتی این آرایه را در مرورگر تولید کرد و سپس این آدرس را برای مثال به src یک iframe انتساب دهیم تا PDF را با استفاده از امکانات توکار مرورگر، نمایش دهد.


      کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorWasmShowBinaryFiles.zip
      مطالب
      امکان تعریف ساده‌تر کلاس‌های Immutable در C# 9.0 با معرفی نوع جدید record
      در مطلب معرفی خواص init-only، با روش معرفی خواص immutable آشنا شدیم. نوع جدیدی که به C# 9.0 به نام record اضافه شده‌است، قسمتی از آن بر اساس همان خواص init-only کار می‌کند. به همین جهت مطالعه‌ی آن مطلب، پیش از ادامه‌ی بحث جاری، ضروری است.


      چرا در C# 9.0 تا این اندازه بر روی سادگی ایجاد اشیاء Immutable تمرکز شده‌است؟

      به شیءای Immutable گفته می‌شود که پس از وهله سازی ابتدایی آن، وضعیت آن دیگر قابل تغییر نباشد. همچنین به کلاسی Immutable گفته می‌شود که تمام وهله‌های ساخته شده‌ی از آن نیز Immutable باشند. نمونه‌ی یک چنین شیءای را از نگارش 1 دات نت در حال استفاده هستیم: رشته‌ها. رشته‌ها در دات نت غیرقابل تغییر هستند و هرگونه تغییری بر روی آن‌ها، سبب ایجاد یک رشته‌ی جدید (یک شیء جدید) می‌شود. نوع جدید record نیز به همین صورت عمل می‌کند.

      مزایای وجود Immutability:

      - اشیاء Immutable یا غیرقابل تغییر، thread-safe هستند که در نتیجه، برنامه نویسی همزمان و موازی را بسیار ساده می‌کنند؛ چون چندین thread می‌توانند با شیءای کار کنند که دسترسی به آن، تنها read-only است.
      - اشیاء Immutable از اثرات جانبی، مانند تغییرات آن‌ها در متدهای مختلف در امان هستند. می‌توانید آن‌ها را به هر متدی ارسال کنید و مطمئن باشید که پس از پایان کار، این شیء تغییری نکرده‌است.
      - کار با اشیاء Immutable، امکان بهینه سازی حافظه را میسر می‌کنند. برای مثال NET runtime.، هش رشته‌های تعریف شده‌ی در برنامه را در پشت صحنه نگهداری می‌کند تا مطمئن شود که تخصیص حافظه‌ی اضافی، برای رشته‌های تکراری صورت نمی‌گیرد. نمونه‌ی دیگر آن نمایش حرف "a" در یک ادیتور یا نمایشگر است. زمانیکه یک شیء Immutable حاوی اطلاعات حرف "a"، ایجاد شود، به سادگی می‌توان این تک وهله را جهت نمایش هزاران حرف "a" مورد استفاده‌ی مجدد قرار داد، بدون اینکه نگران مصرف حافظه‌ی بالای برنامه باشیم.
      - کار با اشیاء Immutable به باگ‌های کمتری ختم می‌شود؛ چون همواره امکان تغییر حالت درونی یک شیء، توسط قسمت‌های مختلف برنامه، می‌تواند به باگ‌های ناخواسته‌ای منتهی شوند.
      - Hash list‌ها که در جهت بهبود کارآیی برنامه‌ها بسیار مورد استفاده قرار می‌گیرند، بر اساس کلیدهایی Immutable قابل تشکیل هستند.


      روش تعریف نوع‌های جدید record

      کلاس ساده‌ی زیر را در نظر بگیرید:
      public class User
      {
         public string Name { set; get; }
      }
      برای تبدیل آن به یک نوع جدید record فقط کافی است واژه‌ی کلیدی class آن‌را با record جایگزین کنیم (به آن nominal record هم می‌گویند):
      public record User
      {
         public string Name { set; get; }
      }
      نحوه‌ی کار با آن و وهله سازی آن نیز دقیقا مانند کلاس‌ها است:
      var user = new User();
      user.Name = "User 1";
      و ... در اینجا امکان انتساب مقداری به خاصیت Name وجود دارد؛ یعنی این خاصیت به صورت پیش‌فرض Immutable نیست.

      روش تعریف دومی نیز در اینجا میسر است (به آن positional record هم می‌گویند):
      public record User(string Name);
      با این‌کار، به صورت خودکار یک record جدید تشکیل می‌شود که به همراه خاصیت Name است؛ چیزی شبیه به record قبلی که تعریف کردیم (به همین جهت نیاز است نام آن‌را شروع شده‌ی با حروف بزرگ درنظر بگیریم). با این تفاوت که این record، اینبار دارای سازنده است و همچنین خاصیت Name آن از نوع init-only است. در این حالت است که کل record به صورت immutable معرفی می‌شود؛ وگرنه روش تعریف یک خاصیت معمولی که از نوع init-only نیست (مانند مثال اول)، سبب بروز Immutability نخواهد شد.

      برای کار با رکورد دومی که تعریف کردیم باید سازند‌ه‌ی این record را مقدار دهی کرد:
      var user = new User("User 1");
      // Error: Init-only property or indexer 'User.Name' can only be assigned
      // in an object initializer, or on 'this' or 'base' in an instance constructor
      // or an 'init' accessor. [CS9Features]csharp(CS8852)
      user.Name = "User 1";
      و همانطور که ملاحظه می‌کنید، چون خاصیت Name از نوع init-only است و در سازنده‌ی record تعریف شده مقدار دهی شده‌است، دیگر نمی‌توان آن‌را مقدار دهی مجدد کرد. همچنین در اینجا امکان استفاده‌ی از object initializers مانند new User { Name = "User 1" } نیز وجود ندارد؛ چون به همراه یک سازنده‌ی به صورت خودکار تولید شده‌است که خاصیتی init-only را مقدار دهی کرده‌است.


      نوع جدید record چه اطلاعاتی را به صورت خودکار تولید می‌کند؟

      روش دوم تعریف recordها اگر در نظر بگیریم:
      public record User(string Name);
      و در این حالت برنامه را کامپایل کنیم، به کدهای زیر که حاصل از دی‌کامپایل است، می‌رسیم:
      using System;
      using System.Collections.Generic;
      using System.Runtime.CompilerServices;
      using System.Text;
      using CS9Features;
      
      public class User : IEquatable<User>
      {
       protected virtual Type EqualityContract
       {
        [System.Runtime.CompilerServices.NullableContext(1)]
        [CompilerGenerated]
        get
        {
         return typeof(User);
        }
       }
      
       public string Name
       {
        get;
        set/*init*/;
       }
      
       public User(string Name)
       {
        this.Name = Name;
        base..ctor();
       }
      
       public override string ToString()
       {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append("User");
        stringBuilder.Append(" { ");
        if (PrintMembers(stringBuilder))
        {
         stringBuilder.Append(" ");
        }
        stringBuilder.Append("}");
        return stringBuilder.ToString();
       }
      
       protected virtual bool PrintMembers(StringBuilder builder)
       {
        builder.Append("Name");
        builder.Append(" = ");
        builder.Append((object?)Name);
        return true;
       }
      
       [System.Runtime.CompilerServices.NullableContext(2)]
       public static bool operator !=(User? r1, User? r2)
       {
        return !(r1 == r2);
       }
      
       [System.Runtime.CompilerServices.NullableContext(2)]
       public static bool operator ==(User? r1, User? r2)
       {
        return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
       }
      
       public override int GetHashCode()
       {
        return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
       }
      
       public override bool Equals(object? obj)
       {
        return Equals(obj as User);
       }
      
       public virtual bool Equals(User? other)
       {
        return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name);
       }
      
       public virtual User <Clone>$()
       {
        return new User(this);
       }
      
       protected User(User original)
       {
        Name = original.Name;
       }
      
       public void Deconstruct(out string Name)
       {
        Name = this.Name;
       }
      }
      این خروجی به صورت خودکار تولید شده‌ی توسط کامپایلر، چنین نکاتی را به همراه دارد:
      - record‌ها هنوز هم در اصل همان class‌های استاندارد #C هستند (یعنی در اصل reference type هستند).
      - این کلاس به همراه یک سازنده و یک خاصیت init-only است (بر اساس تعاریف ما).
      - متد ToString آن بازنویسی شده‌است تا اگر آن‌را بر روی شیء حاصل، فراخوانی کردیم، به صورت خودکار نمایش زیبایی را از محتوای آن ارائه دهد.
      - این کلاس از نوع  <IEquatable<User است که امکان مقایسه‌ی اشیاء record را به سادگی میسر می‌کند. برای این منظور متدهای GetHashCode و Equals آن به صورت خودکار بازنویسی و تکمیل شده‌اند (یعنی مقایسه‌ی آن شبیه به value-type است).
      - این کلاس امکان clone کردن اطلاعات جاری را مهیا می‌کند.
      - همچنین به همراه یک متد Deconstruct هم هست که جهت انتساب خواص تعریف شده‌ی در آن، به یک tuple مفید است.

      بنابراین یک رکورد به همراه قابلیت‌هایی است که سال‌ها در زبان #C وجود داشته‌اند و شاید ما به سادگی حاضر به تشکیل و تکمیل آن‌ها نمی‌شدیم؛ اما اکنون کامپایلر زحمت کدنویسی خودکار آن‌ها را متقبل می‌شود!


      ساخت یک وهله‌ی جدید از یک record با clone کردن آن

      اگر به کدهای حاصل از دی‌کامپایل فوق دقت کنید، یک قسمت جدید clone هم با syntax خاصی در آن ظاهر شده‌است:
      public virtual User <Clone>$()
      {
        return new User(this);
      }
      زمانیکه یک شیء Immutable است، دیگر نمی‌توان مقادیر خواص آن‌را در ادامه تغییر داد. اما اگر نیاز به اینکار وجود داشت، باید چکار کنیم؟ در C# 9.0 برای ایجاد وهله‌ی جدید معادلی از یک record، واژه‌ی کلیدی جدیدی را به نام with، اضافه کرده‌اند. برای نمونه اگر record زیر را در نظر بگیریم که دارای دو خاصیت نام و سن است:
      public record User(string Name, int Age);
      وهله سازی متداول آن به صورت زیر خواهد بود:
      var user1 = new User("User 1", 21);
      اما اگر خواستیم خاصیت سن آن‌را تغییر دهیم، می‌توان با استفاده از واژه‌ی کلیدی with، به صورت زیر عمل کرد:
      var user2 = user1 with { Age = 31 };
      کاری که در اصل در اینجا انجام می‌شود، ابتدا clone کردن شیء user1 است (یعنی دقیقا یک وهله‌ی جدید از user1 را با تمام اطلاعات قبلی آن در اختیار ما قرار می‌دهد که این وهله، ارجاعی را به شیء قبلی ندارد و از آن منقطع است). بنابراین نام user2، دقیقا همان "User 1" است که پیشتر تنظیم کردیم؛ با این تفاوت که اینبار مقدار سن آن متفاوت است. با استفاده از cloning، هنوز شیء user1 که immutable است، دست نخورده باقی مانده‌است و توسط with می‌توان خواص آن‌را تغییر داد و حاصل کار، یک شیء کاملا جدید است که مکان آن در حافظه، با مکان شیء user1 در حافظه، یکی نیست.


      مقایسه‌ی نوع‌های record

      در کدهای حاصل از دی‌کامپایل فوق، قسمت عمده‌ای از آن به تکمیل اینترفیس <IEquatable<User پرداخته شده بود. به همین جهت اکنون دو رکورد با مقادیر خواص یکسانی را ایجاد می‌کنیم:
      var user1 = new User("User 1", 21);
      var user2 = new User("User 1", 21);
      سپس یکبار آن‌ها را از طریق عملگر == و بار دیگر به کمک متد Equals، مقایسه می‌کنیم:
      Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
      Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
      خروجی هر دو حالت، True است:
      user1.Equals(user2) -> True
      user1 == user2 -> True
      این مورد، یکی از مهم‌ترین تفاوت‌های recordها با classها هستند.
      - زمانیکه عملگر == را بر روی شیء user1 و user2 اعمال می‌کنیم، اگر User، از نوع کلاس معمولی باشد، حاصل آن false خواهد بود؛ چون این دو، به یک مکان از حافظه اشاره نمی‌کنند، حتی با اینکه مقادیر خواص هر دو شیء یکی است.
      - اما اگر به قطعه کد دی‌کامپایل شده دقت کنید، در یک رکورد که هر چند در اصل یک کلاس است، حتی عملگر == نیز بازنویسی شده‌است تا در پشت صحنه همان متد Equals را فراخوانی کند و این متد با توجه به پیاده سازی اینترفیس <IEquatable<User، اینبار دقیقا مقادیر خواص رکورد را یک به یک مقایسه کرده و نتیجه‌ی حاصل را باز می‌گرداند:
      public virtual bool Equals(User? other)
      {
         return (object)other != null &&
       EqualityContract == other!.EqualityContract &&
       EqualityComparer<string>.Default.Equals(Name, other!.Name) && 
      EqualityComparer<int>.Default.Equals(Age, other!.Age);
      }
      این متدی است که به صورت خودکار توسط کامپایلر جهت مقایسه‌ی مقادیر خواص رکورد جدید تعریف شده، تشکیل شده‌است. به عبارتی recordها از لحاظ مقایسه، شبیه به value objects عمل می‌کنند؛ هرچند در اصل یک کلاس هستند.

      یک نکته: بازنویسی عملگر == در SDK نگارش rc2 فعلی رخ‌داده‌است و در نگارش‌های قبلی preview، اینگونه نبود.


      امکان ارث‌بری در recordها

      دو رکورد زیر را در نظر بگیرید که اولی به همراه Name است و نمونه‌ی مشتق شده‌ی از آن، خاصیت init-only سن را نیز به همراه دارد:
          public record User
          {
              public string Name { get; init; }
      
              public User(string name)
              {
                  Name = name;
              }
          }
      
          public record UserWithAge : User
          {
              public int Age { get; init; }
      
              public UserWithAge(string name, int age) : base(name)
              {
                  Age = age;
              }
          }
      در اینجا روش دیگر تعریف recordها را ملاحظه می‌کنید که شبیه به کلاس‌ها است و خواص آن init-only هستند. در این حالت اگر مقایسه‌ی زیر را انجام دهیم:
      var user1 = new User("User 1");
      var user2 = new UserWithAge("User 1", 21);
      
      Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
      Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
      به خروجی زیر خواهیم رسید:
      user1.Equals(user2) -> False
      user1 == user2 -> False
      علت آن را هم پیشتر بررسی کردیم. تساوی رکوردها بر اساس مقایسه‌ی مقدار تک تک خواص آن‌ها صورت می‌گیرد و چون user1 به همراه سن نیست، مقایسه‌ی این دو، false را بر می‌گرداند.

      امکان تعریف ارث‌بری رکوردها به صورت زیر نیز وجود دارد و الزاما نیازی به روش تعریف کلاس مانند آن‌ها، مانند مثال فوق نیست:
      public abstract record Food(int Calories);
      public record Milk(int C, double FatPercentage) : Food(C);


      رکوردها متد ToString را بازنویسی می‌کنند

      در مثال قبلی اگر یک ToString را بر روی اشیاء تشکیل شده فراخوانی کنیم:
      Console.WriteLine(user1.ToString());
      Console.WriteLine(user2.ToString());
      به این خروجی‌ها می‌رسیم:
      User { Name = User 1 }
      UserWithAge { Name = User 1, Age = 21 }
      که حاصل بازنویسی خودکار متد ToString در پشت صحنه است.


      امکان استفاده‌ی از Deconstruct در رکوردها

      دو روش برای تعریف رکوردها وجود دارند؛ یکی شبیه به تعریف کلاس‌ها است و دیگری تعریف یک سطری، که positional record نیز نامیده می‌شود:
      public record Person(string Name, int Age);
       فقط در حالت تعریف یک سطری positional record فوق است که خروجی خودکار نهایی تولیدی، به همراه public void Deconstruct نیز خواهد بود:
      public void Deconstruct(out string Name, out int Age)
      {
        Name = this.Name;
        Age = this.Age;
      }
      در این حالت می‌توان از tuples نیز برای کار با آن استفاده کرد:
      var (name, age) = new Person("User 1", 21);
      واژه‌ی «positional» نیز دقیقا به همین قابلیت اشاره می‌کند که بر اساس موقعیت خواص تعریف شده‌ی در رکورد، امکان Deconstruct آن‌ها به متغیرهای یک tuple وجود دارد. حالت تعریف کلاس مانند رکوردها، nominal نام دارد.


      امکان استفاده‌ی از نوع‌های record در ASP.NET Core 5x

      سیستم model binding در ASP.NET Core 5x، از نوع‌های record نیز پشتیبانی می‌کند؛ یک مثال:
       public record Person([Required] string Name, [Range(0, 150)] int Age);
      
       public class PersonController
       {
         public IActionResult Index() => View();
      
         [HttpPost]
         public IActionResult Index(Person person)
         {
          // ...
         }
       }


      پرسش و پاسخ

      آیا نوع‌های record به صورت value type معرفی می‌شوند؟
      پاسخ: خیر. رکوردها در اصل reference type هستند؛ اما از لحاظ مقایسه، شبیه به value types عمل می‌کنند.

      آیا می‌توان در یک کلاس، خاصیتی از نوع رکورد را تعریف کرد؟
      پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

      آیا می‌توان در رکوردها، از struct و یا کلاس‌ها جهت تعریف خواص استفاده کرد؟
      پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

      آیا می‌توان از واژه‌ی کلیدی with با کلاس‌ها و یا structها استفاده کرد؟
      پاسخ: خیر. این واژه‌ی کلیدی در C# 9.0 مختص به رکوردها است.

      آیا رکوردها به صورت پیش‌فرض Immutable هستند؟
      پاسخ: اگر آن‌ها را به صورت positional records تعریف کنید، بله. چون در این حالت خواص تشکیل شده‌ی توسط آن‌ها از نوع init-only هستند. در غیراینصورت، می‌توان خواص غیر init-only را نیز به تعریف رکوردها اضافه کرد.
      مطالب
      کوئری نویسی در EF Core - قسمت سوم - جوین نویسی
      پس از آشنایی با نوشتن یک سری کوئری‌های ساده در EF Core، در این قسمت به نحوه‌ی گزارشگیری از اطلاعات چندین جدول مرتبط به هم توسط Joinها خواهیم پرداخت.

      مثال 1: یافتن زمان‌های شروع رزرو کردن امکانات مختلف، توسط یک کاربر مشخص.

      چگونه می‌توان زمان‌های شروع رزروهای کاربری به نام «David Farrell» را یافت؟


      همانطور که در دیاگرام فوق مشاهده می‌کنید، به ازای هر ID کاربری در جدول کاربران، به دنبال ردیف‌هایی در جدول Bookings هستیم که این ID در آن‌ها درج شده‌است. اما ... در EF-Core برخلاف SQL نویسی معمولی، ما کاری به ذکر قسمت اتصالی ON [Bookings].[MemId] = [Members].[MemId] نداریم. همینقدر که در کوئری نوشته شده به یک سر دیگر رابطه و خاصیت راهبری (navigation property) دیگری اشاره شود، خود EF-Core جوینی را به صورت خودکار تشکیل خواهد داد و شرط یاد شده را نیز برقرار می‌کند.
      در قسمت اول این سری، در حین طراحی موجودیت کاربر، برای تشکیل سر دیگر رابطه‌ی one-to-many آن، به جدول Bookings، خاصیت Member را نیز که بیانگر کلید خارجی به جدول کاربران است، اضافه کردیم:
      namespace EFCorePgExercises.Entities
      {
          public class Booking
          {
             // ...
      
              public int MemId { set; get; }
              public virtual Member Member { set; get; }
      
             // ...
          }
      }
      خاصیت عددی MemId، کلید خارجی است که در بانک اطلاعاتی رابطه‌ای ثبت خواهد شد و خاصیت Member، خاصیت راهبری است که جوین نویسی به جدول کاربران را بدون ذکر صریح جوین میسر می‌کند:
      var startTimes = context.Bookings
                              .Where(booking => booking.Member.FirstName == "David"
                                                  && booking.Member.Surname == "Farrell")
                              .Select(booking => new { booking.StartTime })
                              .ToList();
      در این کوئری همینقدر که در قسمت Where آن booking.Member ذکر شده، جوینی به جدول کاربران را به صورت خودکار تشکیل می‌دهد:




      مثال 2: یافتن زمان‌های شروع به رزرو شدن یک امکان خاص در مجموعه.
      لیست زمان‌های شروع به رزرو شدن زمین(های) تنیس را برای روز 2012-09-21 تولید کنید. خروجی آن باید به همراه ستون‌های StartTime, FacilityName باشد.

      طراحی موجودیت Booking، به همراه یک کلید خارجی به Facility نیز هست:
      namespace EFCorePgExercises.Entities
      {
          public class Booking
          {
             // ...
      
              public int FacId { set; get; }
              public virtual Facility Facility { set; get; }
      
             // ...
          }
      }
      خاصیت عددی FacId، کلید خارجی Facility است که در بانک اطلاعاتی رابطه‌ای ثبت خواهد شد و خاصیت Facility، خاصیت راهبری است که جوین نویسی به جدول Facilities را بدون ذکر صریح جوین میسر می‌کند:
      int[] tennisCourts = { 0, 1 };
      var date1 = new DateTime(2012, 09, 21);
      var date2 = new DateTime(2012, 09, 22);
      var startTimes = context.Bookings
                              .Where(booking => tennisCourts.Contains(booking.Facility.FacId)
                                      && booking.StartTime >= date1
                                      && booking.StartTime < date2)
                              .Select(booking => new { booking.StartTime, booking.Facility.Name })
                              .ToList();
      - زمین‌های تنیس این مجموعه، دارای دو Id مساوی 0 و 1 هستند که در اینجا به صورت صریحی مشخص شده‌اند تا مانند مثال 6 قسمت قبل عمل شود. روش دیگر یافتن آن‌ها می‌تواند مانند مثال 5 قسمت قبل باشد که به صورت «Name.Contains("Tennis")» نوشته شد.
      - در قسمت Where این کوئری چون booking.Facility ذکر شده، سبب ایجاد جوین خودکاری به جدول Facilities خواهد شد.
      - علت استفاده‌ی از دو تاریخ در اینجا برای یافتن اطلاعات تنها یک روز، ثبت زمان، به همراه تاریخ رزرو است. ستون تاریخ شروع، به صورت «2012-09-21 18:00:00.0000000» مقدار دهی شده‌است و نه به صورت «2012-09-21». البته در EF-Core راه دیگری هم برای حل این مساله وجود دارد. هر خاصیت از نوع DateTime، به همراه خاصیت Date نیز هست. برای مثال اگر بجای booking.StartTime نوشته شود booking.StartTime.Date (به خاصیت Date اضافه شده دقت کنید)، کد SQL حاصل، به همراه «CONVERT(date, [b].[StartTime])» خواهد بود که سبب حذف خودکار قسمت زمان این ستون می‌شود.



      مثال 3: تولید لیست کاربرانی که کاربر دیگری را توصیه کرده‌اند.

      چگونه می‌توان لیست کاربرانی را یافت که کاربر دیگری را توصیه کرده‌اند؟ این لیست نباید به همراه ردیف‌های تکراری باشد و همچنین باید بر اساس surname, firstname مرتب شود.

      در اینجا به مفهوم جوین کردن یک جدول با خودش رسیده‌ایم. جدول کاربران، یک جدول خود ارجاع دهنده‌است:
      namespace EFCorePgExercises.Entities
      {
          public class Member
          {
             // ...
      
              public virtual ICollection<Member> Children { get; set; }
              public virtual Member Recommender { set; get; }
              public int? RecommendedBy { set; get; }
      
             // ...
          }
      }
      که در اینجا RecommendedBy، یک کلید خارجی نال پذیر است که به Id همین جدول اشاره می‌کند. دو خاصیت دیگر تعریف شده، مکمل این خاصیت عددی، جهت سهولت کوئری نویسی‌های EF-Core هستند. برای مثال اگر در کوئری Recommender != null ذکر شود، سبب تشکیل جوینی به همین جدول شده و لیست کاربرانی را ارائه می‌دهد که کاربر دیگری را توصیه کرده‌اند:
      var members = context.Members
                              .Where(member => member.Recommender != null)
                              .Select(member => new { member.Recommender.FirstName, member.Recommender.Surname })
                              .Distinct()
                              .OrderBy(member => member.Surname).ThenBy(member => member.FirstName)
                              .ToList();
      وجود Distinct سبب بازگشت ردیف‌هایی غیرتکراری می‌شود (چون دو خاصیت نام و نام خانوادگی انتخاب شده‌اند، ردیف غیرتکراری، ردیفی خواهد بود که هر دوی این ستون‌ها در آن وجود نداشته باشد) و روش مرتب سازی بر اساس دو خاصیت را نیز مشاهده می‌کنید. در اینجا نباید دوبار OrderBy را پشت سر هم ذکر کرد. بار اول OrderBy است و بار دوم ThenBy تعریف می‌شود:



      مثال 4: تولید لیست کاربران به همراه توصیه کننده‌ی آن‌ها.

      چگونه می‌توان لیست کاربران را به همراه توصیه کننده‌ی آن‌ها تولید کرد؟ این لیست باید بر اساس surname, firstname مرتب شود.
      var members = context.Members
                              .Select(member => new
                              {
                                  memFName = member.FirstName,
                                  memSName = member.Surname,
                                  recFName = member.Recommender.FirstName ?? "",
                                  recSName = member.Recommender.Surname ?? ""
                              })
                              .OrderBy(member => member.memSName).ThenBy(member => member.memFName)
                              .ToList();
      در اینجا نیز می‌توان با ذکر member.Recommender سبب تولید یک جوین خودکار شد. همچنین همانطور که در مثال 7 قسمت قبل نیز بررسی کردیم، می‌توان بر روی خواص ذکر شده‌ی در Select، محاسباتی را نیز انجام داد. برای مثال در اینجا بجای درج مقدار null برای کاربرانی که کاربر دیگری را توصیه نکرده‌اند، ترجیح داده‌ایم که یک رشته‌ی خالی بازگشت داده شود که به صورت «COALESCE ([m0].[FirstName], N'')» ترجمه می‌شود:


      همانطور که ملاحظه می‌کنید، نوع جوین خودکار تشکیل شده، Left join است و دیگر مانند جوین‌های مثال‌های ابتدای بحث، inner join نیست. در inner join، جدول سمت راست و چپ بر اساس شرط ON آن‌ها با هم مقایسه شده و ردیف‌های کاملا تطابق یافته‌ای بازگشت داده می‌شوند. کار Left join نیز مشابه است، با این تفاوت که در اینجا ممکن است برای جدول سمت چپ، هیچ ردیف تطابق یافته‌ای در جدول سمت راست وجود نداشته باشد (نوع آن بر اساس نال پذیری خاصیت RecommendedBy تشخیص داده شده‌است)؛ برای مثال یک کاربر ممکن است توسط کاربر دیگری توصیه نشده باشد (و RecommendedBy او نال باشد)، اما علاقمندیم که نام او در لیست نهایی حضور داشته باشد و حذف نشود.


      یک نکته: در SQL Server تفاوتی بین left join و left outer join وجود ندارد و ذکر واژه‌ی کلیدی outer کاملا اختیاری است. جدول موارد مشابهی در SQL Server که به یک معنا هستند، صورت زیر است:
      A LEFT JOIN B            A LEFT OUTER JOIN B
      A RIGHT JOIN B           A RIGHT OUTER JOIN B
      A FULL JOIN B            A FULL OUTER JOIN B
      A INNER JOIN B           A JOIN B


      مثال 5: تولید لیست کاربرانی که از زمین تنیس استفاده کرده‌اند.

      چگونه می‌توان لیست کاربرانی را تولید کرد که از زمین(های) تنیس استفاده کرده‌اند؟ خروجی این گزارش باید به همراه یک ستون جمع نام و نام خانوادگی و ستون نام زمین باشد. این گزارش نباید دارای ردیف‌های تکراری باشد و همچنین باید بر اساس حاصل جمع نام و نام خانوادگی، مرتب شده باشد.

      جدول Bookings به همراه دو کلید خارجی به جداول Facilities و Members است:
      namespace EFCorePgExercises.Entities
      {
          public class Booking
          {
             // ...
      
              public int FacId { set; get; }
              public virtual Facility Facility { set; get; }
      
              public int MemId { set; get; }
              public virtual Member Member { set; get; }
      
             // ...
          }
      }
      بنابراین برای تولید گزارشی که اطلاعات هر دوی این‌ها را به همراه دارد (اطلاعات کاربر و اطلاعات امکاناتی که استفاده کرده)، نیاز است دو جوین به دو جدول یاد شده نوشته شود. برای اینکار نیاز است در کوئری خود به booking.Member و booking.Facility برسیم. به همین جهت از جدول کاربران که دارای خاصیت از نوع ICollection  اشاره کننده‌ی به Bookings کاربران است شروع می‌کنیم:
      namespace EFCorePgExercises.Entities
      {
          public class Member
          {
             // ...
      
              public virtual ICollection<Booking> Bookings { set; get; }
          }
      }
      سپس بر روی این خاصیت مجموعه‌ای، اینبار یک SelectMany را فراخوانی می‌کنیم تا خروجی آن، تک تک رکوردهای booking متناظر باشد. اکنون که به هر رکورد booking کاربران دسترسی یافته‌ایم، می‌توانیم از طریق خواص راهبری booking.Member و booking.Facility هر ردیف، اطلاعات نهایی گزارش را تولید کنیم:
      int[] tennisCourts = { 0, 1 };
      var members = context.Members
                              .SelectMany(x => x.Bookings)
                              .Where(booking => tennisCourts.Contains(booking.Facility.FacId))
                              .Select(booking => new
                              {
                                  Member = booking.Member.FirstName + " " + booking.Member.Surname,
                                  Facility = booking.Facility.Name
                              })
                              .Distinct()
                              .OrderBy(x => x.Member)
                              .ToList();
      ID زمین‌های تنیس مشخص هستند که توسط tennisCourts.Contains به FacId‌های موجود اعمال شده‌اند. همچنین در قسمت Select نیز خاصیت Member آن به جمع دو خاصیت از booking.Member اشاره می‌کند و چون نتیجه‌ی حاصل یک ستون از پیش تعریف شده نیست، نیاز است تا برای آن نام صریحی انتخاب شود.
      پس از آن برای حذف ردیف‌های تکراری حاصل از گزارش، از متد Distinct استفاده شده و OrderBy نیز بر اساس خاصیت جدید Member، قابل تعریف است:



      مثال 6: تولید لیست رزروهای گران قیمت

      لیست رزروهای روز 2012-09-14 را تولید کنید که هزینه‌ی آن‌ها بیشتر از 30 دلار باشد. باید بخاطر داشت که هزینه‌های کاربران با مهمان‌ها متفاوت است و هزینه‌ها بر اساس Slotهای نیم ساعته محاسبه می‌شوند و ID کاربر مهمان همیشه صفر است. خروجی  این گزارش باید به همراه نام کامل کاربر، نام امکانات مورد استفاده و هزینه‌ی نهایی باشد. همچنین باید بر اساس هزینه‌های نهایی به صورت نزولی مرتب شود.
      var date1 = new DateTime(2012, 09, 14);
      var date2 = new DateTime(2012, 09, 15);
      
      var items = context.Members
                              .SelectMany(x => x.Bookings)
                              .Where(booking => booking.StartTime >= date1 && booking.StartTime < date2
                              && (
                                  (((booking.Slots * booking.Facility.GuestCost) > 30) && (booking.MemId == 0)) ||
                                  (((booking.Slots * booking.Facility.MemberCost) > 30) && (booking.MemId != 0))
                              ))
                              .Select(booking => new
                              {
                                  Member = booking.Member.FirstName + " " + booking.Member.Surname,
                                  Facility = booking.Facility.Name,
                                  Cost = booking.MemId == 0 ?
                                              booking.Slots * booking.Facility.GuestCost
                                              : booking.Slots * booking.Facility.MemberCost
                              })
                              .Distinct()
                              .OrderByDescending(x => x.Cost)
                              .ToList();
      در اینجا نیز چون نیاز است خروجی نهایی به همراه نام کاربر و نام امکانات مورد استفاده باشد، همانند مثال قبلی، به حداقل دو جوین نیاز است. به همین جهت از جدول Members به همراه SelectMany بر روی تک تک Bookings آن شروع می‌کنیم.
      سپس بر اساس صفر بودن یا نبودن booking.MemId  (کاربر مهمان بودن یا خیر)، شرط هزینه‌ی بیشتر از 30 دلار اعمال شده‌است.
      در آخر Select گزارش مورد نیاز، به همراه جمع نام و نام خانوادگی، نام امکانات استفاده شده و خاصیت محاسباتی Cost است که بر اساس مهمان بودن یا نبودن کاربر، متفاوت است.
      متد Distinct ردیف‌های تکراری حاصل از این گزارش را حذف می‌کند (محل درج آن مهم است) و متد OrderByDescending، مرتب سازی نزولی بر اساس خاصیت محاسباتی Cost را انجام می‌دهد.



      مثال 7: تولید لیست کاربران به همراه توصیه کننده‌ی آن‌ها، بدون استفاده از جوین.

      در اینجا می‌خواهیم همان مثال 4 را بدون استفاده از جوین بررسی کنیم. بدون استفاده از جوین در اینجا به معنای استفاده از sub-query است (نوشتن یک کوئری داخل کوئری اصلی).
      var members = context.Members
                              .Select(member =>
                              new
                              {
                                  Member = member.FirstName + " " + member.Surname,
                                  Recommender = context.Members
                                      .Where(recommender => recommender.MemId == member.RecommendedBy)
                                      .Select(recommender => recommender.FirstName + " " + recommender.Surname)
                                      .FirstOrDefault() ?? ""
                              })
                              .Distinct()
                              .OrderBy(member => member.Member)
                              .ToList();
      این کوئری به صورت متداولی بر روی جدول Members اعمال شده‌است، با این تفاوت که در حین Select نهایی آن، یکبار دیگر کوئری جدید شروع شده‌ی با context.Members را مشاهده می‌کنید که سبب تولید یک sub-query، زمانیکه ToList نهایی فراخوانی می‌شود، خواهد شد. این sub-query در حقیقت یک outer join را با ذکر recommender.MemId == member.RecommendedBy (بیان صریح روش اتصال ID‌های دو سر رابطه) شبیه سازی می‌کند.



      مثال 8: تولید لیست رزروهای گران قیمت با استفاده از یک sub-query.

      هدف از این مثال، ارائه‌ی روش حل دیگری برای مثال 6، به نحو تمیزتری است. در مثال 6، هزینه‌ی رزرو را دوبار، یکبار در متد Where و یکبار در متد Select محاسبه کردیم. اینبار می‌خواهیم با استفاده از sub-query‌ها این محاسبه را یکبار انجام دهیم.
      var date1 = new DateTime(2012, 09, 14);
      var date2 = new DateTime(2012, 09, 15);
      
      var items = context.Members
                              .SelectMany(x => x.Bookings)
                              .Where(booking => booking.StartTime >= date1 && booking.StartTime < date2)
                              .Select(booking => new
                              {
                                  Member = booking.Member.FirstName + " " + booking.Member.Surname,
                                  Facility = booking.Facility.Name,
                                  Cost = booking.MemId == 0 ?
                                              booking.Slots * booking.Facility.GuestCost
                                              : booking.Slots * booking.Facility.MemberCost
                              })
                              .Where(x => x.Cost > 30)
                              .Distinct()
                              .OrderByDescending(x => x.Cost)
                              .ToList();
      اینبار یک Select نوشته شده که در آن Cost، در ابتدا محاسبه شده و سپس Where دومی ذکر شده که از این Cost استفاده می‌کند.
      هرچند کوئری SQL نهایی تولید شده‌ی توسط EF-Core آن، تفاوتی چندانی با نگارش قبلی ندارد:



      کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.