پیشنیاز: «تنظیمات ارث بری کلاسها در EF Code first»
در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، میتوانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطهی TPH که در آن تمام کلاسهای سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت میشوند، پشتیبانی میشود. سایر روشهای ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارشهای پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارشهای بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC
طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط
مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاسهای دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیتهای برنامه، جهت یافتن سریع آنها توسط Reflection (برای مثال افزودن خودکار موجودیتها به Context برنامه با یافتن تمام کلاسهایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
و پس از آن هر موجودیت برنامه به این شکل خلاصه شده و نشانه گذاری میشود:
حالت پیش فرض ارث بریها در EF Core، همان حالت TPH است که در ادامه توضیح داده خواهد شد. اما هدف ما در اینجا تنظیم هیچکدام از حالتهای ارث بری نیست. هدف صرفا کاهش تعداد فیلدهای تکراری ذکر شدهی در کلاسهای دومین برنامه است. بنابراین جهت لغو تنظیمات ارث بری EF Core، نیاز است یک چنین تنظیمی را انجام داد:
با فراخوانی متد Ignore بر روی کلاس پایهی تهیه شده، این کلاس دیگر وارد تنظیمات روابط EF Core نمیشود و در جداول نهایی، فیلدهای آن به صورت معمول در کنار سایر فیلدهای جداول مشتق شدهی از آنها قرار میگیرند.
مشکل! اگر بر روی کلاس پایهی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاسهای مشتق شدهی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
کاری که در اینجا انجام شده، تنظیم خاصیت DateAdded کلاس پایه، به حالت ValueGeneratedOnAdd و تنظیم خاصیت DateUpdated کلاس پایه به حالت ValueGeneratedOnAddOrUpdate با مقدار پیش فرض getdate است. این مفاهیم را در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکسها» پیشتر بررسی کردیم.
خلاصهی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیتهای خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی میشود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی میشود.
تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار میکند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شدهاست و نه سروری که برنامهی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمتهای بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاسهای پایه و مقدار دهی خواص ویژهی آنها مطرح میکنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.
بررسی تنظیمات رابطهی Table per Hierarchy یا TPH
رابطهی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاسهای دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی میگردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه میشود. از فیلد Discriminator جهت درج نام کلاسهای متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا میداند که چگونه باید خواص متناظر با کلاسهای مختلف را مقدار دهی کند و نوع ردیف درج شدهی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاسها است و نیاز به هیچگونه تنظیم اضافهتری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج میشوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر میباشد:
در اینجا نام فیلد Discriminator، به blog_type، مقدار نوع متناظر با کلاس Blog، به blog_base و مقدار نوع متناظر با کلاس RssBlog، به blog_rss تنظیم شدهاست.
اگر این تنظیمات سفارشی صورت نگیرند، از نامهای پیش فرض نوعها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:
برای کوئری نوشتن در این حالت میتوان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
در مطلب پیشنیاز فوق، تنظیمات روابط ارث بری را تا EF 6.x، میتوانید مطالعه کنید. در EF Core 1.0 RTM، فقط رابطهی TPH که در آن تمام کلاسهای سلسه مراتب ارث بری، به یک جدول در بانک اطلاعاتی نگاشت میشوند، پشتیبانی میشود. سایر روشهای ارث بری که در EF 6.x وجود دارند، مانند TPT و TPC، قرار است به نگارشهای پس از 1.0 RTM آن اضافه شوند:
- لیست مواردی که قرار است به نگارشهای بعدی اضافه شوند
- پیگیری وضعیت پیاده سازی TPT
- پیگیری وضعیت پیاده سازی TPC
طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط
مرسوم است که یک کلاس ویژه را به نام BaseEntity، به شکل زیر تعریف کنند؛ که اهدف آن حداقل سه مورد ذیل است:
الف) کاهش ذکر فیلدهای تکراری در سایر کلاسهای دومین برنامه، مانند فیلد Id
ب) نشانه گذاری موجودیتهای برنامه، جهت یافتن سریع آنها توسط Reflection (برای مثال افزودن خودکار موجودیتها به Context برنامه با یافتن تمام کلاسهایی که از نوع BaseEntity هستند)
ج) مقدار دهی خودکار یک سری از فیلدهای ویژه، مانند زمان افزوده شدن رکورد و آخرین زمان ویرایش شدن رکورد و امثال آن
public class BaseEntity { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } }
public class Person : BaseEntity { public string FirstName { get; set; } public string LastName { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>();
مشکل! اگر بر روی کلاس پایهی تعریف شده تنظیماتی را اعمال کنید (هر نوع تنظیمی را)، با توجه به فراخوانی متد Ignore، این تنظیمات نیز ندید گرفته خواهند شد.
اگر علاقمند بودید تا این تنظیمات را به تمام کلاسهای مشتق شدهی از BaseEntity به صورت خودکار اعمال کنید، روش کار به صورت ذیل است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>(); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var dateAddedProperty = entityType.FindProperty("DateAdded"); dateAddedProperty.ValueGenerated = ValueGenerated.OnAdd; dateAddedProperty.SqlServer().DefaultValueSql = "getdate()"; var dateUpdatedProperty = entityType.FindProperty("DateUpdated"); dateUpdatedProperty.ValueGenerated = ValueGenerated.OnAddOrUpdate; dateUpdatedProperty.SqlServer().ComputedColumnSql = "getdate()"; }
خلاصهی آن نیز به این صورت است:
الف) نیازی نیست تا در حین ثبت اطلاعات موجودیتهای خود، فیلدهای DateAdded و یا DateUpdated را مقدار دهی کنید.
ب) فیلد DateAdded فقط در زمان اولین بار ثبت در بانک اطلاعاتی، به صورت خودکار توسط متد getdate مقدار دهی میشود.
ج) فیلد DateUpdated در هر بار فراخوانی متد SaveChanges (یعنی در هر دو حالت ثبت و یا به روز رسانی) به صورت خودکار توسط متد getdate مقدار دهی میشود.
تذکر! بدیهی است متد getdate، یک متد بومی سمت SQL Server است و این روش خاص تعیین مقدار پیش فرض فیلدها، فقط با SQL Server کار میکند. همچنین این getdate، به معنای دریافت تاریخ و ساعت سروری است که SQL Server بر روی آن نصب شدهاست و نه سروری که برنامهی وب شما در آن قرار دارد و برنامه کوچکترین دخالتی را در مقدار دهی این مقادیر نخواهد داشت.
در قسمتهای بعدی که مباحث Tracking را بررسی خواهیم کرد، روش دیگری را برای طراحی کلاسهای پایه و مقدار دهی خواص ویژهی آنها مطرح میکنیم که مستقل است از نوع بانک اطلاعاتی مورد استفاده.
بررسی تنظیمات رابطهی Table per Hierarchy یا TPH
رابطهی TPH یا تشکیل یک جدول بانک اطلاعاتی، به ازای تمام کلاسهای دخیل در سلسه مراتب ارث بری تعریف شده، بسیار شبیه است به حالت BaseEntity فوق که در آن نیز ارث بری تعریف شده، در نهایت منجر به تشکیل یک جدول، در سمت بانک اطلاعاتی میگردد. با این تفاوت که در حالت TPH، فیلد جدیدی نیز به نام Discriminator، به تعریف نهایی جدول ایجاد شده، اضافه میشود. از فیلد Discriminator جهت درج نام کلاسهای متناظر با هر رکورد، استفاده شده است. به این ترتیب EF در حین کار با اشیاء، دقیقا میداند که چگونه باید خواص متناظر با کلاسهای مختلف را مقدار دهی کند و نوع ردیف درج شدهی در بانک اطلاعاتی چیست؟
باید دقت داشت که تنظیمات TPH، شیوه برخورد پیش فرض EF Core با ارث بری کلاسها است و نیاز به هیچگونه تنظیم اضافهتری را ندارد. اما اگر علاقمند بودید تا نام فیلد خودکار Discriminator و مقادیری را که در آن درج میشوند، سفارشی سازی کنید، روش کار صرفا توسط Fluent API میسر است و به صورت زیر میباشد:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class RssBlog : Blog { public string RssUrl { get; set; } } class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator<string>("blog_type") .HasValue<Blog>("blog_base") .HasValue<RssBlog>("blog_rss"); } }
اگر این تنظیمات سفارشی صورت نگیرند، از نامهای پیش فرض نوعها برای مقدار دهی ستون Discriminator، مانند تصویر ذیل استفاده خواهد شد:
برای کوئری نوشتن در این حالت میتوان از متد الحاقی OfType جهت فیلتر کردن اطلاعات بر اساس کلاسی خاص، کمک گرفت:
var blog1 = db.Blogs.OfType<RssBlog>().FirstOrDefault(x => x.RssUrl == "………");