چرا LINQ؟
اجزای سازندهی LINQ
- Elements عناصر
- Sequences توالیها
int[] fibonacci = {0, 1, 1, 2, 3, 5};
int[] fibonacci = { 0, 1, 1, 2, 3, 5 }; int numberOfElements = fibonacci.Count(); Console.WriteLine($"{numberOfElements}"); IEnumerable<int> distinictNumbers = fibonacci.Distinct(); Console.WriteLine("Elements in output sequence:"); foreach (var number in distinictNumbers) { Console.WriteLine(number); }
6 Elements in output sequence: 0 1 2 3 5
مفهوم Deffer Execution ( اجرای به تاخیر افتاده )
int[] fibonacci = { 0, 1, 1, 2, 3, 5 }; // ایجاد پرس و جو IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2); // در این مرحله پرس و جو ایجاد شده ولی هنوز اجرا نشده است // تغییر عنصر اول توالی fibonacci[0] = 99; // حرکت بر روی عناصر توالی باعث اجرای پرس و جو میشود foreach (var number in numbersGreaterThanTwoQuery) { Console.WriteLine(number); }
99 3 5
int[] fibonacci = { 0, 1, 1, 2, 3, 5 }; // ساخت پرس و جو IEnumerable<int> numbersGreaterThanTwoQuery = fibonacci.Where(x => x > 2) .ToArray(); // در این مرحله به خاطر عملگر استفاده شده پرس و جو اجرا میشود // تغییر اولین عنصر توالی fibonacci[0] = 99; // حرکت بر روی نتیجه foreach (var number in numbersGreaterThanTwoQuery) { Console.WriteLine(number); }
3 5
// does not work: services.AddScoped<IGenericRepository<T>,EFRepository<T>>();
نحوهی معرفی سرویسهای جنریک نامحدود (Open Generics و یا Unbound Generics) به سیستم تزریق وابستگیها
اگر بخواهیم یک سرویس جنریک را به سیستم تزریق وابستگیهای برنامههای NET Core. به نحو متداولی معرفی کنیم، نیاز است به ازای تک تک Tهای میسر و تعریف شدهی در برنامه، اینکار صورت گیرد:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IStore<User>, SqlStore<User>>(); services.AddScoped<IStore<Invoice>, SqlStore<Invoice>>(); services.AddScoped<IStore<Payment>, SqlStore<Payment>>(); // ... }
public void ConfigureServices(IServiceCollection services) { services.AddScoped(typeof(IStore<>), typeof(SqlStore<>)); }
محدودیت کار کردن با جنریکهای نامحدود در سیستم تزریق وابستگیها
با تعریف تک سطر فوق، هر چند برنامه بدون مشکل کامپایل میشود، اما اگر در زمان اجرای برنامه، <IStore<T ای را درخواست کنید که میسر نباشد (در خواست هر نوعی در زمان اجرا با جنریکهای باز معرفی شده، میسر است)، یک استثنای زمان اجرا را دریافت میکنید؛ برای مثال اگر نوع T به کلاسها محدود شده باشد و در قسمتی از برنامه، <IStore<int درخواست شود. هرچند این موارد با یکبار آزمایش برنامه، قابل یافت شدن و رفع میباشند.
کتابخانهی کمکی Scrutor نیز از جنریکهای باز پشتیبانی میکند
در قسمت قبل نحوهی اسکن اسمبلیهای برنامه را توسط کتابخانهی کمکی Scrutor بررسی کردیم. این کتابخانه امکان یافتن و فیلتر کلاسها و معرفی آنها را به سیستم تزریق وابستگیها، بر اساس ویژگی جنریکهای باز نیز دارا است:
services.Scan(scan => scan .FromAssemblyOf<CombinedService>() .AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types .AsMatchingInterface())
ساده سازی «مثال 2: وهله سازی در صورت نیاز وابستگیهای یک سرویس به کمک Lazy loading» قسمت ششم با جنریکهای نامحدود
در قسمت ششم نحوهی تعریف پیشنیازهای وهله سازی به تاخیر افتاده را با استفاده از کلاس Lazy بررسی کردیم:
services.AddTransient<IOrderHandler, OrderHandlerLazy>(); services.AddTransient<IAccounting, Accounting>() .AddTransient(serviceProvider => new Lazy<IAccounting>(() => serviceProvider.GetRequiredService<IAccounting>())); services.AddTransient<ISales, Sales>() .AddTransient(serviceProvider => new Lazy<ISales>(() => serviceProvider.GetRequiredService<ISales>()));
- سپس سرویسهایی که قرار است به صورت Lazy نیز واکشی شوند، بار دیگر توسط روش factory registration با وهله سازی new Lazy از نوع سرویس مدنظر و فراهم آوردن پیاده سازی آن با استفاده از serviceProvider.GetRequiredService، مجددا معرفی خواهند شد.
اگر به شرط دوم دقت کنید، <new Lazy<IAccounting و <new Lazy<ISales، دقیقا مانند همان سرویسهای جنریک <IStore<User و <IStore<Invoice تعریف شدهاند. یعنی نیاز است به ازای هر T ممکن در برنامه، یکبار <new Lazy<T را نیز به سیستم تزریق وابستگیها معرفی کرد. بنابراین تمام این تعریفهای اضافی را میتوان با یک سطر جنریک نامحدود زیر جایگزین و خلاصه کرد:
services.AddTransient(typeof(Lazy<>), typeof(LazyFactory<>));
public class LazyFactory<T> : Lazy<T> where T : class { public LazyFactory(IServiceProvider provider) : base(() => provider.GetRequiredService<T>()) { } }
Portable Class Library برای تولید Assembly مدیریت شده که قابیلت استفاده در اکثر تکنولوژیها نظیر (WP7(Windows Phone 7 و WPF و Silverlight و Windows Store App و حتی XBox را داراست استفاده میشود. این نوع پروژهها میزان استفاده مجدد از کدها رو به حداکثر ممکن میرسونه و در عوض تعداد پروژههای مورد نیاز رو به حداقل. مورد اصلی استفاده از اونها برای تولید Multi-Targeted Application هاست. برای مثال در تولید پروژههای تحت Windows که با تکنولوژی WPF پیاده سازی میشوند پروژه ViewModel و Model رو با این تکنولوژی پیاده سازی میکنند تا در آینده اگر نیاز بود قسمتی از پروژه با استفاده از Silverlight یا Windows Store App انجام بشه بتونیم Model , ViewModel نوشته شده رو به این پروژهها Reference بدیم. تا قبل از برای انجام این کار باید این دو بخش رو دوباره برای هر تکنولوژی بازنویسی میکردیم.
روش استفاده
زمانی که یک پروژه از نوع Portable Class Library رو ایجاد میکنیم باید حداقل 2 یا چند تا از Platformهای مورد نظر رو انتخاب کنیم. به صورت زیر :
بعد از انتخاب گزینه Portable Class Library و زدن کلید Enter فرم زیر ظاهر خواهد شد که در این قسمت باید Platformهای مورد نظر رو انتخاب کنیم.
در اینجا من 2 Platform رو انتخاب کردم. یکی Silverlight و Net 4.5. یعنی این Portable Class Library هم میتونه به پروژه Silverlight و هم به Class library .Net 4.5 رفرنس داده بشه.
حتما میپرسید چه طوری؟
در واقع Microsoft برای انجام این کار فقط یک سری از Referenceهای مشترک بین این 2 Platform رو به پروژه اضافه میکنه و همین موضوع باعث شده کار با این پروژهها نیاز به یکم مهارت داشته باشه.
برای مثال اگر قصد استفاده از تکنولوژی Async&Await رو دارید باید یکم به خودتون زحمت بدید و CTP مورد نظر رو نصب کنید.(حالا اگر از Net4 استفاده میکنید که باید از یک روش دیگه استفاده کنید که در یک مقاله جداگانه براتون توضیح خواهم داد).
به جدول زیر دقت کنید.
Xbox 360 | Windows Phone | Silverlight | Windows Store | .NET Framework | Feature |
√ | √ | √ | √ | √ | Core |
√ | √ | √ | √ | LINQ | |
Only 7.5 | √ | √ | √ | IQueryable | |
√ | √ | Only 4.5 | Dynamic keyword | ||
√ | √ | √ | Managed Extensibility Framework (MEF) | ||
√ | √ | √ | √ | Network Class Library (NCL) | |
√ | √ | √ | √ | Serialization | |
√ | √ | √ | √ | Windows Communication Foundation (WCF) | |
√ | √ | √ |
Only 4.5 | Model-View-View Model (MVVM) | |
√ | √ | Only 4.0.3 and 4.5 | Data annotations | ||
√ | √ | √ | √ | Only 4.0.3 and 4.5 | XLINQ |
در جدول بالا به صورت کامل مشخص شده که در هر پلاتفرم چه چیزی Support میشه. برای مثال Data Annotation فقط در Net4.3 , 4.5 قابل استفاده است.
اسمبلیهای زیر در یک Portable Class Library قابل استفاده هستند.
mscorlib.dll
System.dll
System.Core.dll
System.Xml.dll
System.ComponentModel.Composition.dll
System.Net.dll
System.Runtime.Serialization.dll
System.ServiceModel.dll
System.Xml.Serialization.dll
System.Windows.dll - From Silverlight
پیاده سازی یک مثال عملی
در این مثال قصد دارم یک کلاس برای مدیریت تاریخ شمسی درست کنم. مراحل زیر رو دنبال کنید
یک Portable Class Library به نام Common به پروژه اضافه کنید.
یک کلاس به نام PersianDate به برنامه اضافه کرده به صورت زیر
public class PersianDate<TCalendar> where TCalendar : System.Globalization.Calendar { public PersianDate() { this.CurentCalendar = System.Activator.CreateInstance<TCalendar>(); } public TCalendar CurentCalendar { get; private set; } public string GetDate() { return string.Format( "{0}/{1}/{2}", this.CurentCalendar.GetYear( DateTime.Today ).ToString( "####0000" ) , this.CurentCalendar.GetMonth( DateTime.Today ).ToString( "##00" ) , this.CurentCalendar.GetDayOfMonth( DateTime.Today ).ToString( "##00" ) ); } }
حالا یک پروژه از نوع Console Application به برنامه اضافه کنید و یک Reference به پروژه Common بدید و بعد کلاس زیر رو بنویسید.
public class CustomPersianDate { public CustomPersianDate() { } public static Common.PersianDate<System.Globalization.PersianCalendar> PersianCalendar { get { return _persianCalendar ?? ( _persianCalendar = new Common.PersianDate<System.Globalization.PersianCalendar>() ); } } private static Common.PersianDate<System.Globalization.PersianCalendar> _persianCalendar; }
حالا یک پروژه از نوع Silverlight به برنامه اضافه کنید و یک Reference به پروژه Common بدید و بعد کلاس بالا بدون تغییر بنویسید.
نمای کلی پروژه باید به صورت زیر باشد.
بعد پروژه ConsoleApplication رو به عنوان پروژه StartUp انتخاب کنید و فایل Program رو به صورت زیر تغییر بدید.
class Program { static void Main( string[] args ) { Console.WriteLine( "Today Is ?{0}", CustomPersianDate.PersianCalendar.GetDate() ); Console.ReadLine(); } }
خوب نتیجه به صورت زیر خواهد بود:
برای پروژه Silverlight هم نتیجه قطعا به همین صورت است.
همان طور که دید انتخاب نوع Calendar به خود Applicationها واگذار شد و شما میتونید هر نوع تقویم رو به عنوان TCalendar تعیین کنید.
امیدوارم شما هم مثل من به این تکنولوژی دلچسب علاقه پیدا کرده باشید.
اینطور که در این مطلب عنوان شده، ماوسهای قدیمی در اثر مشکلات سخت افزاری، میتوانند بهازای هر کلیک کاربر، دو سیگنال کلیک، ظرف مدت کوتاهی (برای مثال 5 میلی ثانیه) تولید کنند. برنامههای مبتنی بر Blazor، توسط متدهای نامتقارن میتوانند هردوی این سیگنالها را دریافت کرده و بنابراین متد مربوطه در کسری از ثانیه دوبار اجرا خواهد شد.
برای رهایی از این مشکل میتوان از کدی شبیه زیر بهره جست:
<button disabled="@_busy" Value="do-stuff" /> code{ private bool _busy = false; public async Task Handler() { if(_busy) return; _busy = true; try { // do your thing } finally { _busy = false; } } }
منطق آن ساده است؛ تا زمانی که اجرای متد، پایان نپذیرفتهاست، دکمهی مربوطه غیرفعال میگردد، تا نتوان دوباره روی آن کلیک کرد.
اگر نمیخواهید به ازای هر کامپوننت، این کدهای تکراری را ایجاد کنید، میتوانید کدهای فوق را در قالب یک کامپوننت مانند زیر ایجاد کنید (با نام دلخواه HandleValidSubmitForm.razor):
<EditForm Model="Model" OnValidSubmit="HandleValidSubmit"> @ChildContent?.Invoke(context) <button disabled="@_busy">Submit</button> </EditForm> @code { private bool _busy; [Parameter] public object? Model { get; set; } [Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; } [Parameter] public RenderFragment<EditContext>? ChildContent { get; set; } private async Task HandleValidSubmit(EditContext editContext) { if (_busy) return; _busy = true; try { await OnValidSubmit.InvokeAsync(editContext); } finally { _busy = false; } } }
سپس میتوانید در دیگر کامپوننتها به شکل زیر از آن بهره ببرید.
<HandleValidSubmitForm Model="_customer" OnValidSubmit="HandleValidSubmit"> <InputText @bind-Value="_customer.FirstName" /> <InputText @bind-Value="_customer.LastName" /> </HandleValidSubmitForm> @code { private Customer _customer = new Customer(); private async Task HandleValidSubmit() { // do your thing } public class Customer { public string? FirstName { get; set; } public string? LastName { get; set; } } }
نحوه پیاده سازی متد savechange در کلاس unitofwork
int SaveAllChanges(bool invalidateCacheDependencies = true, Guid? auditUserId = null); Task<int> SaveAllChangesAsync(bool invalidateCacheDependencies = true, Guid? auditUserId = null);
ادامه آموزش MVVM در صورت امکان با MVVM Light
در این آموزشها به MVVM Light هم پرداخته شده، ضمن اینکه زیر ساخت آن و کتابخانههای مشابه نیز بررسی شدن.
به طور مثال در کلاس بالا یک کارمند میتواند فروشنده یا مهندس باشد. پیاده سازی بالا این مورد را با استفاده از دو فیلد نشان دادهاست که در صورت true بودن، مقدار هریک از آنها، نوع کلاس متناظر با آن خواهد بود.
مثلا اگر IsSalesman مقدار true داشته باشد، شیء ما کارمندی با نقش Salesman است و در صورتی که IsEngineer مقدار true داشته باشد ، شیء کارمند، نقش مهندس دارد. بماند که حالتهای دیگری نیز برای مقادیر این فیلدها وجود دارند!
تا اینجا مشکل خاصی وجود ندارد؛ بجز کمی ناخوانا شدن کد و کثیف کاری. اما مشکل اساسی زمانی پیش میآید که کلاس Employee نیاز به پیاده سازی رفتارهایی مختص به هر یک از این انواع را پیدا کند. به طور مثال شرایط کاری و امکانات مورد نیاز یک مهندس، با فروشنده متفاوت است و مثلا هنگام ثبت حکم یک فرد، نیاز به بررسی شرایط متفاوتی نسبت به نوع یک کارمند، وجود داشته باشد.
اگر با همین فرمان کدنویسی را ادامه دهیم احتمالا با کلاسی روبرو خواهیم شد که پر است از گذارههای if else ، switch و یا مواردی از این دست؛ که ابدا شرایط دلپذیری برای دوستانی که قصد نگهداری از کد ما را دارند، نیست!
زمانیکه با چنین موردی مواجه میشوید. ابتدا به ارتباط معنایی نوعهای به کار رفتهی در کلاس توجه کنید. در صورتیکه میتوان این انواع را به صورت polymorphic طراحی مجدد کرد، حتما این کار را انجام دهید. البته به ندرت مشاهده کردهام چنین چیزی امکان نداشته باشد. در صورتیکه ارتباط معنایی خاصی وجود نداشته باشد، میتوانید با استفاده از دیگر بازسازیهای کد، کلاسها را جدا کرده و دو کلاس مجزا را ایجاد نمایید. یا با استفاده از دیگر بازسازیهای کد که در آینده خواهم گفت، به طریق دیگری کد را تغییر دهید که خدا را هم خوش بیاید. به طور مثال طراحی زیر میتواند نتیجه بازسازی کلاس بالا با روش ذکر شده باشد.
مراحل انجام این بازسازی کد
- اگر type کد درونی کلاس از طریق سازنده به کلاس ارسال شده است، این سازنده را با یک متد سازنده (Factory method) جایگزین کنید.
- به ازای هر مقدار از type کدهای درونی کلاس، یک زیر کلاس جدید بسازید.
- تمامی استفادهها از type کدهای درونی کلاس را بازسازی کرده و به کلاسهای مربوط به خود منتقل کنید (احتمالا تمامی پیاده سازیهایی که if else یا switch ای بر روی مقدار type کدها دارند).
- از کلاس پایه، type کد را حذف کنید.
- در صورت وجود تمامی استفادهها از سازنده، کلاس اولیه را به استفاده از متد سازنده تغییر دهید.
- کد را کامپایل و تست نمایید.
بررسی ویجت Kendo UI File Upload
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(MyModel model, IEnumerable<HttpPostedFileBase> files) { // code return View(); }
[HttpPost] public ActionResult Save(MyModel model, IEnumerable<HttpPostedFileBase> files) { var sModel = new MyViewModel { MainPhoto = model.MainPhoto, Phone = model.Phone, Title = model.Title, WebSiteUrl = model.WebSiteUrl, // .... }; if (files != null) { var images = files.Select(file => new Image { FileName = Helpers.Helper.UploadFile(file) }).ToList(); sModel.Images = images; } _locationService.AddLocation(sModel); _uow.SaveAllChanges(); return RedirectToAction("List"); }