من خیلی به جاوااسکریپت وارد نیستم اما همین کار شما رو برای تقویم گوگل انجام دادم اما دو تا مشکل داره:
1- سال میلادی رو نمی فهمه و باید برای هر سال میلادی بصورت دستی این اسکریپت رو تغییر داد.
2- فقط برای هفته ی جاری، هفته ی بعد و هفته ی قبل درست کار می کنه.
پیشنهاد می کنم یه نگاهی بهش بندازی و اصلاحش کنی. بدرد من یکی که خیلی می خوره.
http://nima.rasouli.org/2008/08/google-calendar-persian.html
معرفی Bit Platform
وب اسمبلی چیست؟
<BlazorMode> ... </BlazorMode> <WebAppDeploymentType> ... </WebAppDeploymentType>
- وجود سیستم Exception handling در سرور و کلاینت (این موضوع به گونه ای بر اساس Best Practiceها پیاده سازی شده که اپلیکیشن را از بروز هر خطایی که بخواهد موجب Crash کردن برنامه شود ایزوله کرده)
- وجود سیستم User Authentication بر اساس JWT که شما در همان ابتدا که از این تمپلیت پروژه جدیدی میسازید صفحات SignIn ، SignUp را خواهید داشت.
- پکیج Bit Blazor UI که بالاتر درمورد آن صحبت کرده ایم از همان ابتدا در TodoTemplate نصب و تنظیم شده تا بتوانید به راحتی صفحات جدید با استفاده از آن بسازید.
- کانفیگ استاندارد Swagger در سمت سرور.
- ارسال ایمیل در روند SignUp.
- وجود خاصیت AutoInject برای سادهسازی تزریق وابستگی ها.
- و بسیاری موراد دیگر که در داکیومنتهای پروژه میتوانید آنهارا ببینید.
- شما میتوانید این پروژه را در گیتهاب مشاهده کنید.
- برای اشکالات یا قابلیت هایی که میخواهید برطرف شود Issue ثبت کنید.
- پروژه را Fork کنید و Star دهید.
- ایشوهایی که وجود دارد را برطرف کنید و Pull Request ارسال کنید.
- برای در جریان بودن از روند توسعه در جلسات برنامه ریزی (Planning Meeting) و گزارشات هفتگی (Standup Meeting ) که همه اینها در Microsoft Teams برگزار میشود شرکت کنید.
namespace Microsoft.Extensions.DependencyInjection { public interface IServiceCollection : ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor> { } }
ServiceProvider و مؤلفههای درونی آن، از یک مجموعه از ServiceDescriptorها برای برنامهی شما بر اساس سرویسهای ثبت شدهی توسط IServiceCollection استفاده میکنند. ServiceDescriptor حاوی اطلاعاتی در مورد سرویسهای ثبت شدهاست. اگر به کد منبع این کلاس برویم، میبینیم پنج Property اصلی دارد که با استفاده از آنها اطلاعات یک سرویس ثبت و نگهداری میشوند. با استفاده از این اطلاعات در هنگام اجرا ، DI Container به واکشی و ساخت نمونههایی از سرویس درخواستی اقدام میکند:
public Type ImplementationType { get; } public object ImplementationInstance { get; } public Func<IServiceProvider, object> ImplementationFactory { get; } public ServiceLifetime Lifetime { get; } public Type ServiceType { get; }
هر کدام از این Property ها کاربرد خاص خود را دارند:
- · ServiceType : نوع سرویسی را که میخواهیم ثبت شود، مشخص میکنیم ( مثلا اینترفیس IMessageService ) .
- · ImplementionType : نوع پیاده سازی سرویس مورد نظرمان را مشخص میکند ( مثلا کلاس MessageService ).
- · LifeTime : طول حیات سرویس را مشخص میکند. DI Container بر اساس این ویژگی، اقدام به ساخت و از بین بردن نمونههایی از سرویس میکند.
- · ImplementionInstance : نمونهی ساخته شدهی از سرویس است.
- · ImplementionFactory : یک Delegate است که چگونگی ساخته شدن یک نمونه از پیاده سازی سرویس را در خود نگه میدارد. این Delegate یک IServiceProvider را به عنوان ورودی دریافت میکند و یک object را بازگشت میدهد.
به صورت عادی، در سناریوهای معمول ثبت سرویسها درون IServiceCollection، نیازی به استفاده از ServiceDescriptor نیست؛ ولی اگر بخواهیم سرویسها را به روشهای پیشرفتهتری ثبت کنیم، مجبوریم که به صورت مستقیم با این کلاس کار کنیم.
می توانیم یک ServiceDesciriptor را به روشهای زیر تعریف کنیم:
var serviceDescriptor1 = new ServiceDescriptor( typeof(IMessageServiceB), typeof(MessageServiceBB), ServiceLifetime.Scoped); var serviceDescriptor2 = ServiceDescriptor.Describe( typeof(IMessageServiceB), typeof(MessageServiceBB), ServiceLifetime.Scoped); var serviceDescriptor3 = ServiceDescriptor.Singleton(typeof(IMessageServiceB), typeof(MessageServiceBB)); var serviceDescriptor4 = ServiceDescriptor.Singleton<IMessageServiceB, MessageServiceBB>();
همانطور که دیدیم، IServiceCollection در
واقع لیست و مجموعهای از اشیاء است که از نمونههای جنریک IServiceCollection ، IList ، IEnumerable و Ienumberabl ارث بری میکند؛ بنابراین میتوان از متدهای تعریف شدهی در این
اینترفیسها برای IServiceCollection نیز استفاده کرد. حالا ما برای اضافه کردن این سرویسهای جدید،
بدین طریق عمل میکنیم:
Services.Add(serviceDescriptor1);
استفاده از متدهای TryAdd()
به کد زیر نگاه کنید :
services.AddScoped<IMessageServiceB, MessageServiceBA>(); services.AddScoped<IMessageServiceB, MessageServiceBB>();
برای جلوگیری از این خطا میتوانیم از متدهای TryAddSingleton() ، TryAddScoped() و TryAddTransient() استفاده کنیم. این متدها درون فضای نام Microsoft.Extionsion.DependencyInjection.Extension قرار دارند.
عملکرد کلی این
متدها درست مثل متدهای Add() است؛ با این تفاوت که این متد ابتدا IServiceCollection را جستجو میکند و اگر برای type مورد نظر سرویسی ثبت نشده بود،
آن را ثبت میکند:
services.TryAddScoped<IMessageServiceB, MessageServiceBA>(); services.TryAddScoped<IMessageServiceB, MessageServiceBB>();
جایگذاری یک سرویس با نمونهای دیگر
گاهی اوقات میخواهیم یک پیاده سازی دیگر را بجای پیاده سازی فعلی، در DI Container ثبت کنیم. در این حالت از متد Replace() بر روی IServiceCollection برای این کار استفاده میکنیم. این متد فقط یک ServiceDescriptor را به عنوان پارامتر ورودی میگیرد:
services.Replace(serviceDescriptor3);
services.RemoveAll<IMessageServiceB>();
معمولا در پروژههای معمول خودمان نیازی به استفاده از Replace() و RemoveAll() نداریم؛ مگر اینکه بخواهیم پیاده سازی اختصاصی خودمان را برای سرویسهای درونی فریم ورک یا کتابخانههای شخص ثالث، بجای پیاده سازی پیش فرض، ثبت و استفاده کنیم.
AddEnumerable()
فرض کنید دارید برنامهی نوبت دهی یک کلینیک را مینویسید و به صورت پیش فرض از شما خواستهاند که هنگام صدور نوبت، این قوانین را بررسی کنید:
- هر شخص در هفته نتواند بیش از 2 نوبت برای یک تخصص بگیرد.
- اگر شخص در ماه بیش از 3 نوبت رزرو شده داشته باشد ولی مراجعه نکرده باشد، تا پایان ماه، امکان رزرو نوبت را نداشته باشد .
- تعداد نوبتهای ثبت شدهی برای پزشک در آن روز نباید بیش از تعدادی باشد که پزشک پذیرش میکند.
- و ...
یک روش معمول برای پیاده سازی این قابلیت، ساخت سرویسی برای ثبت نوبت است که درون آن متدی برای بررسی کردن قوانین ثبت نام وجود دارد. خب، ما این کار را انجام میدهیم. تستهای واحد و تستهای جامع را هم مینویسیم و بعد برنامه را انتشار میدهیم و همه چیز خوب است؛ تا اینکه مالک محصول یک نیازمندی جدید را میخواهد که در آن ما باید قانون زیر را در هنگام ثبت نوبت بررسی کنیم:
- نوبتهای ثبت شده برای یک شخص نباید دارای تداخل باشند.
در این حالت ما باید دوباره سرویس Register را باز کنیم و به متد بررسی کردن قوانین برویم و دوباره کدهایی را برای بررسی کردن قانون جدید بنویسیم و احتمالا کد ما به این صورت خواهد شد:
public class RegisterAppointmentService : RegisterAppointmentService { public Task<Result> RegisterAsync( PatientInfoDTO patientIfno , DateTimeOffset requestedDateTime , PhysicianId phusicianId ) { CheckRegisterantionRule(patientInfo); // code here } private Task CheckRegisterationRule(PatientInfoDTO patientInfo) { CheckRule1(patientInfo); CheckRule2(patientInfo); CheckRule3(patientInfo); } }
در این حالت باید به ازای هر قانون جدید، به متد CheckRegisterationRule برویم و به ازای هر قانون، یک متد private جدید را بسازیم. مشکل این روش این است که در این حالت ما مجبوریم با هر کم و زیاد شدن قانون، این کلاس را باز کنیم و آن را تغییر دهیم و با هر تغییر دوباره، تستهای واحد آن را دوباره نویسی کنیم. در یک کلام در کد بالا اصول Separation of Concern و Open/Closed Principle را رعایت نمیشود.
یک راهکار این است که یک
سرویس جداگانه را برای بررسی کردن قوانین بنویسیم و آن را به سرویس ثبت نوبت تزریق کنیم:
public class ICheckRegisterationRuleForAppointmentService : ICheckRegisterationRuleForAppointmentService { public Task CheckRegisterantionRule(PatientInfoDTO patientInfo) { CheckRule1(patientInfo); CheckRule2(patientInfo); CheckRule3(patientInfo); } } public class RegisterAppointmentService : IRegisterAppointmentService { private ICheckRegisterationRuleForAppointmentService _ruleChecker; public RegisterAppointmentService (RegisterAppointmentService ruleChecker) { _ruleChecker = ruleChecker; } public Task<Result> RegisterAsync( PatientInfoDTO patientIfno , DateTimeOffset requestedDateTime , PhysicianId phusicianId ) { _ruleChecker.CheckRegisterantionRule(patientInfo); // code here } }
با این کار وظیفهی چک کردن قوانین و وظیفهی ثبت و ذخیره سازی قوانین را از یکدیگر جدا کردیم؛ ولی همچنان در سرویس بررسی کردن قوانین، اصل Open/Closed رعایت نشدهاست. خب راه حل چیست !؟
یکی از راه حلهای موجود، استفاده از الگوی قوانین یا Rule Pattern است. برای اجرای این الگو، میتوانیم با تعریف یک اینترفیس کلی برای بررسی کردن قانون، به ازای هر قانون یک پیاده سازی اختصاصی را داشته باشیم:
interface IAppointmentRegisterationRule { Task CheckRule(PatientInfo patientIfno); } public class AppointmentRegisterationRule1 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 1 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule2 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) {
Console.WriteLine("Rule 2 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule3 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 3 is checked"); return Task.CompletedTask; } } public class AppointmentRegisterationRule4 : IAppointmentRegisterationRule { public Task CheckRule(PatientInfo patientIfno) { Console.WriteLine("Rule 4 is checked"); return Task.CompletedTask; } }
services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule1>(); services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule2>(); services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule3>(); services.AddScoped<IAppointmentRegisterationRule, AppointmentRegisterationRule4>();
public class CheckRegisterationRuleForAppointmentService : ICheckRegisterationRuleForAppointmentService { private IEnumerable<IAppointmentRegisterationRule> _rules ; public CheckRegisterationRuleForAppointmentService(IEnumerable<IAppointmentRegisterationRule> rules) { _rules = rules; } public Task CheckRegisterantionRule(PatientInfoDTO patientInfo) { foreach(var rule in rules) { rule.CheckRule(patientInfo); } } }
کد بالا به
نظر کامل میآید ولی مشکلی دارد! اگر در DI Container برای IAppointmentRegisterationRule یک قانون را دو یا چند بار ثبت کنیم، در هر بار بررسی کردن قوانین، آن را به همان تعداد بررسی میکند و اگر این فرآیند منابع زیادی را به
کار میگیرد، میتواند عملکرد برنامهی ما را به هم بریزد. برای جلوگیری از این مشکل، از متد TryAddEnumerabl()
استفاده میکنیم که لیستی از ServiceDescriptor ها را میگیرد و هر serviceDescriptor را فقط یکبار ثبت میکند:
services.TryAddEnumerable(new[] { ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule1)), ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule2)), ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule3)), ServiceDescriptor.Scoped(typeof(IAppointmentRegisterationRule), typeof(AppointmentRegisterationRule4)), });
سلام
سال نو مبارک! به امید سالی بهتر از پارسال!
این روزها با هزینهای معادل هزینهی تهیهی یک هاست اشتراکی سالیانه برای بالاگذاری یک سایت معمولی در 5 سال قبل، میتوان یک VPS تهیه کرد و به این صورت قفل و کلید یک نیمچه سرور را (با 200 و خردهای مگ رم، 30 گیگ فضا، سرعت CPU نزدیک به 700 MHz و ویندوز سرور 2003 یا 2008) در اختیار شما قرار میدهند (البته به قول معروف هر چقدر پول بدهید همانقدر هم سخت افزار در اختیار شما قرار میدهند) بجای صرفا یک دایرکتوری مجازی محدود با 100 مگ فضای هاست که هر احدی در آن هاست اشتراکی میتواند سر مبارک را اندکی چرخانده و تمام زندگی شما را مرور کند و غیره!
استفادهی مفیدی هم که این VPS برای من داشته، ترنس لود کردن یک سری فایل است (با توجه به سرعتهای نجومی دریافت فایل این سرورها). برای مثال دریافت فایل از یوتیوب و انتقال به یک هاست دیگر برای دریافت سادهتر خودم و یا دیگران.
برای نمونه سایت dotnet-tv.com را در نظر بگیرید. تعدادی از ویدیوهای این سایت در یوتیوب هاست شده و از این دست زیاد هستند. خیلیها برای فرار از مشکلات کمبود پهنای باند از یوتیوب استفاده میکنند. یوتیوب هم که از این طرف بسته است. خوب، من الان میخواهم ویدیوی مربوط به ASP.Net MVC آن را مشاهده کنم، چکار باید کرد؟!
یک برنامهی سادهی کنسول را تهیه کردهام که این کار را برای VPS داران تسهیل میکند.
- دریافت فایل از یوتیوب
- آپلود خودکار آن به رپیدشیر
یک نمونه خروجی آن: (فایلهای یوتیوب سایت ذکر شده که به رپیدشیر منتقل شده)
دریافت
در سورس این برنامه موارد زیر پیاده سازی شده است:
- یافتن لینکهای یوتیوب سایت dotnet-tv.com با استفاده از regular expressions
- یافتن لینک دانلود مستقیم این فایلها از سایت یوتیوب که شامل استفاده از regular expressions برای استخراج قسمتهای مفید از صفحات و همچنین استفاده از امکانات Json دات نت فریم ورک سه و نیم برای parse قسمتهای استخراج شده است.
- ایجاد یک thread pool سفارشی که هر بار 7 لینک مستقیم را به صورت همزمان از یوتیوب دریافت میکند. (thread pool پیش فرض دات نت تمام تردها را به یکباره شروع میکند که برای اینکار مفید نیست. به همین جهت از این thread pool سفارشی شده استفاده شد)
پیش فرض فایلی که از سایت یوتیوب دریافت میشود MP4 با کیفیت بالا است که با fmt=18 در فایل Youtube.cs مشخص شده. فرمتهای دیگر را میتوانید از این فایل ایده بگیرید.
- آپلود فایل دریافتی از یوتیوب به یک اکانت رایگان کالکتور در رپیدشیر. (ماخذ این مورد در سایت code projects)
مشخصات این اکانت رایگان کالکتور در فایل app.config باید ذکر شود.
این سورس میتونه ایدهی ابتدایی بسیاری از کارهای مشابه باشد. برای مثال ایجاد یک وب سرویس، یک وب سایت، یک سرویس ایمیلی و غیره.
پ.ن.
کار انجام شده فعالیت وارز محسوب نمیشود زیرا مجوز ویدیوهای سایت یوتیوب این امکان توزیع (و بسیاری موارد دیگر) را به شما میدهد.
مطلب زیر چکیدهای است از کتاب Framework Design Guidelines در مورد سربارگذاری توابع
الف) از یک نوع خروجی استفاده کنید
مثال زیر را در نظر بگیرید:
public User[] GetGroupMembers(int groupId)
public List<User> GetGroupMembers(string groupName)
مشکل اینجا است که هنگام فراخوانی هر کدام، نحوهی استفاده متفاوت خواهد بود. بنابراین باید از این نوع سربارگذاری پرهیز کرد.
ب) نامهای آرگومانهای توابع سربارگذاری شده شما باید یکسان باشند
در مثال زیر، از اولین آرگومان جهت دریافت شناسهی یک گروه استفاده میشود:
public List<User> GetGroupMembers(int groupId)
public List<User> GetGroupMembers(int id, int pageIndex, int pageSize)
ج) ترتیب آرگومانها را در توابع سربارگذاری شده حفظ و رعایت نمائید
لطفا به مثال زیر دقت کنید:
public List<User> GetGroupMembers(int groupId)
public List<User> GetGroupMembers(int pageIndex, int pageSize, int groupId)
د) از call forwarding استفاده کنید
جهت توضیح بهتر call forwarding به مثال زیر دقت نمائید:
public List<User> GetGroupMembers(int groupId)
{
return GetGroupMembers(groupId, 0, 10);
}
public List<User> GetGroupMembers(int groupId, int pageIndex, int pageSize)
{
return GetGroupMembers(groupId, pageIndex, pageSize, SortOrder.Ascending);
}
public List<User> GetGroupMembers(int groupId, int pageIndex, int pageSize, SortOrder sortOrder)
{
var query = new GroupQuery();
query.GroupID = groupId;
query.PageIndex = pageIndex;
query.PageSize = pageSize;
query.SortOrder = sortOrder;
return GetGroupMembers(query);
}
public List<User> GetGroupMembers(GroupQuery groupQuery)
{
// Actual implementation to get group members goes here
}
ه) زیاده روی نکنید!
آخرین موردی که توصیه شده این است که در تهیه توابع سربارگذاری شده زیاده روی نکنید. در این نوع موارد باید قانون 80/20 را در نظر گرفت. 2 تا 3 تابع سربارگذاری شده ارائه دهید که 80 درصد کار را انجام میدهند و سپس یک تابع سربارگذاری شدهی دیگر ارائه دهید که 20 درصد باقیمانده را پوشش دهد.
پ.ن.
در سی شارپ 4 ، با معرفی optional parameters ، شاید کمتر به سربارگذاری نیاز باشد.
namespace EntitySample1.DomainClasses { public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public virtual PersonInfo PersonInfo { get; set; } public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; } public virtual ICollection<Address> Addresses { get; set; } } }
namespace EntitySample1.DomainClasses { public class PersonInfo { public int Id { get; set; } public string Note { get; set; } public string Major { get; set; } } }
namespace EntitySample1.DomainClasses { public enum PhoneType { Home, Mobile, Work } public class PhoneNumber { public int Id { get; set; } public string Number { get; set; } public PhoneType PhoneType { get; set; } public virtual Person Person { get; set; } } }
namespace EntitySample1.DomainClasses { public class Address { public int Id { get; set; } public string City { get; set; } public string Street { get; set; } public virtual ICollection<Person> Persons { get; set; } } }
namespace EntitySample1.DataLayer { public class PhoneBookDbContext : DbContext { public DbSet<Person> Persons { get; set; } public DbSet<PhoneNumber> PhoneNumbers { get; set; } public DbSet<Address> Addresses { get; set; } } }
استفاده از ردیابی تغییر عکس فوری
ردیابی تغییر عکس فوری، وابسته به این است که EF بفهمد، چه زمانی تغییرات رخ داده است. رفتار پیش فرض DbContext API ، این هست که به صورت خودکار بازرسی لازم را در نتیجهی رخدادهای DbContext انجام دهد. DetectChanges تنها اطلاعات مدیریت حالت context، که وظیفهی انعکاس تغییرات صورت گرفته به پایگاه داده را دارد، به روز نمیکند، بلکه اصلاح رابطه(ralationship) ترکیبی از خواص راهبری مرجع ، مجموعه ای و کلیدهای خارجی را انجام میدهد. این خیلی مهم خواهد بود که درک روشنی داشته باشیم از این که چگونه و چه زمانی تغییرات تشخیص داده میشوند،چه چیزی باید از آن انتظار داشته باشیم و چگونه کنترلش کنیم.
چه زمانی تشخیص خودکار تغییرات اجرا میشود؟
متد DetectChanges کلاس ObjectContext، از EF نسخهی 4 به عنوان بخشی از الگوی ردیابی تغییر عکس فوری اشیای POCO ،در دسترس بوده است. تفاوتی که در مورد DataContext.ChangeTracker.DetectChanges( در حقیقت ObjectContext.DetectChanges فراخوانی میشود) وجود دارد این است که، رویدادهای خیلی بیشتری وجود دارند که به صورت خودکار DetectChanges را فراخوانی میکنند.
لیستی از متدهایی که باعث انجام عمل تشخیص تغییرات (DetectChanges)، میشوند را در ادامه مشاهده میکنید:
• DbSet.Add
• DbSet.Find
• DbSet.Remove
• DbSet.Local
• DbSet.SaveChanges
• فراخوانی Linq Query از DbSet
• DbSet.Attach
• DbContext.GetValidationErrors
• DbContext.Entry
• DbChangeTracker.Entries
کنترل زمان فراخوانی DetectChanges
بیشترین زمانی که EF احتیاج به فهمیدن تغییرات دارد، در زمان SaveChanges است، اما حالتهای زیاد دیگری نیز هست. برای مثال، اگر ما از ردیاب تغییرات، درخواست وضعیت فعلی یک شی را بکنیم،EF احتیاج به اسکن کردن و بررسی تغییرات رخ داده را دارد. همچنین وضعیتی را در نظر بگیرید که شما از پایگاه داده یک شماره تلفن را واکشی میکنید و سپس آن را به مجموعه شماره تلفنهای یک شخص جدید اضافه میکنید.آن شماره تلفن اکنون تغییر کرده است، چرا که انتساب آن به یک شخص جدید،خاصیت PersonId آن را تغییر داده است. ولی EF برای اینکه بفهمد تغییر رخ داده است(یا حتی نداده است) ، احتیاج به اسکن کردن همهی اشیا Person دارد.
بیشتر عملیاتی که بر روی DbContext API انجام میدهید، موجب فراخوانی DetectChanges میشود. در بیشتر موارد DetectChanges به اندازه کافی سریع هست تا باعث ایجاد مشکل کارایی نشود. با این حال ممکن است ، شما تعداد خیلی زیادی اشیا در حافظه داشته باشید، و یا تعداد زیادی عملیات در DbContext ، در مدت خیلی کوتاهی انجام دهید، رفتار تشخیص خودکار تغییرات ممکن است، باعث نگرانیهای کارایی شود. خوشبختانه گزینه ای برای خاموش کردن رفتار تشخیص خودکار تغییرات وجود دارد و هر زمانی که میدانید لازم است، میتوانید آن را به صورت دستی فراخوانی کنید.
EF بر مبنای این فرض ساخته شده است که شما ، در صورتی که در فراخوانی آخرین API، موجودیتی تغییر پیدا کرده است، قبل از فراخوانی API جدید، باید DetectChanges صدا زده شود. این شامل فراخوانی DetectChanges، قبل از اجرای هر query نیز میشود.اگر این عمل ناموفق یا نابجا انجام شود،ممکن است عواقب غیر منتظره ای در بر داشته باشد. DbContext انجام این وظیفه را بر عهده گرفته است و به همین دلیل به طور پیش فرض تشخیص تغییرات خودکار آن فعال است.
نکته: تشخیص اینکه چه زمانی احتیاج به فراخوانی DetectChanges است،آن طور که ساده و بدیهی به نظر میآید نیست. تیم EF شدیدا توصیه کرده اند که فقط، وقتی با مشکلات عدم کارایی روبرو شدید، تشخیص تغییرات را به حالت دستی در بیاورید.همچنین توصیه شده که در چنین مواقعی، تشخیص خودکار تغییرات را فقط برای قسمتی از کد که با کارایی پایین مواجه شدید خاموش کنید و پس از اینکه اجرای آن قسمت از کد تمام شد،دوباره آن را روشن کنید.
برای خاموش یا روشن کردن تشخیص خودکار تغییرات، باید متغیر بولین DbContext.Configuration.AutoDetectChangesEnabled را تنظیم کنید.
در مثال زیر، ما در متد ManualDetectChanges، تشخیص خودکار تغییرات را خاموش کرده ایم و تاثیرات آن را بررسی کرده ایم.
private static void ManualDetectChanges() { using (var context = new PhoneBookDbContext()) { context.Configuration.AutoDetectChangesEnabled = false; // turn off Auto Detect Changes var p1 = context.Persons.Single(p => p.FirstName == "joe"); p1.LastName = "Brown"; Console.WriteLine("Before DetectChanges: {0}", context.Entry(p1).State); context.ChangeTracker.DetectChanges(); // call detect changes manually Console.WriteLine("After DetectChanges: {0}", context.Entry(p1).State); } }
در کدهای بالا ابتدا تشخیص خودکار تغییرات را خاموش کرده ایم و سپس یک شخص با نام joe را از دیتابیس فراخواندیم و سپس نام خانوادگی آن را به Brown تغییر دادیم. سپس در خط بعد، وضعیت فعلی موجودیت p1 را از context جاری پرسیدیم. در خط بعدی، DetectChanges را به صورت دستی صدا زده ایم و دوباره همان پروسه را برای به دست آوردن وضیعت شی p1، انجام داده ایم. همان طور که میبینید ، برای به دست آوردن وضعیت فعلی شی مورد نظر از متد Entry متعلق به ChangeTracker API استفاده میکنیم، که در آینده مفصل در مورد آن بحث خواهد شد. اگر شما متد Main را با صدا زدن ManualDetectChanges ویرایش کنید ، خروجی زیر را مشاهده خواهید کرد:
Before DetectChanges: Unchanged After DetectChanges: Modified
همان طور که انتظار میرفت، به دلیل خاموش کردن تشخیص خودکار تغییرات، context قادر به تشخیص تغییرات صورت گرفته در شی p1 نیست، تا زمانی که متد DetectChanges را به صورت دستی صدا بزنیم. دلیل این که در دفعه اول، ما نتیجهی غلطی مشاهده میکنیم، این است که ما قانون را نقض کرده ایم و قبل از صدا زدن هر API ، متد DetectChanges را صدا نزده ایم. خوشبختانه چون ما در اینجا وضعیت یک شی را بررسی کردیم، با عوارض جانبی آن روبرو نشدیم.
نکته: به این نکته توجه داشته باشید که متد Entry به صورت خودکار، DetectChanges را فراخوانی میکند. برای اینکه دانسته بخواهیم این رفتار را غیر فعال کنیم، باید AutoDetectChangesEnabled را غیر فعال کنیم.
در مثال فوق ،خاموش کردن تشخیص خودکار تغییرات، برای ما مزیتی به همراه نداشت و حتی ممکن بود برای ما دردسر ساز شود. ولی حالتی را در نظر بگیرید که ما یک سری API را فراخوانی میکنیم ،بدون این که در این بین ،در حالت اشیا تغییری ایجاد کنیم.در نتیجه میتوانیم از فراخوانیهای بی جهت DetectChanges جلوگیری کنیم.
در متد AddMultiplePersons مثال بعدی، این کار را نشان داده ام:
private static void AddMultiplePerson() { using (var context = new PhoneBookDbContext()) { context.Configuration.AutoDetectChangesEnabled = false; context.Persons.Add(new Person { FirstName = "brad", LastName = "watson", BirthDate = new DateTime(1990, 6, 8) }); context.Persons.Add(new Person { FirstName = "david", LastName = "brown", BirthDate = new DateTime(1990, 6, 8) }); context.Persons.Add(new Person { FirstName = "will", LastName = "smith", BirthDate = new DateTime(1990, 6, 8) }); context.SaveChanges(); } }
استفاده از DetectChanges برای فراخوانی اصلاح رابطه
DetectChanges همچنین مسئولیت انجام اصلاح رابطه ، برای هر رابطه ای که تشخیص دهد تغییر کرده است را دارد.اگر شما بعضی از روابط را تغییر دادید و مایل بودید تا همهی خواص راهبری و خواص کلید خارجی را منطبق کنید، DetectChanges این کار را برای شما انجام میدهد. این قابلیت میتواند برای سناریوهای data-binding که در آن ممکن است در رابط کاربری(UI) یکی از خواص راهبری (یا حتی یک کلید خارجی) تغییر کند، و شما بخواهید که خواص دیگری این رابطه به روز شوند و تغییرات را نشان دهند، مفید واقع شود.
متد DetectRelationshipChanges در مثال زیر از DetectChanges برای انجام اصلاح رابطه استفاده میکند.
private static void DetectRelationshipChanges() { using (var context = new PhoneBookDbContext()) { var phone1 = context.PhoneNumbers.Single(x => x.Number == "09351234567"); var person1 = context.Persons.Single(x => x.FirstName == "will"); person1.PhoneNumbers.Add(phone1); Console.WriteLine("Before DetectChanges: {0}", phone1.Person.FirstName); context.ChangeTracker.DetectChanges(); // ralationships fix-up Console.WriteLine("After DetectChanges: {0}", phone1.Person.FirstName); } }
در اینجا ابتدا ما شماره تلفنی را از دیتابیس لود میکنیم. سپس شخص دیگری را نیز با نام will از دیتابیس میخوانیم. قصد داریم شماره تلفن خوانده شده را به این شخص نسبت دهیم و مجموعه شماره تلفنهای وی اضافه کنیم و ما این کار را با افزودن phone1 به مجموعه شماره تلفنهای person1 انجام داده ایم. چون ما از اشیای POCO استفاده کرده ایم،EF نمیفهمد که ما این تغییر را ایجاد کرده ایم و در نتیجه کلید خارجی PersonId شی phone1 را اصلاح نمیکند. ما میتوانیم تا زمانی صبر کنیم تا متدی مثل SaveChanges، متد DetectChanges را فراخوانی کند،ولی اگر بخواهیم این عمل در همان لحظه انجام شود، میتوانیم DetectChanges را دستی صدا بزنیم.
اگر ما متد Main را با اضافه کردن فرخوانی DetectRealtionShipsChanges تغییر بدهیم و آن را اجرا کنیم، نتیجه زیر را مشاهده میکنید:
Before DetectChanges: david After DetectChanges: will
تا قبل از فراخوانی تشخیص تغییرات(DetectChanegs)، هنوز phone1 منتسب به شخص قدیمی(david) بوده، ولی پس از فراخوانی DetectChanges ، اصلاح رابطه رخ داده و همه چیز با یکدیگر منطبق میشوند.
فعال سازی و کار با پروکسیهای ردیابی تغییر
اگر پروفایلر کارایی شما، فراخوانیهای بیش از اندازه DetectChnages را به عنوان یک مشکل شناسایی کند، و یا شما ترجیح میدهید که اصلاح رابطه به صورت بلادرنگ صورت گیرد ، ردیابی تغییر پروکسیهای پویا، به عنوان گزینه ای دیگر مطرح میشود.فقط با چند تغییر کوچک در کلاسهای POCO، EF قادر به ساخت پروکسیهای پویا خواهد بود.پروکسیهای ردیابی تغییر به EF اجازه ردیابی تغییرات در همان لحظه ای که ما تغییری در اشیای خود میدهیم را میدهند و همچنین امکان انجام اصلاح رابطه را در هر زمانی که تغییرات روابط را تشخیص دهد، دارد.
برای اینکه پروکسی ردیابی تغییر بتواند ساخته شود، باید قوانین زیر رعایت شود:
• کلاس باید public باشد و seald نباشد.
• همهی خواص(properties) باید virtual تعریف شوند.
• همهی خواص باید getter و setter با سطح دسترسی public داشته باشند.
• همهی خواص راهبری مجموعه ای باید نوعشان، از نوع ICollection<T> تعریف شوند.
کلاس Person مثال خود را به گونه ای بازنویسی کرده ایم که تمام قوانین فوق را پیاده سازی کرده باشد.
نکته: توجه داشته باشید که ما دیگر در داخل سازنده کلاس ،کدی نمینویسیم و منطقی که باعث نمونه سازی اولیه خواص راهبری میشدند، را پیاده سازی نمیکنیم. این پروکسی ردیاب تغییر، همهی خواص راهبری مجموعه ای را تحریف کرده و ار نوع مجموعه ای مخصوص خود(EntityCollection<T>) استفاده میکند. این نوع مجموعه ای، هر تغییری که در این مجموعه صورت میگیرد را زیر نظر گرفته و به ردیاب تغییر گزارش میدهد. اگر تلاش کنید تا نوع دیگری مانند List<T> که معمولا در سازنده کلاس از آن استفاده میکردیم را به آن انتساب دهیم، پروکسی، استثنایی را پرتاب میکند.
namespace EntitySample1.DomainClasses { public class Person { public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual DateTime BirthDate { get; set; } public virtual PersonInfo PersonInfo { get; set; } public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; } public virtual ICollection<Address> Addresses { get; set; } } }
با این که احتیاجات رسیدن به پروکسیهای ردیابی تغییر خیلی ساده هستند، اما سادهتر از آن ها، فراموش کردن یکی از آن هاست.حتی از این هم سادهتر میشود که در آینده تغییری در آن کلاسها ایجاد کنید و ناخواسته یکی از آن قوانین را نقض کنید.به این خاطر، فکر خوبیست که یک آزمون واحد نیز اضافه کنیم تا مطمئن شویم که EF توانسته، پروکسی ردیابی تغییر را ایجاد کند یا نه.
در مثال زیر یک متد نوشته شده که این مورد را مورد آزمایش قرار میدهد. همچنین فراموش نکنید که فضای نام System.Data.Object.DataClasses را به usingهای خود اضافه کنید.
private static void TestForChangeTrackingProxy() { using (var context = new PhoneBookDbContext()) { var person = context.Persons.First(); var isProxy = person is IEntityWithChangeTracker; Console.WriteLine("person is a proxy: {0}", isProxy); } }
اکنون متد ManualDetectChanges را که کمی بالاتر بررسی کرده ایم را در نظر بگیرید و کد context.ChangeTracker.DetectChanges آن را حذف کنید و بار دیگر آن را فرا بخوانید و نتیجه را مشاهده کنید:
Before DetectChanges: Modified After DetectChanges: Modified
اکنون متد DetectRelationshipChanges را ویرایش کرده و برنامه را اجرا کنید:
Before DetectChanges: will After DetectChanges: will
نکته: زمانی که شما از پروکسیهای ردیابی تغییر استفاده میکنید،احتیاجی به غیرفعال کردن تشخیص خودکار تغییرات نیست. DetectChanges برای همه اشیایی که تغییرات را به صورت بلادرنگ گزارش میدهند،فرآیند تشخیص تغییرات را انجام نمیدهد. بنابراین فعال سازی پروکسیهای ردیابی تغییر،برای رسیدن به مزایای کارایی بالا در هنگام عدم استفاده از DetectChanges کافی است. در حقیقت زمانی که EF، یک پروکسی ردیابی پیدا میکند، از مقادیر خاصیت ها، عکس فوری نمیگیرد. همچنین DetectChanges این را نیز میداند که نباید تغییرات موجودیت هایی که عکسی از مقادیر اصلی آنها ندارد را اسکن کند.
تذکر: اگر شما موجودیت هایی داشته باشید که شامل انواع پیچیده(Complex Types) میشوند،EF هنوز هم از ردیابی تغییر عکس فوری، برای خواص موجود در نوع پیچیده استفاده میکند، و از این جهت لازم است کهEF، برای نمونهی نوع پیچیده، پروکسی ایجاد نمیکند.شما هنوز هم، تشخیص خودکار تغییرات خواصی که مستقیما درون آن موجودیت(Entity) تعریف شده اند را دارید، ولی تغییرات رخ داده درون نوع پیچیده، فقط از طریق DetectChanges قابل تشخیص است.
چگونگی اطمینان از اینکه نمونههای جدید ، پروکسیها را دریافت خواهند کرد
EF به صورت خودکار برای نتایج حاصل از کوئری هایی که شما اجرا میکنید، پروکسیها را ایجاد میکند. با این حال اگر شما فقط از سازندهی کلاس POCO خود برای ایجاد نمونهی جدید استفاده کنید،دیگر پروکسیها ایجاد نخواهند شد.بدین منظور برای دریافت پروکسی ها، شما باید از متد DbSet.Create برای دریافت نمونههای جدید آن موجودیت استفاده کنید.
نکته: اگر شما، پروکسیهای ردیابی تغییر را برای موجودیتی از مدلتان فعال کرده باشید،هنوز هم میتوانید،نمونههای فاقد پروکسی آن موجودیت را ایجاد و بیافزایید.خوشبختانه EF با موجودیتهای پروکسی و غیر پروکسی در همان مجموعه(set) کار میکند.شما باید آگاه باشید که ردیابی خودکار تغییرات و یا اصلاح رابطه، برای نمونه هایی که پروکسی هایی ردیابی تغییر نیستند، قابل استفاده نیستند.داشتن مخلوطی از نمونههای پروکسی و غیر پروکسی در همان مجموعه، میتواند گیج کننده باشد.بنابر این عموما توصیه میشود که برای ایجاد نمونههای جدید از DbSet.Create استفاده کنید، تا همهی موجودیتهای موجود در مجموعه، پروکسیهای ردیابی تغییر باشند.
متد CreateNewProxies را به برنامهی خود اضافه کرده و آن را اجرا کنید.
private static void CreateNewProxies() { using (var context = new PhoneBookDbContext()) { var phoneNumber = new PhoneNumber { Number = "987" }; var davidPersonProxy = context.Persons.Create(); davidPersonProxy.FirstName = "david"; davidPersonProxy.PhoneNumbers.Add(phoneNumber); Console.WriteLine(phoneNumber.Person.FirstName); } }
خروجی مثال فوق david خواهد بود.همان طور که میبینید با استفاده از context.Persons.Create، نمونهی ساخته شده، دیگر شی POCO نیست، بلکه davidPersonProxy، از جنس پروکسی ردیابی تغییر است و تغییرات آن به طور خودکار ردیابی شده و رابطه آن نیز به صورت خودکار اصلاح میشود.در اینجا نیز با افزودن phoneNumber به شماره تلفنهای davidPersonProxy، به طور خودکار رابطهی بین phoneNumber و davidPersonPeroxy بر قرار شده است. همان طور که میدانید این عملیات بدون استفاده از پروکسیهای ردیابی تغییرات امکان پذیر نیست و موجب بروز خطا میشود.
ایجاد نمونههای پروکسی برای انواع مشتق شده
اورلود جنریک دیگری برای DbSet.Create وجود دارد که برای نمونه سازی کلاسهای مشتق شده در مجموعه ما استفاده میشود .برای مثال، فراخوانی Create بر روی مجموعهی Persons ،نمونه ای از کلاس Person را بر میگرداند.ولی ممکن است کلاس هایی در مجموعهی Persons وجود داشته باشند، که از آن مشتق شده باشند، مانند Student. برای دریافت نمونهی پروکسی Student، از اورلود جنریک Create استفاده میکنیم.
var newStudent = context.Persons.Create<Student>();
تا به این جای کار باید متوجه شده باشید که ردیابی تغییرات، فرآیندی ساده و بدیهی نیست و مقداری سربار در کار است. در بعضی از بخشهای برنامه تان، احتمالا دادهها را به صورت فقط خواندنی در اختیار کاربران قرار میدهید و چون اطلاعات هیچ وقت تغییر نمیکنند، شما میخواهید که سربار ناشی از ردیابی تغییرات را حذف کنید.
خوشبختانه EF شامل متد AsNoTracking است که میتوان از آن برای اجرای کوئریهای بدون ردیابی استفاده کرد.یک کوئری بدون ردیابی، یک کوئری ساده هست که نتایج آن توسط context برای تشخیص تغییرات ردیابی نخواهد شد.
متد PrintPersonsWithoutChangeTracking را به برنامه اضافه کنید و آن را اجرا کنید:
private static void PrintPersonsWithoutChangeTracking() { using (var context = new PhoneBookDbContext()) { var persons = context.Persons.AsNoTracking().ToList(); foreach (var person in persons) { Console.WriteLine(person.FirstName); } } }
نکته: واکشی دادهها بدون ردیابی تغییرات،معمولا وقتی باعث افزایش قابل توجه کارایی میشود که بخواهیم تعداد خیلی زیادی داده را به صورت فقط خواندنی نمایش دهیم. اگر برنامهی شما داده ای را تغییر میدهد و میخواهد آن را ذخیره کند، باید از AsNoTracking استفاده نکنید.
AsNoTracking یک متد الحاقی است، که در <IQueryable<T تعریف شده است، در نتیجه شما میتوانید از آن، در کوئریهای LINQ نیز استفاده کنید. شما میتوانید از AsNoTracking، در انتهای DbSet ،در خط from کوئری استفاده کنید.
var query = from p in context.Persons.AsNoTracking() where p.FirstName == "joe" select p;
شما همچنین از AsNoTracking میتوانید برای تبدیل یک کوئری LINQ موجود، به یک کوئری فاقد ردیابی استفاده کنید. این نکته را به یاد داشته باشید که فقط AsNoTracking بر روی کوئری، فرانخوانده شده است، بلکه متغیر query را با نتیجهی حاصل از فراخوانی AsNoTracking بازنویسی(override) کرده است و این، از این جهت لازم است که AsNoTracking ،تغییری در کوئری ای که بر روی آن فراخوانده شده نمیدهد، بلکه یک کوئری جدید بر میگرداند.
var query = from p in context.Persons where p.FirstName == "joe" select p; query = query.AsNoTracking();
منبع: ترجمه ای آزاد از کتاب Programming Entity Framework: DbContext
- ساخت برنامههای دسکتاپ به صورت چندسکویی (ویندوز، لینوکس، مک)
- استفاده از HTML,CSS,JavaScript که طراحان وب در این زمینه با آن به آسانی ارتباط برقرار میکنند.
- قابلیت استفاده از کتابخانههای قدرتمند تحت وب چون Bootstrap,Jquery,Angular Js و ...
- متن باز و رایگان است.
D:\electron\test1>npm init
npm i electron-prebuilt --save-dev
لازم است در اینجا توضیحی کوتاه در مورد انواع وابستگیها داشته باشیم:
Dependencies این نوع از وابستگیها، بستههایی را نصب میکنند که شما از آنها در کدهایتان استفاده میکنید و در آینده به همراه پروژه کمپایل میشوند. به طور خودکار وقتی بستهای را به عنوان وابستگی معرفی میکنید، npm وابستههای آن بسته را به صورت درختی بررسی میکند و آنها را هم نصب میکند.
DevDependencies : این نوع از وابستگیهای برای کارهای دیباگینگ و ... است؛ مثل آزمون واحد و ... که نیازی نیست در کامپایل نهایی لحاظ گردند. اگر این نوع کتابخانهها را به جای devdependencies به dependencies ارسال کنید، اتفاق خاصی نمیافتد. ولی در حجم برنامهی نهایی شما تاثیرگذار خواهند بود.
PeerDependencies: این نوع وابستگیها برای معرفی بستههایی استفاده میشوند که در پلاگینهایی که استفاده میکنید تاثیر دارند. ممکن است پلاگینی نیاز به استفادهی از یک بسته را دارد، ولی آن را در کد، Require نکرده باشد (در مورد Require بعدا صحبت میکنیم). ولی برای اجرا نیاز به این بسته دارد. به همین دلیل از نسخهی 3 به بعد، به شما هشدار میدهد که این بستهها را نیز لحاظ کنید (تا نسخهی npm2 به طور خودکار نصب میشد). همچنین نسخه بندی این وابستگیها را نیز در نظر میگیرد. این حالت را میتوانید مانند پلاگینهای جیکوئری تصور کنید که نیاز است قبل از آنها، کتابخانهی جیکوئری صدا زده شود؛ در صورتی که در خود پلاگین، جی کوئری صدا زده نشده است.
ویرایشگر اتم
قبل از اینکه بخواهیم کدنویسی با هر زبانی را آغاز کنیم، عموما یک ادیتور مناسب را برای کارمان بر میگزینیم. الکترون نیازی به ادیتور خاصی ندارد و از Notepad گرفته تا هر ادیتور قدرتمند دیگری را میتوانید استفاده کنید. ولی ادیتور اتم Atom که توسط خود الکترون هم تولید شده است، برای استفاده رایج است. ویژوال استودیو هم در این زمینه بسیار خوب و قدرتمند ظاهر شده است و حاوی Intellisense هوشمندی است.
این ادیتور که با ظاهری جذاب، توسط تیم گیت هاب تولید شده است، یک ویرایشگر متن باز با قابلیت توسعه و تغییر پذیری بالاست و از بستههای Node.js پشتیانی میکند و به صورت داخلی مجهز به سیستم گیت میباشد. بیشتر فناوریهای استفاده شده در این ویرایشگر، رایگان بوده و دارای جامعهی بزرگ متن باز میباشند. از فناوریهای مورد استفادهی آن میتوان به الکترون، CoffeScript ، Node.js ,LESS و ... اشاره کرد. شعار سازندگان این ادیتور «یک ویرایشگر قابل هک برای قرن 21» میباشد.
برای پشتیبانی از زبانهای مختلف، حاوی تعدادی زیادی پلاگین پیش فرض است مانند روبی ، سی شارپ، PHP ,Git,Perl,C/C++, Go,Objective-C,YAML و ...
آغاز کدنویسی
بگذارید کدنویسی را شروع کنیم. اگر اتم را نصب کرده باشید، میتوانید با وارد کردن عبارت زیر، پروژه خود را در ادیتور باز کنید:
atom .
{ "name": "electron", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "electron ." }, "author": "", "license": "ISC" }
const electron = require('electron'); const {app} = electron; const {BrowserWindow} = electron;
let win; app.on('ready', function() { // Create the browser window. win = new BrowserWindow({ width: 800, height: 600 }); });
اکنون در کنسول مینویسیم:
npm start
برای اینکه اولین برنامه واقعا خالی نباشد و ظاهری به آن بدهیم، یک فایل html میسازیم و در callback رویداد ready، بعد از ساخت پنجره آن را صدا میزنیم:
win.loadURL(`file://${__dirname}/index.html`);
تغییر Major تنها در Babel کافی نیست
پس از اعلام خبری که قرار بود نسخه اول اورلیا در آخر این هفته منتشر شود تنها کاری که Babel توانست انجام دهد تغییر در major ورژن خود و ایجاد تغییرات اساسی در ورژن بعدی بود، با این خبر اورلیا در آخر هفته تنها به منتشر نسخه Beta اکتفا میکند و این به نظر من نتیجه ای مشابه Knockout در آینده پیدا خواهد کرد، چون هیچ تضمینی از این که Durandal مدت زیادی آن را پشتیبانی کند نیست...