اشتراک‌ها
مقایسه‌ی Blazor و Angular

A comparison of Blazor and Angular when it comes to modern web development—a review of the pros and cons. Does Blazor stack up? 

مقایسه‌ی Blazor و Angular
مطالب
بررسی اجمالی Redis
نام Redis از Remote Dictionary server گرفته شده‌است. Redis یکی از محبوب‌ترین key-value store‌ها می‌باشد و هم چنین توسط برند‌های بزرگ IT جهان استفاده می‌شود. لازم به ذکر است  Amazon Elastic Cache از Redis پشتیبانی می‌کند. Redis یک دیتابیس No SQL است و بر روی مفهوم زوج  کلید-مقدار (key-value ) کار می‌کند. key-value store امکانی را برای ذخیره داده‌ها که Value  نامیده میشود، در یک Key فراهم می‌کند. شما می‌توانید بعدا این داد‌ه‌ها را دریافت کنید، تنها اگر نام دقیق کلیدی را که برای ذخیره داده استفاده کرده‌اید، بدانید.

What Is In-Memory, Key-Value Store  

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

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

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


Redis Advantages

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

Redis Single Instance Architecture 

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


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


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

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

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

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

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

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


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

Clustering In Redis 

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

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

Redis Client 

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


در قسمت بعد در رابطه با نصب Redis  بر روی سیستم عامل ویندوز و دیتا تایپ‌ها در Redis صحبت خواهیم کرد.
اشتراک‌ها
دوره پیاده سازی minimal API با دات نت 7

.NET 7 minimal API from scratch | FULL COURSE | clean architecture, repository pattern, CQRS MediatR

In this course I want to provide you a project structure and code organization to get you started with real .NET 7 minimal API projects. It's a full course on this topic where I start from creating and explaining the project structure, setting up different layers using EF Core, repository pattern, CQRS and MediatR. The biggest part of the video is however around the .NET 7 minimal API, taking you from the initial setup, explaining route handlers, implementing all CRUD operations and so on. Last but not least, this course walks you through the process of refactoring the .NET 7 minimal API so that it becomes readable, maintainable and scalable. At the end, you'll have a full project structure organized according to modern architectural patterns that you can take as a template for your own projects.

Contents
1. Intro: 00:00
2. Structuring the solution: 01:00
3. Coding the domain layer: 05:25
4. Coding the data access layer: 08:22
5. Creating repositories: 11:17
6. Adding migrations and database update: 22:30
7. CQRS with MediatR: 29:07
8. Route and rout handlers: 52:06
9. Dependency injection: 55:52
10. Implementing GET by ID : 57:40
11. Implementing POST route: 01:00:26
12. Implementing GET all route: 01:03:41
13. Implement PUT and DELETE: 01:04:57
14. Testing with Postman: 01:09:01
15. Is there a problem? 01:12:41
16. Refactoring service registrations: 01:15:49
17. Refactoring route registrations: 01:20:01
18. Automatic registration of endpoints: 01:26:28
19. Introducing route groups:  01:31:43
20. Extract lambdas into instance methods: 01:34:31
21: Model validation with endpoint filters: 01:45:58
22. Global exception handling: 01:55:10
23. Conclusions: 01:59:49 

دوره پیاده سازی minimal API با دات نت 7
مطالب
فارسی کردن گرید تلریک در برنامه های ویندوزی

در پروژه‌های ویندوزی یکی از بیشترین ابزار کاربردی گریدویو  تلریک Telerik GridView میباشد و اینکه تمامی امکانات گرید مانند گروه بندی ، فیلترینگ و ... همه فارسی باشند خیلی برای پروژه خوب است.
منم در یکی از پروژه‌ها نیاز به فارسی کردن این ابزار پرکاربرد ویندوزی داشتم و توانستم این مورد را حل کنم . نحوه فارسی کردن این ابزار به شرح ذیل میباشد:

1- یک پروژه جدید ویندوزی در visual studio  ایجاد میکنیم

2- اضافه کردن یک radGridView به فرم و خاصیت Dock آن را به حالت Fill و خاصیت RightToLeft را Yes قرار میدهیم :

3- حال برای اینکه یک سری اطلاعاتی داخل این گرید نمایش بدهیم یک کلاس در همان فرم درست میکنیم مشابه کد ذیل :
   public List<MyCustomData> GetData() {

            List<MyCustomData> myList = new List<MyCustomData>();
            for (int i = 1; i < 11; i++)
            {
                myList.Add(new MyCustomData() {
                     ID = i,
                     Name = "Name Family " + i.ToString(),
                      Age = 29
                });
            }

            return myList;
        }

       public class MyCustomData
        {
            public int ID { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
            public bool Sex { get; set; }
            
        }

4 - حال برای اینکه این اطلاعات را در گرید نمایش دهیم کد زیر را در بخش Load_Form1 مینویسیم :
 private void Form1_Load(object sender, EventArgs e)
        {
            radGridView1.DataSource = GetData();
        }
5 - در این صورت اگر برنامه را اجرا کنیم بدین صورت در گرید نمایش داده میشود که هیچ کدام از موارد فارسی نیستند:
 
6 - برای اینکه این موارد فارسی شوند نیاز به یک کلاس یا Provider داریم که این عمل ترجمه را انجام دهد که حتی در سایت خود تلریک در بخش مربوطه نیز ارائه شده است. بنده این کلاس را کپی کرده و تمامی ترجمه‌های آنها را نیز نوشتم ( اگر در ترجمه ایرادی بود به بزرگی خودتان بخشیده و تصحیح نمائید . ) که کد آن را در زیر میتوانید در اختیار داشته باشید:
class PersianRadGridLocalizationProvider : RadGridLocalizationProvider
    {
        public override string GetLocalizedString(string id)
        {
            switch (id)
            {
                case RadGridStringId.FilterFunctionBetween: return "بین"; //Between  
                case RadGridStringId.FilterOperatorBetween: return "بین";
                case RadGridStringId.FilterFunctionContains: return "حاوی";
                case RadGridStringId.FilterOperatorContains: return "حاوی";
                case RadGridStringId.FilterFunctionDoesNotContain: return "شامل نشود"; //Does not contain
                case RadGridStringId.FilterOperatorDoesNotContain: return "شامل نشود";
                case RadGridStringId.FilterFunctionEndsWith: return "پایان پذیرد با"; //Ends with 
                case RadGridStringId.FilterOperatorEndsWith: return "پایان پذیرد با";
                case RadGridStringId.FilterFunctionEqualTo: return "برابر با"; //Equals     
                case RadGridStringId.FilterOperatorEqualTo: return "برابر با";
                case RadGridStringId.FilterFunctionGreaterThan: return "بزرگتر از"; //Greater than
                case RadGridStringId.FilterOperatorGreaterThan: return "بزرگتر از";
                case RadGridStringId.FilterFunctionGreaterThanOrEqualTo: return "بزرگتر یا مساوی با"; //Greater than or equal to 
                case RadGridStringId.FilterOperatorGreaterThanOrEqualTo: return "بزرگتر یا مساوی با";
                case RadGridStringId.FilterFunctionIsEmpty: return "خالی باشد"; //Is empty
                case RadGridStringId.FilterOperatorIsEmpty: return "خالی باشد";
                case RadGridStringId.FilterFunctionIsNull: return "تهی باشد"; //Is null
                case RadGridStringId.FilterOperatorIsNull: return "تهی باشد";
                case RadGridStringId.FilterFunctionLessThan: return "کمتر از"; //Less than 
                case RadGridStringId.FilterOperatorLessThan: return "کمتر از";
                case RadGridStringId.FilterFunctionLessThanOrEqualTo: return "کمتر یا مساوی با"; //Less than or equal to
                case RadGridStringId.FilterOperatorLessThanOrEqualTo: return "کمتر یا مساوی با";
                case RadGridStringId.FilterFunctionNoFilter: return "بدون شرط"; //No filter 
                case RadGridStringId.FilterOperatorNoFilter: return "بدون شرط";
                case RadGridStringId.FilterFunctionNotBetween: return "نباشد بین"; //Not between
                case RadGridStringId.FilterOperatorNotBetween: return "نباشد بین"; //Operator
                case RadGridStringId.FilterFunctionNotEqualTo: return "برابر نباشد با"; //Not equal to 
                case RadGridStringId.FilterOperatorNotEqualTo: return "برابر نباشد با";
                case RadGridStringId.FilterFunctionNotIsEmpty: return "خالی نباشد"; //Is not empty          
                case RadGridStringId.FilterFunctionNotIsNull: return "خالی نباشد"; //Is not null         
                case RadGridStringId.FilterFunctionStartsWith: return "شروع شود با"; //Starts with          
                case RadGridStringId.FilterFunctionCustom: return "شرط دلخواه"; //Custom          
                case RadGridStringId.CustomFilterMenuItem: return "شرط دلخواه منو"; //Custom         
                case RadGridStringId.CustomFilterDialogCaption: return "انتخاب شرط دلخواه"; //RadGridView Custom Filter Dialog         
                case RadGridStringId.CustomFilterDialogLabel: return ":نشان دادن سطرهایی که"; //Show rows where:         
                case RadGridStringId.CustomFilterDialogRbAnd: return "و"; //And         
                case RadGridStringId.CustomFilterDialogRbOr: return "یا"; //Or          
                case RadGridStringId.CustomFilterDialogBtnOk: return "تایید"; //OK         
                case RadGridStringId.CustomFilterDialogBtnCancel: return "انصراف"; //Cancel 
                case RadGridStringId.AddNewRowString: return "برای افزودن سطر جدید اینجا کلیک کنید";
                case RadGridStringId.ClearValueMenuItem: return "پاک کردن مقدار سلول";
                case RadGridStringId.DeleteRowMenuItem: return "حذف سطر"; //Delete Row          
                case RadGridStringId.SortAscendingMenuItem: return "مرتب سازی صعودی"; //Sort Ascending         
                case RadGridStringId.SortDescendingMenuItem: return "مرتب سازی نزولی"; //Sort Descending         
                case RadGridStringId.ClearSortingMenuItem: return "حذف مرتب سازی"; //Clear Sorting         
                case RadGridStringId.ConditionalFormattingMenuItem: return "قالب بندی مشروط"; //Conditional Formatting         
                case RadGridStringId.GroupByThisColumnMenuItem: return "گروهبندی بر حسب این ستون"; //Group by this column         
                case RadGridStringId.UngroupThisColumn: return "حذف این ستون از گروهبندی "; //Ungroup this column         
                case RadGridStringId.ColumnChooserMenuItem: return "انتخابگر ستون"; //Column Chooser         
                case RadGridStringId.HideMenuItem: return "مخفی کردن ستون"; //Hide         
                case RadGridStringId.UnpinMenuItem: return "حالت پیش فرض"; //Unpin         
                case RadGridStringId.PinMenuItem: return "حالت ستون"; //Pin       
                case RadGridStringId.PinAtLeftMenuItem: return "چسپیدن به سمت چپ";
                case RadGridStringId.PinAtRightMenuItem: return "چسپیدن به سمت راست";
                case RadGridStringId.PinAtTopMenuItem: return "چسپیدن به بالا";
                case RadGridStringId.PinAtBottomMenuItem: return "چسپیدن به پایین";
                case RadGridStringId.BestFitMenuItem: return "اندازه بهینه ستون"; //Best Fit         
                case RadGridStringId.PasteMenuItem: return "چسپاندن"; //Paste         
                case RadGridStringId.EditMenuItem: return "ویرایش"; //Edit         
                case RadGridStringId.CopyMenuItem: return "کپی"; //Copy         
                case RadGridStringId.ConditionalFormattingCaption: return "قالب بندی مشروط"; //Custom Formatting Condition Editor   
                case RadGridStringId.ConditionalFormattingLblColumn: return "قالب بندی سلولهایی با شرط:"; //Column:         
                case RadGridStringId.ConditionalFormattingLblName: return "نام شرط:"; //Name:          
                case RadGridStringId.ConditionalFormattingLblType: return "مقدار سلول:"; //Type:         
                case RadGridStringId.ConditionalFormattingLblValue1: return "مقدار اول:"; //Value 1:         
                case RadGridStringId.ConditionalFormattingLblValue2: return "مقدار دوم:"; //Value 2:         
                case RadGridStringId.ConditionalFormattingGrpConditions: return "شرایط"; //Conditions         
                case RadGridStringId.ConditionalFormattingGrpProperties: return "مشخصات"; //Properties         
                case RadGridStringId.ConditionalFormattingChkApplyToRow: return "اعمال این شرط به کل سطر"; //Apply to row         
                case RadGridStringId.ConditionalFormattingBtnAdd: return "افزودن شرایط"; //Add         
                case RadGridStringId.ConditionalFormattingBtnRemove: return "حذف شرایط انتخابی"; //Remove         
                case RadGridStringId.ConditionalFormattingBtnOK: return "تایید"; //OK         
                case RadGridStringId.ConditionalFormattingBtnCancel: return "انصراف"; //Cancel         
                case RadGridStringId.ConditionalFormattingBtnApply: return "اعمال قالب بندی"; //Apply         
                case RadGridStringId.ColumnChooserFormCaption: return "انتخاب ستون ها"; //Column Chooser         
                case RadGridStringId.ColumnChooserFormMessage: return "برای حذف یکی از ستونها، آن ستون را به اینجا بکشید";//"Drag a column header from the grid here to remove it from the current view.";
                case RadGridStringId.CompositeFilterFormErrorCaption: return "خطا";
                case RadGridStringId.ConditionalFormattingChooseOne: return "[یکی را انتخاب کنید]";
                case RadGridStringId.ConditionalFormattingContains: return "[حاوی [مقدار اول";
                case RadGridStringId.ConditionalFormattingDoesNotContain: return "حاوی [مقدار اول] نباشد";
                case RadGridStringId.ConditionalFormattingEndsWith: return "با [مقدار اول] پایان یابد";
                case RadGridStringId.ConditionalFormattingEqualsTo: return "[برابر با [مقدار اول";
                case RadGridStringId.ConditionalFormattingIsBetween: return "بین [مقدار اول] و [مقدار دوم] باشد";
                case RadGridStringId.ConditionalFormattingIsGreaterThan: return "[بزرگتر از [مقدار اول";
                case RadGridStringId.ConditionalFormattingIsGreaterThanOrEqual: return "[بزرگتر یا مساوی با [مقدار اول";
                case RadGridStringId.ConditionalFormattingIsLessThan: return "کوچکتر از [مقدار اول]";
                case RadGridStringId.ConditionalFormattingIsLessThanOrEqual: return "کوچکتر یا مساوی با [مقدار اول]";
                case RadGridStringId.ConditionalFormattingIsNotBetween: return "بین [مقدار اول] و [مقدار دوم] نباشد";
                case RadGridStringId.ConditionalFormattingIsNotEqualTo: return "برابر با [مقدار اول] نباشد";
                case RadGridStringId.ConditionalFormattingRuleAppliesOn: return "اعمال شرایط روی:";
                case RadGridStringId.ConditionalFormattingStartsWith: return "با [مقدار اول] شروع می‌شود";
                case RadGridStringId.CustomFilterDialogCheckBoxNot: return "با این شرایط نباشد";
                case RadGridStringId.CustomFilterDialogFalse: return "False";
                case RadGridStringId.CustomFilterDialogTrue: return "True";
                case RadGridStringId.FilterCompositeNotOperator: return "نباشد";
                case RadGridStringId.FilterLogicalOperatorAnd: return "و";
                case RadGridStringId.FilterLogicalOperatorOr: return "یا";
                case RadGridStringId.FilterMenuAvailableFilters: return "فیلتر شده";
                case RadGridStringId.FilterMenuButtonCancel: return "انصراف";
                case RadGridStringId.FilterMenuButtonOK: return "تایید";
                case RadGridStringId.FilterMenuClearFilters: return "پاک کردن فیلتر";
                case RadGridStringId.FilterMenuSearchBoxText: return "جستجو...";
                case RadGridStringId.FilterMenuSelectionAll: return "همه";
                //case RadGridStringId.FilterMenuSelectionAllSearched: return "نتیجه همه جستجو";
                case RadGridStringId.FilterMenuSelectionNotNull: return "خالی نباشد";
                case RadGridStringId.FilterMenuSelectionNull: return "خالی باشد";
                case RadGridStringId.FilterOperatorCustom: return "دلخواه";
                case RadGridStringId.FilterOperatorIsLike: return "مانند";
                case RadGridStringId.FilterOperatorNotIsContainedIn: return "نباشد در";
                case RadGridStringId.FilterOperatorNotIsEmpty: return "خالی نباشد";
                case RadGridStringId.FilterOperatorNotIsLike: return "نباشد شبیه";
                case RadGridStringId.FilterOperatorNotIsNull: return "خالی نباشد";
                case RadGridStringId.FilterOperatorStartsWith: return "شروع شود با";
                case RadGridStringId.GroupingPanelDefaultMessage: return "برای گروهبندی ستونها، ستونی را به اینجا بکشید";
                case RadGridStringId.GroupingPanelHeader: return ":گروهبندی بر حسب";
                case RadGridStringId.NoDataText: return "داده ای برای نمایش وجود ندارد";
                case RadGridStringId.UnpinRowMenuItem: return "حالت پیش فرض";
                default:
                    return base.GetLocalizedString(id);
            }
        }
    }

7 - حال اگر برنامه را اجرا کنید باز موارد انگلیسی گرید تلریک فارسی نمی‌شوند و باید در کلاس Program.cs پروژه این یک خط کد را هم اضافه نمائید.
//using Telerik.WinControls.UI.Localization;

 RadGridLocalizationProvider.CurrentProvider = new PersianRadGridLocalizationProvider();

8 - حال اگر برنامه را اجرا نمایید تمامی موارد را فارسی مشاهده خواهید نمود ( شکل ذیل )

لطفا ما را از نظرات سازنده خود بی نصیب نفرمائید. با تشکر


مطالب
کوئری نویسی در EF Core - قسمت هشتم - کوئری‌های بازگشتی
جدول اعضای این مجموعه، خود ارجاع دهنده طراحی شده‌است:
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 هستند که در قسمت‌های قبل نیز تعدادی کوئری را در این زمینه مشاهده کردید؛ مانند:
- تولید لیست کاربرانی که کاربر دیگری را توصیه کرده‌اند.
- تولید لیست کاربران، به همراه توصیه کننده‌ی آن‌ها.
- تولید لیست کاربران به همراه توصیه کننده‌ی آن‌ها، بدون استفاده از جوین.
- هر کاربر چه تعداد کاربر دیگری را توصیه کرده‌است؟

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


مثال 1: رنجیره‌ی توصیه کنندگان کاربر با ID مساوی 27 را محاسبه کنید.

می‌خواهیم بدانیم چه کسی کاربر 27 را توصیه کرده و همچنین این کاربر نیز توسط چه شخص دیگری توصیه شده و به همین ترتیب تا بالاترین سطح ممکن.

روش معمول انجام این نوع کوئری‌ها استفاده از «WITH Hierachy» است. اما اگر بخواهیم بدون SQL نویسی مستقیم اینکار را انجام دهیم، می‌توان به صورت زیر عمل کرد:
var id = 27;
var entity27WithAllOfItsParents =
                        context.Members
                            .Where(member => member.MemId == id
                                            || member.Children.Any(m => member.MemId == m.RecommendedBy))
                            .ToList() //It's a MUST - get all children from the database
                            .FirstOrDefault(x => x.MemId == id);// then get the root of the tree


این کوئری ابتدا تمام رکوردهای جدول کاربران را لیست می‌کند. سپس خاصیت Recommender هر کدام را تا n سطح مقدار دهی می‌کند (خود EF-Core اینکار را انجام می‌دهد). تمام این اتفاقات تا قسمت ToList آن رخ می‌دهند. پس از آن یک FirstOrDefault سمت کاربر را هم داریم (LINQ to Objects). هدف از آن، بازگشت تنها ریشه‌ی مرتبط با ID=27 است و تمام Recommenderهای متصل به آن. این موارد را در تصویر ذیل بهتر می‌توانید مشاهده کنید:


لیست تمام کاربران وجود دارند. سپس سیزدهمین مورد آن، همان کاربر 27 است که توسط کاربر 20 توصیه شده. کاربر 20 توسط کاربر 5 توصیه شده و کاربر 5 توسط کاربر 1 و پس از آن خاصیت Recommender نال است که به معنای پایان پیمودن این زنجیره‌است.
بنابراین مرحله‌ی بعدی پس از یافتن ریشه‌ی کاربر 27، پیمودن خاصیت‌های Recommender به صورت بازگشتی است؛ کاری شبیه به متد FindParents زیر:
namespace EFCorePgExercises.Exercises.RecursiveQueries
{
    public static class RecursiveUtils
    {
        public static void FindParents(Member member, List<dynamic> actualResult)
        {
            if (member == null || member.Recommender == null)
            {
                return;
            }

            var item = member.Recommender;
            actualResult.Add(new { Recommender = item.MemId, item.FirstName, item.Surname });

            if (item.Recommender != null)
            {
                FindParents(item, actualResult);
            }
        }
    }
}
که به صورت زیر می‌تواند مورد استفاده قرار گیرد:
var actualResult = new List<dynamic>();
RecursiveUtils.FindParents(entity27WithAllOfItsParents, actualResult);


مثال 2: زنجیره‌ی توصیه شده‌های توسط کاربر با ID مساوی 1 را محاسبه کنید.

می‌خواهیم بدانیم کاربر 1، چه کسی را توصیه کرده و این کاربر نیز چه کاربر دیگری را توصیه کرده و به همین ترتیب تا پایین‌ترین سطح ممکن.
var id = 1;
var entity1WithAllOfItsDescendants =
                        context.Members
                            .Include(member => member.Children)
                            .Where(member => member.MemId == id
                                            || member.Children.Any(m => member.MemId == m.RecommendedBy))
                            .ToList() //It's a MUST - get all children from the database
                            .FirstOrDefault(x => x.MemId == id);// then get the root of the tree
این کوئری نیز شبیه به کوئری مثال قبلی است؛ با یک تفاوت. در اینجا Include(member => member.Children) هم ذکر شده‌است. هدف این است که EF-Core، خاصیت Children را تا n سطح ممکن به صورت خودکار مقدار دهی کند و این مورد دقیقا هدف اصلی مثال جاری است.
وجود Include، سبب تولید یک چنین کوئری می‌شود که در آن جدول کاربران با خودش جوین شده‌است:
SELECT   [m].[MemId],
         [m].[Address],
         [m].[FirstName],
         [m].[JoinDate],
         [m].[RecommendedBy],
         [m].[Surname],
         [m].[Telephone],
         [m].[ZipCode],
         [m0].[MemId],
         [m0].[Address],
         [m0].[FirstName],
         [m0].[JoinDate],
         [m0].[RecommendedBy],
         [m0].[Surname],
         [m0].[Telephone],
         [m0].[ZipCode]
FROM     [Members] AS [m]
         LEFT OUTER JOIN
         [Members] AS [m0]
         ON [m].[MemId] = [m0].[RecommendedBy]
WHERE    ([m].[MemId] = 1)
         OR EXISTS (SELECT 1
                    FROM   [Members] AS [m1]
                    WHERE  ([m].[MemId] = [m1].[RecommendedBy])
                           AND ([m].[MemId] = [m1].[RecommendedBy]))
ORDER BY [m].[MemId], [m0].[MemId];
پس از آن باید خاصیت member.Children را تا هر سطح ممکن به صورت بازگشتی پیمود تا به جواب اصلی این مثال رسید:
namespace EFCorePgExercises.Exercises.RecursiveQueries
{
    public static class RecursiveUtils
    {
        public static void FindChildren(Member member, List<dynamic> actualResult)
        {
            if (member == null)
            {
                return;
            }

            foreach (var item in member.Children)
            {
                actualResult.Add(new { item.MemId, item.FirstName, item.Surname });
                if (item.Children != null)
                {
                    FindChildren(item, actualResult);
                }
            }
        }
    }
}
که به صورت زیر می‌تواند مورد استفاده قرار گیرد:
var actualResult = new List<dynamic>();
RecursiveUtils.FindChildren(entity1WithAllOfItsDescendants, actualResult);


کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
نظرات مطالب
React 16x - قسمت 30 - React Hooks - بخش 1 - معرفی useState و useEffect
سوال من در مورد useEffect می‌باشد.با توجه به قطعه کد زیر
import React, { useState, useEffect } from "react";
import "./styles.css";

export default function App() {
  const initData = {
    name: "",
    class: ""
  };
  const condition = true;
  const exampleData = { name: "Alex", class: "4" };
  const [currentStudent, setCurrentStudent] = useState({});
  const [formData, setFormData] = useState({});
  useEffect(() => {
    setCurrentStudent(exampleData); // My example code to setState
    //The result of currentStudent is {name: "Alex", class: "4"}
    setFormData(condition ? exampleData : initData);
    console.log("useEffect =", formData);
  }, []);
  return <>{console.log("ui = ", formData)}</>;
}
کنسولی که در useEffect نوشته شده است مقدار `{}`‌را نشان میدهد ولی در `return` مقدار `{ name: "Alex", class: "4" } `.چرا؟
نظرات مطالب
استفاده از نگارش سوم Google Analytics API در سرویس‌های ویندوز یا برنامه‌های وب
یک نکته‌ی تکمیلی
اگر کتابخانه‌ی Google.Apis.Analytics.v3 را بر روی یک سیستم دات نت 4 اجرا کنید، احتمالا خطای ذیل را دریافت خواهید کرد:
Could not load type 'System.Net.HttpStatusCode' from assembly System.Net
علت اینجا است که دات نت 4 نیاز به وصله‌ی KB2468871 دارد تا بتواند portable libraries را بارگذاری کند.
اشتراک‌ها
مقایسه و تفاوت های Stress Test ، Load Test با Performance Test
  • Performance testing is a testing method used to determine the speed of a computer, network or devices.
  • Load testing simulates real-world load on any application or website.
  • Stress testing determines the stability and robustness of the system
  • Performance testing helps to check the performance of website servers, databases, networks.
  • Load testing is used for the Client/Server, Web-based applications.
  • Stress testing is done unexpected test traffic of your website. 
مقایسه و تفاوت های Stress Test ، Load Test با Performance Test
مطالب
EF Code First #10

حین کار با ORMهای پیشرفته، ویژگی‌های جالب توجهی در اختیار برنامه نویس‌ها قرار می‌گیرد که در زمان استفاده از کلاس‌های متداول SQLHelper از آن‌ها خبری نیست؛ مانند:
الف) Deferred execution
ب) Lazy loading
ج) Eager loading

نحوه بررسی SQL نهایی تولیدی توسط EF

برای توضیح موارد فوق، نیاز به مشاهده خروجی SQL نهایی حاصل از ORM است و همچنین شمارش تعداد بار رفت و برگشت به بانک اطلاعاتی. بهترین ابزاری را که برای این منظور می‌توان پیشنهاد داد، برنامه EF Profiler است. برای دریافت آن می‌توانید به این آدرس مراجعه کنید: (^) و (^)

پس از وارد کردن نام و آدرس ایمیل، یک مجوز یک ماهه آزمایشی، به آدرس ایمیل شما ارسال خواهد شد.
زمانیکه این فایل را در ابتدای اجرای برنامه به آن معرفی می‌کنید، محل ذخیره سازی نهایی آن جهت بازبینی بعدی، مسیر MyUserName\Local Settings\Application Data\EntityFramework Profiler خواهد بود.

استفاده از این برنامه هم بسیار ساده است:
الف) در برنامه خود، ارجاعی را به اسمبلی HibernatingRhinos.Profiler.Appender.dll که در پوشه برنامه EFProf موجود است، اضافه کنید.
ب) در نقطه آغاز برنامه، متد زیر را فراخوانی نمائید:
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();

نقطه آغاز برنامه می‌تواند متد Application_Start برنامه‌های وب، در متد Program.Main برنامه‌های ویندوزی کنسول و WinForms و در سازنده کلاس App برنامه‌های WPF باشد.
ج) برنامه EFProf را اجرا کنید.

مزایای استفاده از این برنامه
1) وابسته به بانک اطلاعاتی مورد استفاده نیست. (برخلاف برای مثال برنامه معروف SQL Server Profiler که فقط به همراه SQL Server ارائه می‌شود)
2) خروجی SQL نمایش داده شده را فرمت کرده و به همراه Syntax highlighting نیز هست.
3) کار این برنامه صرفا به لاگ کردن SQL تولیدی خلاصه نمی‌شود. یک سری از Best practices را نیز به شما گوشزد می‌کند. بنابراین اگر نیاز دارید سیستم خود را بر اساس دیدگاه یک متخصص بررسی کنید (یک Code review ارزشمند)، این ابزار می‌تواند بسیار مفید باشد.
4) می‌تواند کوئری‌های سنگین و سبک را به خوبی تشخیص داده و گزارشات آماری جالبی را به شما ارائه دهد.
5) می‌تواند دقیقا مشخص کند، کوئری را که مشاهده می‌کنید از طریق کدام متد در کدام کلاس صادر شده است و دقیقا از چه سطری.
6) امکان گروه بندی خودکار کوئری‌های صادر شده را بر اساس DbContext مورد استفاده به همراه دارد.
و ...

استفاده از این برنامه حین کار با EF «الزامی» است! (البته نسخه‌های NH و سایر ORMهای دیگر آن نیز موجود است و این مباحث در مورد تمام ORMهای پیشرفته صادق است)
مدام باید بررسی کرد که صفحه جاری چه تعداد کوئری را به بانک اطلاعاتی ارسال کرده و به چه نحوی. همچنین آیا می‌توان با اعمال اصلاحاتی، این وضع را بهبود بخشید. بنابراین عدم استفاده از این برنامه حین کار با ORMs، همانند راه رفتن در خواب است! ممکن است تصور کنید برنامه دارد به خوبی کار می‌کند اما ... در پشت صحنه فقط صفحه جاری برنامه، 100 کوئری را به بانک اطلاعاتی ارسال کرده، در حالیکه شما تنها نیاز به یک کوئری داشته‌اید.


کلاس‌های مدل مثال جاری

کلاس‌های مدل مثال جاری از یک دپارتمان که دارای تعدادی کارمند می‌باشد، تشکیل شده است. ضمنا هر کارمند تنها در یک دپارتمان می‌تواند مشغول به کار باشد و رابطه many-to-many نیست :

using System.Collections.Generic;

namespace EF_Sample06.Models
{
public class Department
{
public int DepartmentId { get; set; }
public string Name { get; set; }

//Creates Employee navigation property for Lazy Loading (1:many)
public virtual ICollection<Employee> Employees { get; set; }
}
}

namespace EF_Sample06.Models
{
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

//Creates Department navigation property for Lazy Loading
public virtual Department Department { get; set; }
}
}

نگاشت دستی این کلاس‌ها هم ضرورتی ندارد، زیرا قراردادهای توکار EF Code first را رعایت کرده و EF در اینجا به سادگی می‌تواند primary key و روابط one-to-many را بر اساس navigation properties تعریف شده، تشخیص دهد.

در اینجا کلاس Context برنامه به شرح زیر است:

using System.Data.Entity;
using EF_Sample06.Models;

namespace EF_Sample06.DataLayer
{
public class Sample06Context : DbContext
{
public DbSet<Department> Departments { set; get; }
public DbSet<Employee> Employees { set; get; }
}
}


و تنظیمات ابتدایی نحوه به روز رسانی و آغاز بانک اطلاعاتی نیز مطابق کدهای زیر می‌باشد:

using System.Collections.Generic;
using System.Data.Entity.Migrations;
using EF_Sample06.Models;

namespace EF_Sample06.DataLayer
{
public class Configuration : DbMigrationsConfiguration<Sample06Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}

protected override void Seed(Sample06Context context)
{
var employee1 = new Employee { FirstName = "f name1", LastName = "l name1" };
var employee2 = new Employee { FirstName = "f name2", LastName = "l name2" };
var employee3 = new Employee { FirstName = "f name3", LastName = "l name3" };
var employee4 = new Employee { FirstName = "f name4", LastName = "l name4" };

var dept1 = new Department { Name = "dept 1", Employees = new List<Employee> { employee1, employee2 } };
var dept2 = new Department { Name = "dept 2", Employees = new List<Employee> { employee3 } };
var dept3 = new Department { Name = "dept 3", Employees = new List<Employee> { employee4 } };

context.Departments.Add(dept1);
context.Departments.Add(dept2);
context.Departments.Add(dept3);
base.Seed(context);
}
}
}

نکته: تهیه خروجی XML از نگاشت‌های خودکار تهیه شده

اگر علاقمند باشید که پشت صحنه نگاشت‌های خودکار EF Code first را در یک فایل XML جهت بررسی بیشتر ذخیره کنید، می‌توان از متد کمکی زیر استفاده کرد:

void ExportMappings(DbContext context, string edmxFile)
{
var settings = new XmlWriterSettings { Indent = true };
using (XmlWriter writer = XmlWriter.Create(edmxFile, settings))
{
System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(context, writer);
}
}

بهتر است پسوند فایل XML تولیدی را edmx قید کنید تا بتوان آن‌را با دوبار کلیک بر روی فایل، در ویژوال استودیو نیز مشاهده کرد:

using (var db = new Sample06Context())
{
ExportMappings(db, "mappings.edmx");
}



الف) بررسی Deferred execution یا بارگذاری به تاخیر افتاده

برای توضیح مفهوم Deferred loading/execution بهترین مثالی را که می‌توان ارائه داد، صفحات جستجوی ترکیبی در برنامه‌ها است. برای مثال یک صفحه جستجو را طراحی کرده‌اید که حاوی دو تکست باکس دریافت FirstName و LastName کاربر است. کنار هر کدام از این تکست باکس‌ها نیز یک چک‌باکس قرار دارد. به عبارتی کاربر می‌تواند جستجویی ترکیبی را در اینجا انجام دهد. نحوه پیاده سازی صحیح این نوع مثال‌ها در EF Code first به چه نحوی است؟

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample06.DataLayer;
using EF_Sample06.Models;

namespace EF_Sample06
{
class Program
{
static IList<Employee> FindEmployees(string fName, string lName, bool byName, bool byLName)
{
using (var db = new Sample06Context())
{
IQueryable<Employee> query = db.Employees.AsQueryable();

if (byLName)
{
query = query.Where(x => x.LastName == lName);
}

if (byName)
{
query = query.Where(x => x.FirstName == fName);
}

return query.ToList();
}
}

static void Main(string[] args)
{
// note: remove this line if you received : create database is not supported by this provider.
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();

Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample06Context, Configuration>());

var list = FindEmployees("f name1", "l name1", true, true);
foreach (var item in list)
{
Console.WriteLine(item.FirstName);
}
}
}
}

نحوه صحیح این نوع پیاده سازی ترکیبی را در متد FindEmployees مشاهده می‌کنید. نکته مهم آن، استفاده از نوع IQueryable و متد AsQueryable است و امکان ترکیب کوئری‌ها با هم.
به نظر شما با فراخوانی متد FindEmployees به نحو زیر که هر دو شرط آن توسط کاربر انتخاب شده است، چه تعداد کوئری به بانک اطلاعاتی ارسال می‌شود؟

var list = FindEmployees("f name1", "l name1", true, true);

شاید پاسخ دهید که سه بار : یکبار در متد db.Employees.AsQueryable و دوبار هم در حین ورود به بدنه شرط‌های یاد شده و اینجا است که کسانی که قبلا با رویه‌های ذخیره شده کار کرده باشند، شروع به فریاد و فغان می‌کنند که ما قبلا این مسایل رو با یک SP در یک رفت و برگشت مدیریت می‌کردیم!
پاسخ صحیح: «فقط یکبار»! آن‌هم تنها در زمان فراخوانی متد ToList و نه قبل از آن.
برای اثبات این مدعا نیاز است به خروجی SQL لاگ شده توسط EF Profiler مراجعه کرد:

SELECT [Extent1].[EmployeeId]              AS [EmployeeId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM [dbo].[Employees] AS [Extent1]
WHERE ([Extent1].[LastName] = 'l name1' /* @p__linq__0 */)
AND ([Extent1].[FirstName] = 'f name1' /* @p__linq__1 */)


IQueryable قلب LINQ است و تنها بیانگر یک عبارت (expression) از رکوردهایی می‌باشد که مد نظر شما است و نه بیشتر. برای مثال زمانیکه یک IQueryable را همانند مثال فوق فیلتر می‌کنید، هنوز چیزی از بانک اطلاعاتی یا منبع داده‌ای دریافت نشده است. هنوز هیچ اتفاقی رخ نداده است و هنوز رفت و برگشتی به منبع داده‌ای صورت نگرفته است. به آن باید به شکل یک expression builder نگاه کرد و نه لیستی از اشیاء فیلتر شده‌ی ما. به این مفهوم، deferred execution (اجرای به تاخیر افتاده) نیز گفته می‌شود.
کوئری LINQ شما تنها زمانی بر روی بانک اطلاعاتی اجرا می‌شود که کاری بر روی آن صورت گیرد مانند فراخوانی متد ToList، فراخوانی متد First یا FirstOrDefault و امثال آن. تا پیش از این فقط به شکل یک عبارت در برنامه وجود دارد و نه بیشتر.
اطلاعات بیشتر: «تفاوت بین IQueryable و IEnumerable در حین کار با ORMs»



ب) بررسی Lazy Loading یا واکشی در صورت نیاز

در مطلب جاری اگر به کلاس‌های مدل برنامه دقت کنید، تعدادی از خواص به صورت virtual تعریف شده‌اند. چرا؟
تعریف یک خاصیت به صورت virtual، پایه و اساس lazy loading است و به کمک آن، تا به اطلاعات شیءایی نیاز نباشد، وهله سازی نخواهد شد. به این ترتیب می‌توان به کارآیی بیشتری در حین کار با ORMs رسید. برای مثال در کلاس‌های فوق، اگر تنها نیاز به دریافت نام یک دپارتمان هست، نباید حین وهله سازی از شیء دپارتمان، شیء لیست کارمندان مرتبط با آن نیز وهله سازی شده و از بانک اطلاعاتی دریافت شوند. به این وهله سازی با تاخیر، lazy loading گفته می‌شود.
Lazy loading پیاده سازی ساده‌ای نداشته و مبتنی است بر بکارگیری AOP frameworks یا کتابخانه‌هایی که امکان تشکیل اشیاء Proxy پویا را در پشت صحنه فراهم می‌کنند. علت virtual تعریف کردن خواص رابط نیز به همین مساله بر می‌گردد، تا این نوع کتابخانه‌ها بتوانند در نحوه تعریف اینگونه خواص virtual در زمان اجرا، در پشت صحنه دخل و تصرف کنند. البته حین استفاده از EF یا انواع و اقسام ORMs دیگر با این نوع پیچیدگی‌ها روبرو نخواهیم شد و تشکیل اشیاء Proxy در پشت صحنه انجام می‌شوند.

یک مثال: قصد داریم اولین دپارتمان ثبت شده در حین آغاز برنامه را یافته و سپس لیست کارمندان آن‌را نمایش دهیم:

using (var db = new Sample06Context())
{
var dept1 = db.Departments.Find(1);
if (dept1 != null)
{
Console.WriteLine(dept1.Name);
foreach (var item in dept1.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}



رفتار یک ORM جهت تعیین اینکه آیا نیاز است برای دریافت اطلاعات بین جداول Join صورت گیرد یا خیر، واکشی حریصانه و غیرحریصانه را مشخص می‌سازد.
در حالت واکشی حریصانه به ORM خواهیم گفت که لطفا جهت دریافت اطلاعات فیلدهای جداول مختلف، از همان ابتدای کار در پشت صحنه، Join های لازم را تدارک ببین. در حالت واکشی غیرحریصانه به ORM خواهیم گفت به هیچ عنوان حق نداری Join ایی را تشکیل دهی. هر زمانی که نیاز به اطلاعات فیلدی از جدولی دیگر بود باید به صورت مستقیم به آن مراجعه کرده و آن مقدار را دریافت کنی.
به صورت خلاصه برنامه نویس در حین کار با ORM های پیشرفته نیازی نیست Join بنویسد. تنها باید ORM را طوری تنظیم کند که آیا اینکار را حتما خودش در پشت صحنه انجام دهد (واکشی حریصانه)، یا اینکه خیر، به هیچ عنوان SQL های تولیدی در پشت صحنه نباید حاوی Join باشند (lazy loading).

در مثال فوق به صورت خودکار دو کوئری به بانک اطلاعاتی ارسال می‌گردد:

SELECT [Limit1].[DepartmentId] AS [DepartmentId],
[Limit1].[Name] AS [Name]
FROM (SELECT TOP (2) [Extent1].[DepartmentId] AS [DepartmentId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Departments] AS [Extent1]
WHERE [Extent1].[DepartmentId] = 1 /* @p0 */) AS [Limit1]


SELECT [Extent1].[EmployeeId] AS [EmployeeId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM [dbo].[Employees] AS [Extent1]
WHERE ([Extent1].[Department_DepartmentId] IS NOT NULL)
AND ([Extent1].[Department_DepartmentId] = 1 /* @EntityKeyValue1 */)

یکبار زمانیکه قرار است اطلاعات دپارتمان‌ یک (db.Departments.Find) دریافت شود. تا این لحظه خبری از جدول Employees نیست. چون lazy loading فعال است و فقط اطلاعاتی را که نیاز داشته‌ایم فراهم کرده است.
زمانیکه برنامه به حلقه می‌رسد، نیاز است اطلاعات dept1.Employees را دریافت کند. در اینجا است که کوئری دوم، به بانک اطلاعاتی صادر خواهد شد (بارگذاری در صورت نیاز).


ج) بررسی Eager Loading یا واکشی حریصانه

حالت lazy loading بسیار جذاب به نظر می‌رسد؛ برای مثال می‌توان خواص حجیم یک جدول را به جدول مرتبط دیگری منتقل کرد. مثلا فیلد‌های متنی طولانی یا اطلاعات باینری فایل‌های ذخیره شده، تصاویر و امثال آن. به این ترتیب تا زمانیکه نیازی به اینگونه اطلاعات نباشد، lazy loading از بارگذاری آن‌ها جلوگیری کرده و سبب افزایش کارآیی برنامه می‌شود.
اما ... همین lazy loading در صورت استفاده نا آگاهانه می‌تواند سرور بانک اطلاعاتی را در یک برنامه چندکاربره از پا درآورد! نیازی هم نیست تا شخصی به سایت شما حمله کند. مهاجم اصلی همان برنامه نویس کم اطلاع است!
اینبار مثال زیر را درنظر بگیرید که بجای دریافت اطلاعات یک شخص، مثلا قصد داریم، اطلاعات کلیه دپارتمان‌ها را توسط یک Grid نمایش دهیم (فرقی نمی‌کند برنامه وب یا ویندوز باشد؛ اصول یکی است):

using (var db = new Sample06Context())
{
foreach (var dept in db.Departments)
{
Console.WriteLine(dept.Name);
foreach (var item in dept.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}
یک نکته: اگر سعی کنیم کد فوق را اجرا کنیم به خطای زیر برخواهیم خورد:

There is already an open DataReader associated with this Command which must be closed first

برای رفع این مشکل نیاز است گزینه MultipleActiveResultSets=True را به کانکشن استرینگ اضافه کرد:

<connectionStrings>
<clear/>
<add
name="Sample06Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true;MultipleActiveResultSets=True;"
providerName="System.Data.SqlClient"
/>
</connectionStrings>

سؤال: به نظر شما در دو حلقه تو در توی فوق چندبار رفت و برگشت به بانک اطلاعاتی صورت می‌گیرد؟ با توجه به اینکه در متد Seed ذکر شده در ابتدای مطلب، تعداد رکوردها مشخص است.
پاسخ: 7 بار!


و اینجا است که عنوان شد استفاده از EF Profiler در حین توسعه برنامه‌های مبتنی بر ORM «الزامی» است! اگر از این نکته اطلاعی نداشتید، بهتر است یکبار تمام صفحات گزارش‌گیری برنامه‌های خود را که حاوی یک Grid هستند، توسط EF Profiler بررسی کنید. اگر در این برنامه پیغام خطای n+1 select را دریافت کردید، یعنی در حال استفاده ناصحیح از امکانات lazy loading می‌باشید.

آیا می‌توان این وضعیت را بهبود بخشید؟ زمانیکه کار ما گزارشگیری از اطلاعات با تعداد رکوردهای بالا است، استفاده ناصحیح از ویژگی Lazy loading می‌تواند به شدت کارآیی بانک اطلاعاتی را پایین بیاورد. برای حل این مساله در زمان‌های قدیم (!) بین جداول join می‌نوشتند؛ الان چطور؟
در EF متدی به نام Include جهت Eager loading اطلاعات موجودیت‌های مرتبط به هم درنظر گرفته شده است که در پشت صحنه همینکار را انجام می‌دهد:

using (var db = new Sample06Context())
{
foreach (var dept in db.Departments.Include(x => x.Employees))
{
Console.WriteLine(dept.Name);
foreach (var item in dept.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}

همانطور که ملاحظه می‌کنید اینبار به کمک متد Include، نسبت به واکشی حریصانه Employees اقدام کرده‌ایم. اکنون اگر برنامه را اجرا کنیم، فقط یک رفت و برگشت به بانک اطلاعاتی انجام خواهد شد و کار Join نویسی به صورت خودکار توسط EF مدیریت می‌گردد:

SELECT [Project1].[DepartmentId]            AS [DepartmentId],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
[Project1].[EmployeeId] AS [EmployeeId],
[Project1].[FirstName] AS [FirstName],
[Project1].[LastName] AS [LastName],
[Project1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM (SELECT [Extent1].[DepartmentId] AS [DepartmentId],
[Extent1].[Name] AS [Name],
[Extent2].[EmployeeId] AS [EmployeeId],
[Extent2].[FirstName] AS [FirstName],
[Extent2].[LastName] AS [LastName],
[Extent2].[Department_DepartmentId] AS [Department_DepartmentId],
CASE
WHEN ([Extent2].[EmployeeId] IS NULL) THEN CAST(NULL AS int)
ELSE 1
END AS [C1]
FROM [dbo].[Departments] AS [Extent1]
LEFT OUTER JOIN [dbo].[Employees] AS [Extent2]
ON [Extent1].[DepartmentId] = [Extent2].[Department_DepartmentId]) AS [Project1]
ORDER BY [Project1].[DepartmentId] ASC,
[Project1].[C1] ASC


متد Include در نگارش‌های اخیر EF پیشرفت کرده است و همانند مثال فوق، امکان کار با lambda expressions را جهت تعریف خواص مورد نظر به صورت strongly typed ارائه می‌دهد. در نگارش‌های قبلی این متد، تنها امکان استفاده از رشته‌ها برای معرفی خواص وجود داشت.
همچنین توسط متد Include امکان eager loading چندین سطح با هم نیز وجود دارد؛ مثلا x.Employees.Kids و همانند آن.


چند نکته در مورد نحوه خاموش کردن Lazy loading

امکان خاموش کردن Lazy loading در تمام کلاس‌های برنامه با تنظیم خاصیت Configuration.LazyLoadingEnabled کلاس Context برنامه به نحو زیر میسر است:

public class Sample06Context : DbContext
{
public Sample06Context()
{
this.Configuration.LazyLoadingEnabled = false;
}

یا اگر تنها در مورد یک کلاس نیاز است این خاموش سازی صورت گیرد، کلمه کلیدی virtual را حذف کنید. برای مثال با نوشتن public ICollection<Employee> Employees بجای public virtual ICollection<Employee> Employees در اولین بار وهله سازی کلاس دپارتمان، لیست کارمندان آن به نال تنظیم می‌شود. البته در این حالت null object pattern را نیز فراموش نکنید (وهله سازی پیش فرض Employees در سازنده کلاس):

public class Department
{
public int DepartmentId { get; set; }
public string Name { get; set; }

public ICollection<Employee> Employees { get; set; }
public Department()
{
Employees = new HashSet<Employee>();
}
}

به این ترتیب به خطای null reference object بر نخواهیم خورد. همچنین وهله سازی، با مقدار دهی لیست دریافتی از بانک اطلاعاتی متفاوت است. در اینجا نیز باید از متد Include استفاده کرد.

بنابراین در صورت خاموش کردن lazy loading، حتما نیاز است از متد Include استفاده شود. اگرlazy loading فعال است، جهت تبدیل آن به eager loading از متد Include استفاده کنید (اما اجباری نیست).
نظرات مطالب
بازنویسی سطح دوم کش برای Entity framework 6
با تشکر.
آیا این کتابخانه با کتابخانه EntityFramework.Extended سازگاری دارد؟
چون قصد  دارم از این دو  کنار هم استفاده کنم. یه کاری شبیه به کار زیر
  public IList<string> GetUserPermissions(int[] roleIds, int userId)
        {
            var permissionsOfRoles = (from p in _permissions
                                      from r in p.ApplicationRoles
                                      where roleIds.Contains(r.Id)
                                      select p.Name).Cacheable().Future();

            var permissionsOfUser = (from p in _permissions
                                     from r in p.AssignedUsers
                                     where userId == r.Id
                                     select p.Name).Cacheable().Future().ToList();
            return permissionsOfUser.Union(permissionsOfRoles).ToList();
        }
ولی با خطای 
The source query must be of type ObjectQuery or DbQuery.
Parameter name: source
[ArgumentException: The source query must be of type ObjectQuery or DbQuery.
Parameter name: source]
   EntityFramework.Extensions.FutureExtensions.Future(IQueryable`1 source) +249
مواجه شدم. که مشخص است برای اعمال متد Future باید مبدا از نوع IQueryable باشد.آیا اعمال متد AsQueryable در روند کار کتابخانه EFSecondLevelCache مشکلی ایجاد نخواهد شد؟