فارسی نویسی در Unity
ReSharper Ultimate 2018.3.1 منتشر شد
This bug-fix update resolves the following issues:
ReSharper Build couldn’t build a project if MSBuild was installed as part of Visual Studio 2019 Preview 1 (RSRP-472694).
Visual Studio froze on JavaScript debugging (RSRP-472802).
Inconsistent default state around ‘Inconsistent Naming‘ inspection (RSRP-472812).
A false positive for NUnit TestCase (RSRP-472787).
Unit test coverage highlighting is not shown for projects targeting .NET Framework 3.5 and lower (DCVR-9525).
False “Cannot convert type” error for Promise in TypeScript.
Find Usages did not process bodies of #define macros when looking for textual occurrences (RSCPP-24977).
“Remove unused parameter” quick-fix was broken in C++ (RSCPP-25094).
Now you can upgrade any .NET application to the latest version of .NET inside of Visual Studio! We are happy to introduce it as a Visual Studio extension and will upgrade your .NET Framework or .NET Core web- and desktop apps. In this video, Olia shows you how to get the extension and start to update your projects to the latest version of NET in minutes.
معرفی NET 5 Preview 4.
.NET apps can now run natively on Windows ARM64. This follows the support we added for Linux ARM64 in .NET Core 3.0. With .NET 5.0, you can develop web and UI apps on Windows ARM64 devices, and deliver your applications to users who own Surface Pro X and similar devices. You can already run .NET Core and .NET Framework apps on Windows ARM64, but via x86 emulation. It’s workable, but native ARM64 execution has much better performance.
کتابخانه Multi-Step-Form-Js
Multi Step Form with jQuery validation Demo
- utilizes jquery validation (with or without jquery unobtrusive validation) to validate the form at each step.
- contains customizable header step classes to distinguish between active, complete, and incomplete steps.
- triggers custom change events with relevant step data for custom processing (e.g. updating progress bars)
npm install multi-step-form-js
یکی از چالشهایی که در طراحی زیرساخت برای Domain هایی که تعداد زیادی عملیات CRUD را در back office سیستم خود دارند، داشتن مکانیزمی برای ذخیره سازی اطلاعات Master-Detail یا چه بسا Master-Detail-DetailOfDetail میباشد. در ادامه نحوه برخورد با چنین سناریوهایی را در EF Core و همچنین با استفاده از AutoMapper و FluentValidation بررسی خواهیم کرد.
موجودیتهای فرضی
public abstract class Entity : IHaveTrackingState { public long Id { get; set; } [NotMapped] public TrackingState TrackingState { get; set; } } public class Master : Entity { public string Title { get; set; } public ICollection<Detail> Details { get; set; } } public class Detail : Entity { public string Title { get; set; } public ICollection<DetailOfDetail> Details { get; set; } public Master Master { get; set; } public long MasterId { get; set; } } public class DetailOfDetail : Entity { public string Title { get; set; } public Detail Detail { get; set; } public long DetailId { get; set; } }
DbContext برنامه
public class ProjectDbContext : DbContext { public DbSet<Master> Masters { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseInMemoryDatabase("SharedDatabaseName"); } }
public interface IHaveTrackingState { TrackingState TrackingState { get; set; } //ICollection<string> ModifiedProperties { get; set; } } public enum TrackingState { Unchanged = 0, Added = 1, Modified = 2, Deleted = 3 }
با استفاده از پراپرتی TrackingState بالا، امکان مشخص کردن صریح State رکورد ارسالی توسط کلاینت مهیا میشود. قبلا نیز مطلبی در راستای STE یا همان Self-Tracking Entity تهیه شده است؛ و همچنین نظرات ارسالی این مطلب نیز میتواند مفید واقع شود.
DTOهای متناظر با موجودیتهای فرضی
public abstract class Model : IHaveTrackingState { public long Id { get; set; } public TrackingState TrackingState { get; set; } } public class MasterModel : Model { public string Title { get; set; } public ICollection<DetailModel> Details { get; set; } } public class DetailModel : Model { public string Title { get; set; } public ICollection<DetailOfDetailModel> Details { get; set; } } public class DetailOfDetailModel : Model { public string Title { get; set; } }
Mapper.Initialize(expression => { expression.CreateMap<MasterModel, Master>(MemberList.None).ReverseMap(); expression.CreateMap<DetailModel, Detail>(MemberList.None).ReverseMap(); expression.CreateMap<DetailOfDetailModel, DetailOfDetail>(MemberList.None).ReverseMap(); });
البته بهتر است این تنظیمات در درون Profileهای مرتبط با AutoMapper کپسوله شوند و در زمان مورد نیاز نیز برای انجام نگاشتها، واسط IMapper تزریق شده و استفاده شود.
تهیه داده ارسالی فرضی توسط کلاینت
var masterModel = new MasterModel { Title = "Master-Title", TrackingState = TrackingState.Added, Details = new List<DetailModel> { new DetailModel { Title = "Detail-Title", TrackingState = TrackingState.Added, Details = new List<DetailOfDetailModel> { new DetailOfDetailModel { Title = "DetailOfDetail-Title", TrackingState = TrackingState.Added, } } } } };
ذخیره سازی اطلاعات
در EF Core، متد جدید context.ChangeTracker.TrackGraph برای به روز رسانی وضعیت یک گراف از اشیاء مشابه به اطلاعات ارسالی ذکر شده در بالا، اضافه شده است. این مکانیزم مفهوم کاملا جدیدی در EF Core میباشد که امکان کنترل نهایی برروی اشیایی را که قرار است توسط Context ردیابی شوند، مهیا میکند. با پیمایش یک گراف، امکان اجرای عملیات مورد نظر شما را برروی تک تک اشیاء، مهیا میسازد.
using (var context = new ProjectDbContext()) { Console.WriteLine("################ Create Master and Details and DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
در تکه کد بالا، پس از انجام عملیات نگاشت، توسط متد TrackGraph به صورت صریح، وضعیت موجودیتها مشخص شده است؛ این کار با تغییر State ارسالی توسط کلاینت به State قابل فهم توسط EF انجام شدهاست. برای این منظور دو متد الحاقی زیر را میتوان در نظر گرفت:
public static class TrackingStateExtensions { public static EntityState ToEntityState(this TrackingState trackingState) { switch (trackingState) { case TrackingState.Added: return EntityState.Added; case TrackingState.Modified: return EntityState.Modified; case TrackingState.Deleted: return EntityState.Deleted; case TrackingState.Unchanged: return EntityState.Unchanged; default: return EntityState.Unchanged; } } public static TrackingState ToTrackingState(this EntityState state) { switch (state) { case EntityState.Added: return TrackingState.Added; case EntityState.Modified: return TrackingState.Modified; case EntityState.Deleted: return TrackingState.Deleted; case EntityState.Unchanged: return TrackingState.Unchanged; default: return TrackingState.Unchanged; } } }
//GetForEditAsync var masterModel = context.Masters .ProjectTo<MasterModel>() .AsNoTracking().Single(a => a.Id == 1); //Client var detail1 = masterModel.Details.First(); detail1.Title = "Details-EditedTitle"; detail1.TrackingState = TrackingState.Modified; foreach (var detail in detail1.Details) { detail.TrackingState = TrackingState.Deleted; //detail.Title = "DetailOfDetails-EditedTitle"; }
متدی تحت عنوان GetForEditAsync که یک MasterModel را بازگشت میدهد، در نظر بگیرید؛ کلاینت از طریق API، این Object Graph را دریافت میکند و تغییرات خود را اعمال کرده و همانطور که مشخص میباشد به دلیل اینکه تنظیمات نگاشت بین Detail و DetailModel در ابتدای بحث نیز انجام شده است، این بار دیگر نیاز به استفاده از متد Include نمیباشد و این عملیات توسط متد ProjectTo خودکار میباشد. در نهایت داده ارسالی توسط کلاینت را دریافت کرده و به شکل زیر عملیات به روز رسانی انجام میشود:
using (var context = new ProjectDbContext()) { Console.WriteLine( "################ Unchanged Master and Modified Details and Deleted DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
برای بحث اعتبارسنجی هم میتوان به شکل زیر عمل کرد:
public class MasterValidator : AbstractValidator<MasterModel> { public MasterValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailValidator()); } } public class DetailValidator : AbstractValidator<DetailModel> { public DetailValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailOfDetailValidator()); } } public class DetailOfDetailValidator : AbstractValidator<DetailOfDetailModel> { public DetailOfDetailValidator() { RuleFor(a => a.Title).NotEmpty(); } }
با استفاده از متد RuleForEach و SetValidator موجود در کتابخانه FluentValidation، امکان مشخص کردن اعتبارسنج برای Detail موجود در شیء Master را خواهیم داشت.
همچنین با توجه به این که برای عملیات Create و Edit از یک مدل (DTO) استفاده خواهیم کرد، شاید لازم باشد اعتبارسنجی خاصی را فقط در زمان ویرایش لازم داشته باشیم، که در این صورت میتوان از امکانات RuleSet استفاده کنید. در مطلب «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها» با استفاده ValidateWithRuleAttribute امکان مشخص کردن RuleSet مورد نظر برای اعتبارسنجی ورودی متد سرویس نیز در نظر گرفته شده است.
منابع تکمیلی
- ChangeTracker.TrackGraph() in Entity Framework Core
- Disconnected entities
- https://msdn.microsoft.com/magazine/mt694083
- https://msdn.microsoft.com/magazine/mt767693
- https://blog.tonysneed.com/2017/10/01/trackable-entities-for-ef-core/
- Tracking Individually Modified Properties