‫به روز رسانی اطلاعات Master-Detail یا Master-Detail-DetailOfDetail با استفاده از EF Core


‫به روز رسانی اطلاعات Master-Detail یا Master-Detail-DetailOfDetail با استفاده از EF Core

یکی از چالش‌هایی که در طراحی زیرساخت برای Domain هایی که تعداد زیادی عملیات CRUD را در back office سیستم خود دارند، داشتن مکانیزمی برای ذخیره سازی اطلاعات Master-Detail یا چه بسا Master-Detail-DetailOfDetail می‌باشد. در ادامه نحوه برخورد با چنین سناریوهایی را در EF Core و همچنین با استفاده از AutoMapper و FluentValidation بررسی خواهیم...

یکی از چالش‌هایی که در طراحی زیرساخت برای Domain هایی که تعداد زیادی عملیات CRUD را در back office سیستم خود دارند، داشتن مکانیزمی برای ذخیره سازی اطلاعات Master-Detail یا چه بسا Master-Detail-DetailOfDetail می‌باشد. در ادامه نحوه برخورد با چنین سناریوهایی را در EF Core و همچنین با استفاده از AutoMapper و FluentValidation بررسی خواهیم کرد. با ما همراه شوید.


موجودیت‌های فرضی

public abstract class Entity : IHaveTrackingState { public long Id [NotMapped] public TrackingState TrackingState } public class Master : Entity { public string Title public ICollection Details } public class Detail : Entity { public string Title public ICollection Details public Master Master public long MasterId } public class DetailOfDetail : Entity { public string Title public Detail Detail public long DetailId }

DbContext برنامه

public class ProjectDbContext : DbContext { public DbSet Masters protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseInMemoryDatabase("SharedDatabaseName"); } }

واسط IHaveTrackingState

public interface IHaveTrackingState { TrackingState TrackingState //ICollection ModifiedProperties } 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 public TrackingState TrackingState } public class MasterModel : Model { public string Title public ICollection Details } public class DetailModel : Model { public string Title public ICollection Details } public class DetailOfDetailModel : Model { public string Title }
تنظیمات نگاشت موجودیت‌ها و DTOها
Mapper.Initialize(expression => { expression.CreateMap(MemberList.None).ReverseMap(); expression.CreateMap(MemberList.None).ReverseMap(); expression.CreateMap(MemberList.None).ReverseMap(); });

البته بهتر است این تنظیمات در درون Profile‌های مرتبط با AutoMapper کپسوله شوند و در زمان مورد نیاز نیز برای انجام نگاشت‌ها، واسط IMapper تزریق شده و استفاده شود.


تهیه داده ارسالی فرضی توسط کلاینت

var masterModel = new MasterModel { Title = "Master-Title", TrackingState = TrackingState.Added, Details = new List { new DetailModel { Title = "Detail-Title", TrackingState = TrackingState.Added, Details = new List { 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(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() .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(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }

با خروجی زیر:

‫به روز رسانی اطلاعات Master-Detail یا Master-Detail-DetailOfDetail با استفاده از EF Core

برای بحث اعتبارسنجی هم می‌توان به شکل زیر عمل کرد:

public class MasterValidator : AbstractValidator { public MasterValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailValidator()); } } public class DetailValidator : AbstractValidator { public DetailValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailOfDetailValidator()); } } public class DetailOfDetailValidator : AbstractValidator { public DetailOfDetailValidator() { RuleFor(a => a.Title).NotEmpty(); } }

با استفاده از متد RuleForEach و SetValidator موجود در کتابخانه FluentValidation، امکان مشخص کردن اعتبارسنج برای Detail موجود در شیء Master را خواهیم داشت.

همچنین با توجه به این که برای عملیات Create و Edit از یک مدل (DTO) استفاده خواهیم کرد، شاید لازم باشد اعتبارسنجی خاصی را فقط در زمان ویرایش لازم داشته باشیم، که در این صورت می‌توان از امکانات 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
کتابخانه کمکی
TrackableEntities.Core
کدهای کامل مطلب جاری را از اینجا می‌توانید دریافت کنید.

dotnettips

‫به روز رسانی اطلاعات Master-Detail یا Master-Detail-DetailOfDetail با استفاده از EF Core

حتما بخوانید: سایر مطالب گروه آموزش

برای مشاهده فوری اخبار و مطالب در کانال تلگرام ما عضو شوید!


روی کلید واژه مرتبط کلیک کنید
منتخب امروز

بیشترین بازدید یک ساعت گذشته


لخته خون گوشتی شبیه جگر در پریودی چیست و چه علتی دارد‌؟ +‌روش درمان