یکی از چالشهایی که در طراحی زیرساخت برای 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();
}
با خروجی زیر:
برای بحث اعتبارسنجی هم میتوان به شکل زیر عمل کرد:
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
مطالب پیشنهادی
با کمتر از 10 دلار اسمارتفون اندرویدی بخرید
تولید کفش از زبالههای دریایی با بهرهگیری از پرینت سه بعدی
ساخت مجسمههایی از حیوانات با ضایعات فلزی
با استفاده از کیف گوگل و فقط با یک شماره تلفن پول ارسال کنید
بازگرداندن انسانها از مرگ با هوش مصنوعی
از طریق اکوآمازون با اتومبیلهای فورد صحبت کنید
با هدبند 3RDi از وقایع زندگیتان عکس بگیرید
اسمارتفونی از شیائومی با نمایشگر 5 اینچ و پردازندهی 8 هستهای از تنا مجوز گرفت
خواستگاری دختر جوان با لباس عروس از فوتبالیست معروف!+ تصاویر
سرقت از بانک با نقاب ترامپ در ایتالیا! +فیلم
اسمارتفون میان ردهای از شیائومی با پردازنده 10 هسته ای از تنا گواهی گرفت
فناوری تصویربرداری از کل بدن با ردیابی جریان خون
جهان را با چشم کوسهها ببینید؛ ساخت دوربینی با الهام از چشم کوسه
با رعایت این موارد از پیری پوست با بالا رفتن سن جلوگیری کنید
شارژ گوشی با گوشی دیگر از طریق کابل OTG
چگونه تحریم های پلی استور را از طریق تلگرام دور بزنیم؟
چگونه از طریق آیدی تلگرام با دیگران چت کنیم؟
گوشی دیگری از سامسونگ با اسنپدرگون 620 مورد محک قرار گرفت
فاز جدیدی از کربن با قابلیت تبدیل سریع به الماس
ساخت کامپیوتر ارگانیک با استفاده از مغز موش