در مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانکهای اطلاعاتی از پیش موجود»، نحوهی مهندسی معکوس ساختار جداول و ارتباطات یک بانک اطلاعاتی از پیش موجود را به روش Code First بررسی کردیم. با توجه به رسمی بودن این ابزار، میتوان از آن برای یافتن معادلهای سمت بانک اطلاعاتی، در EF Core نیز استفاده کرد. برای مثال بررسی کرد، درک EF Core از بانک اطلاعاتی طراحی شده چیست و هر چند در آن مطلب عنوان شد که میتوان با پارامتر data-annotations-- ، خروجی نهایی را بر اساس روش data-annotations، بجای Fluent API به دست آورد، اما در مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکسها» مشاهده کردیم که بسیاری از تنظیمات پیشرفتهی EF Core، اساسا معادل data-annotation ایی ندارند. بنابراین بهتر است این پارامتر را فعال سازی نکنید.
تنظیمات روابط یک به چند در EF Core
همان اسکریپت ابتدای مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانکهای اطلاعاتی از پیش موجود» را درنظر بگیرید. رابطهی تعریف شدهی در آن از نوع one-to-many است: یک بلاگ که میتواند چندین مطلب را داشته باشد.
اگر EF Core را وادار به تولید نگاشتهای Code First معادل آن کنیم، به این خروجیها خواهیم رسید:
الف) با استفاده از روش Fluent API
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
با خروجی:
نحوهی تشخیص خودکار روابط
EF Core به صورت پیش فرض، روابط را بر اساس ارجاعات بین کلاسها تشخیص میدهد. در اینجا به خاصیت Blog نام navigation property را میدهند:
و به خاصیت Post نیز Collection navigation property میگویند:
در اینجا اگر تنها دو navigation property، در کلاسهای به هم مرتبط شده، یافت شوند، به صورت خودکار به عنوان دو سر رابطه تنظیم میشوند. اگر بیشتر از یک navigation property در کلاسی وجود داشت، هیچ رابطهای به صورت خودکار تشکیل نشده و باید ابتدا و انتهای روابط را به صورت دستی مشخص نمود.
نحوهی تشخیص خودکار کلیدهای خارجی
اگر در یک طرف رابطهی تشخیص داده شده، خاصیتی با یکی از سه نام زیر وجود داشت:
آنگاه این خاصیت به صورت خودکار به عنوان کلید خارجی تنظیم میشود. در رابطهی فوق Blog از نوع principal است (پدر رابطه) و Post از نوع dependent (فرزند رابطه).
برای مثال در رابطهی فوق، نام خاصیت BlogId دقیقا بر اساس همان الگوی <primary key property name> طرف دیگر رابطهاست:
بنابراین به صورت خودکار به عنوان کلید خارجی درنظر گرفته میشود.
تا اینجا اگر مطلب را دنبال کرده باشید به این نتیجه خواهید رسید که دو کلاس فوق، اساسا نیازی به هیچ نوع تنظیم Fluent و یا Data annotations ایی برای برقراری ارتباط یک به چند ندارند. چون روابط بین آنها بر اساس خواص راهبری (navigation property) و همچنین الگوی <primary key property name>، به صورت خودکار قابل تشخیص و تنظیم است. به علاوه ... در هر طرف رابطه، فقط یک navigation property وجود دارد و نیازی به تنظیم دستی سر دیگر رابطه نیست.
استفاده از Fluent API برای تنظیم رابطهی One-to-Many
در تنظیمات فوق، در متد OnModelCreating، ذکر صریح این روابط را صرفا جهت از بین بردن هرگونه ابهامی مشاهده میکنید:
از هر طرفی که شروع میکنید، متدهای HasOne و یا HasMany، مشخص کنندهی navigation property هستند که در سمت موجودیت معرفی شده قرار دارند. در اینجا چون کار با موجودیت Post شروع شدهاست، متد HasOne به خاصیت راهبری در همان سمت و به خاصیت Blog آن اشاره میکند.
مرحلهی بعد، مشخص کردن سر دیگر رابطه (inverse navigation) است. اینکار توسط یکی از متدهای WithOne و یا WithMany انجام میشود.
متدهایی که اسامی فرد دارند مانند HasOne/WithOne به یک navigation property ساده اشاره میکنند.
متدهایی که اسامی جمع دارند مانند HasMany/WithMany به collection navigation properties اشاره خواهند کرد.
متد HasForeignKey نیز برای ذکر صریح کلید خارجی بکار رفتهاست.
ب) با استفاده از روش data-annotations
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
با خروجی:
همانطور که در توضیحات روش Fluent API عنوان شد، این مدل خاص، چون دقیقا بر اساس پیش فرضهای EF Core طراحی شدهاست، نیازی به هیچگونه تنظیم اضافهتری ندارد. اما اگر کلید خارجی، مطابق سه الگویی که عنوان شد، قابل تشخیص نباشد، باید آنرا در روش data annotations توسط ویژگی ForeignKey، به نحو صریحی مشخص کرد:
همچنین اگر بیش از یک خاصیت راهبری (navigation property) وجود داشت، ذکر InverseProperty نیز ضروری است تا مشخص شود سر دیگر این رابطه دقیقا کدام است.
در این حالت (داشتن بیش از یک خاصیت راهبری)، باید ویژگی InverseProperty را نیز به سر دوم رابطه، اعمال کرد.
مطالب تکمیلی
علت virtual بودن خواص راهبری تولید شده
اگر دقت کنید، EF Core کدی را که تولید کردهاست، به همراه خاصیتهایی virtual است:
در اینجا تمام خاصیتهای راهبری virtual تعریف شدهاند. علت آن، به پیاده سازی مباحث AOPبر میگردد. زمانیکه خاصیتی به صورت virtual تعریف میشود، EF core میتواند آنرا توسط یک شیء پروکسی شفاف احاطه کند. این پروکسیها دو هدف را دنبال میکند:
الف) پیاده سازی lazy loading (بارگذاری خودکار اعضای مرتبط (همان خواص راهبری) با اولین دسترسی به آنها)
ب) پیاده سازی change tracking
مبحث lazy loading فعلا در EF Core 1.0 پشتیبانی نمیشود. اما change tracking آن فعال است.
بنابراین اگر مشاهده کردید خواص راهبری به صورت virtual تعریف شدهاند، علت آن فعال سازی lazy loading است و اگر سایر خواص به صورت virtual تعریف شدهاند، هدف اصلی آن بهبود عملکرد سیستم change tracking است.
همچنین اگر دقت کرده باشید، نوع مجموعهها نیز ICollection ذکر شدهاست. این مورد نیز یکی دیگر از پیش فرضهای توکار EF Core است؛ در جهت تشکیل پروکسیها بر روی خواص راهبری مجموعهای (علاوه بر virtual تعریف کردن آنها). عنوان شدهاست که اگر برای مثال از List استفاده کنید (پیاده سازی اینترفیس) یا هر اینترفیس دیگری که از ICollection مشتق شدهاست، این پروکسیها تشکیل نخواهند شد.
واکشی اعضای به هم مرتبط
همانطور که عنوان شد، نگارش اول EF Core برخلاف EF 6.x از Lazy loading پشتیبانی نمیکند. البته این مساله در کل مورد مثبتی است؛ خصوصا در برنامههای وب! چون استفادهی نادرست از Lazy loading که به select n+1 نیز مشهور است، سبب رفت و برگشتهای بیشماری به بانک اطلاعاتی میشود و عموم برنامه نویسهای وب باید مدام توسط برنامههای Profiler بررسی کنند که آیا این مساله رخ دادهاست یا خیر. فعلا EF Core از این مشکل در امان است!
اما ... اگر به روش کار EF 6.x عادت کرده باشید، قطعه کد ذیل:
چنین خطایی را صادر میکند:
علت اینجا است که چون Lazy loading غیرفعال است (هنوز در EF Core 1.0 پیاده سازی نشدهاست)، اولین دسترسی به شیء Blog، سبب وهله سازی خودکار آن نشده و این شیء نال است. به همین جهت استثنای فوق را مشاهده میکنیم.
برای رفع این مشکل باید توسط متد Include، سبب لغو عملیات Lazy loading و واکشی صریح Blog مرتبط شویم که اصطلاحا به آن eager loading میگویند:
نکتهای در مورد سطوح بارگذاری اعضای به هم مرتبط در EF Core
متد Include ایی را که تا اینجا مشاهده کردید، با EF 6.x تفاوتی ندارد. برای مثال اگر شیء Blog حاوی خواص راهبری Posts و همچنین Owner باشد، برای بارگذاری این اعضای مرتبط، میتوان همانند قبل، متدهای Include را پشت سر هم ذکر کرد:
اما فرض کنید خاصیت Post، دارای یک خاصیت راهبری دیگری به نام Author نیز باشد و میخواهیم این خاصیت هم بارگذاری شود:
روش انجام چنین کاری در EF Core، توسط متد الحاقی جدید ThenInclude است. ابتدا لیست Blogها عنوان شدهاست. سپس در این لیست علاقمند به واکشی تمام مطالب این بلاگها هم بودهایم. به علاوه در این مطالب، نیاز است خاصیت Author آنها نیز از پیش مقدار دهی شده و قابل دسترسی باشد. به همین جهت برای دسترسی به چندین سطح مختلف از متد ThenInclude کمک گرفته شدهاست.
همچنین در اینجا امکان ذکر زنجیروار متدهای ThenInclude هم هست:
در این مثال یک سطح دیگر جلو رفته و شیء Photo مربوط به شیء Author را هم واکشی کردهایم.
به علاوه امکان ذکر چندین ریشه و چندین زیر ریشه هم وجود دارند:
یک نکته:متد Include تنها زمانی درنظر گرفته خواهد شد که نوع خروجی نهایی کوئری، دقیقا از نوع موجودیتی باشد که با آن شروع به کار کردهایم. برای مثال اگر در این بین یک Select اضافه شود و فقط تنها تعدادی از خواص Blog واکشی شوند، از تمام Includeهای ذکر شده صرفنظر میشود؛ مانند کوئری ذیل:
تنظیمات حذف آبشاری در رابطهی one-to-many
زمانیکه در رابطهی one-to-many قسمت principal (والد رابطه) و یا همان Blog در مثال جاری حذف میشود، سه اتفاق برای فرزندان آن میسر خواهند بود:
الف) Cascade : در این حالت ردیفهای فرزندان وابسته نیز حذف خواهند شد.
باید دقت داشت که حالت Cascade فقط برای موجودیتهایی اعمال میشود که توسط Context بارگذاری شده و در آن وجود دارند. اگر میخواهید سایر موجودیتهای مرتبط نیز با این روش حذف شوند، باید در سمت دیتابیس نیز تنظیماتی مانند ON DELETE CASCADE زیر نیز وجود داشته باشند:
و اگر با EF Core بانک اطلاعاتی خود را ایجاد میکنید (مباحث مهاجرتها)، این تنظیم به صورت خودکار اعمال خواهد شد؛ اگر DeleteBehavior را به نحو ذیل مشخص کرده باشید:
ب) SetNull: در این حالت فرزندان وابسته حذف نمیشوند و تنها کلید خارجی آنها به نال تنظیم میشود.
ج) Restrict: هیچ تغییری بر روی فرزندان رابطه رخ نمیدهد.
یک نکته:به صورت پیش فرض اگر رابطهی one-to-many، به Required تنظیم شود، حالت حذف آن cascade خواهد بود. در غیراینصورت برای حالتهای Optional، حالت SetNull تنظیم میگردد:
در اینجا ذکر صریح متد IsRequired به این معنا است که مقدار دهی کلید خارجی سر دیگر رابطه، اجباری است.
به علاوه باید دقت داشت، همان مباحث «تعیین اجباری بودن یا نبودن ستونها در EF Core» در قسمت قبل، در اینجا هم صادق است. برای مثال چون BlogId (کلید خارجی در کلاس Post) از نوع int است و نال پذیر نیست، بنابراین از دیدگاه EF Core یک فیلد اجباری درنظر گرفته میشود. به همین جهت است که در کدهای تولید شدهی توسط EF Core در ابتدای بحث، ذکر متد IsRequired و یا OnDelete را مشاهده نمیکنید.
بنابراین اگر میخواهید حالت SetNull را فعال کنید، باید این کلید خارجی را نیز نال پذیر و به صورت int? BlogId ذکر کنید تا optional درنظر گرفته شود.
تنظیمات روابط یک به چند در EF Core
همان اسکریپت ابتدای مطلب «شروع به کار با EF Core 1.0 - قسمت 4 - کار با بانکهای اطلاعاتی از پیش موجود» را درنظر بگیرید. رابطهی تعریف شدهی در آن از نوع one-to-many است: یک بلاگ که میتواند چندین مطلب را داشته باشد.
اگر EF Core را وادار به تولید نگاشتهای Code First معادل آن کنیم، به این خروجیها خواهیم رسید:
الف) با استفاده از روش Fluent API
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose
using System; using System.Collections.Generic; namespace Core1RtmEmptyTest.Entities { public partial class Blog { public Blog() { Post = new HashSet<Post>(); } public int BlogId { get; set; } public string Url { get; set; } public virtual ICollection<Post> Post { get; set; } } }
using System; using System.Collections.Generic; namespace Core1RtmEmptyTest.Entities { public partial class Post { public int PostId { get; set; } public string Content { get; set; } public string Title { get; set; } public virtual Blog Blog { get; set; } public int BlogId { get; set; } } }
using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; namespace Core1RtmEmptyTest.Entities { public partial class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>(entity => { entity.Property(e => e.Url).IsRequired(); }); modelBuilder.Entity<Post>(entity => { entity.HasOne(d => d.Blog) .WithMany(p => p.Post) .HasForeignKey(d => d.BlogId); }); } public virtual DbSet<Blog> Blog { get; set; } public virtual DbSet<Post> Post { get; set; } } }
نحوهی تشخیص خودکار روابط
EF Core به صورت پیش فرض، روابط را بر اساس ارجاعات بین کلاسها تشخیص میدهد. در اینجا به خاصیت Blog نام navigation property را میدهند:
public virtual Blog Blog { get; set; }
public virtual ICollection<Post> Post { get; set; }
نحوهی تشخیص خودکار کلیدهای خارجی
اگر در یک طرف رابطهی تشخیص داده شده، خاصیتی با یکی از سه نام زیر وجود داشت:
<primary key property name><navigation property name><primary key property name><principal entity name><primary key property name>
برای مثال در رابطهی فوق، نام خاصیت BlogId دقیقا بر اساس همان الگوی <primary key property name> طرف دیگر رابطهاست:
public virtual Blog Blog { get; set; } public int BlogId { get; set; }
تا اینجا اگر مطلب را دنبال کرده باشید به این نتیجه خواهید رسید که دو کلاس فوق، اساسا نیازی به هیچ نوع تنظیم Fluent و یا Data annotations ایی برای برقراری ارتباط یک به چند ندارند. چون روابط بین آنها بر اساس خواص راهبری (navigation property) و همچنین الگوی <primary key property name>، به صورت خودکار قابل تشخیص و تنظیم است. به علاوه ... در هر طرف رابطه، فقط یک navigation property وجود دارد و نیازی به تنظیم دستی سر دیگر رابطه نیست.
استفاده از Fluent API برای تنظیم رابطهی One-to-Many
در تنظیمات فوق، در متد OnModelCreating، ذکر صریح این روابط را صرفا جهت از بین بردن هرگونه ابهامی مشاهده میکنید:
modelBuilder.Entity<Post>(entity => { entity.HasOne(d => d.Blog) .WithMany(p => p.Post) .HasForeignKey(d => d.BlogId); });
مرحلهی بعد، مشخص کردن سر دیگر رابطه (inverse navigation) است. اینکار توسط یکی از متدهای WithOne و یا WithMany انجام میشود.
متدهایی که اسامی فرد دارند مانند HasOne/WithOne به یک navigation property ساده اشاره میکنند.
متدهایی که اسامی جمع دارند مانند HasMany/WithMany به collection navigation properties اشاره خواهند کرد.
متد HasForeignKey نیز برای ذکر صریح کلید خارجی بکار رفتهاست.
ب) با استفاده از روش data-annotations
دستور استفاده شده برای مهندسی معکوس بانک اطلاعاتی نمونه:
dotnet ef dbcontext scaffold "Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true" Microsoft.EntityFrameworkCore.SqlServer -o Entities --context MyDBDataContext --verbose -a
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Core1RtmEmptyTest.Entities { public partial class Blog { public Blog() { Post = new HashSet<Post>(); } public int BlogId { get; set; } [Required] public string Url { get; set; } [InverseProperty("Blog")] public virtual ICollection<Post> Post { get; set; } } }
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Core1RtmEmptyTest.Entities { public partial class Post { public int PostId { get; set; } public string Content { get; set; } public string Title { get; set; } [ForeignKey("BlogId")] [InverseProperty("Post")] public virtual Blog Blog { get; set; } public int BlogId { get; set; } } }
using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; namespace Core1RtmEmptyTest.Entities { public partial class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=BloggingCore2016;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { } public virtual DbSet<Blog> Blog { get; set; } public virtual DbSet<Post> Post { get; set; } } }
[ForeignKey("BlogId")] [InverseProperty("Post")] public virtual Blog Blog { get; set; } public int BlogId { get; set; }
در این حالت (داشتن بیش از یک خاصیت راهبری)، باید ویژگی InverseProperty را نیز به سر دوم رابطه، اعمال کرد.
[InverseProperty("Blog")] public virtual ICollection<Post> Post { get; set; }
مطالب تکمیلی
علت virtual بودن خواص راهبری تولید شده
اگر دقت کنید، EF Core کدی را که تولید کردهاست، به همراه خاصیتهایی virtual است:
public virtual Blog Blog { get; set; }
الف) پیاده سازی lazy loading (بارگذاری خودکار اعضای مرتبط (همان خواص راهبری) با اولین دسترسی به آنها)
ب) پیاده سازی change tracking
مبحث lazy loading فعلا در EF Core 1.0 پشتیبانی نمیشود. اما change tracking آن فعال است.
بنابراین اگر مشاهده کردید خواص راهبری به صورت virtual تعریف شدهاند، علت آن فعال سازی lazy loading است و اگر سایر خواص به صورت virtual تعریف شدهاند، هدف اصلی آن بهبود عملکرد سیستم change tracking است.
همچنین اگر دقت کرده باشید، نوع مجموعهها نیز ICollection ذکر شدهاست. این مورد نیز یکی دیگر از پیش فرضهای توکار EF Core است؛ در جهت تشکیل پروکسیها بر روی خواص راهبری مجموعهای (علاوه بر virtual تعریف کردن آنها). عنوان شدهاست که اگر برای مثال از List استفاده کنید (پیاده سازی اینترفیس) یا هر اینترفیس دیگری که از ICollection مشتق شدهاست، این پروکسیها تشکیل نخواهند شد.
واکشی اعضای به هم مرتبط
همانطور که عنوان شد، نگارش اول EF Core برخلاف EF 6.x از Lazy loading پشتیبانی نمیکند. البته این مساله در کل مورد مثبتی است؛ خصوصا در برنامههای وب! چون استفادهی نادرست از Lazy loading که به select n+1 نیز مشهور است، سبب رفت و برگشتهای بیشماری به بانک اطلاعاتی میشود و عموم برنامه نویسهای وب باید مدام توسط برنامههای Profiler بررسی کنند که آیا این مساله رخ دادهاست یا خیر. فعلا EF Core از این مشکل در امان است!
اما ... اگر به روش کار EF 6.x عادت کرده باشید، قطعه کد ذیل:
var firstPost = context.Post.First(); Console.WriteLine(firstPost.Blog.Url);
System.NullReferenceException Object reference not set to an instance of an object.
برای رفع این مشکل باید توسط متد Include، سبب لغو عملیات Lazy loading و واکشی صریح Blog مرتبط شویم که اصطلاحا به آن eager loading میگویند:
var firstPost = context.Post.Include(x => x.Blog).First(); Console.WriteLine(firstPost.Blog.Url);
نکتهای در مورد سطوح بارگذاری اعضای به هم مرتبط در EF Core
متد Include ایی را که تا اینجا مشاهده کردید، با EF 6.x تفاوتی ندارد. برای مثال اگر شیء Blog حاوی خواص راهبری Posts و همچنین Owner باشد، برای بارگذاری این اعضای مرتبط، میتوان همانند قبل، متدهای Include را پشت سر هم ذکر کرد:
var blogs = context.Blogs .Include(blog => blog.Posts) .Include(blog => blog.Owner) .ToList();
var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ToList();
همچنین در اینجا امکان ذکر زنجیروار متدهای ThenInclude هم هست:
var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ThenInclude(author => author.Photo) .ToList();
به علاوه امکان ذکر چندین ریشه و چندین زیر ریشه هم وجود دارند:
var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ThenInclude(author => author.Photo) .Include(blog => blog.Owner) .ThenInclude(owner => owner.Photo) .ToList();
یک نکته:متد Include تنها زمانی درنظر گرفته خواهد شد که نوع خروجی نهایی کوئری، دقیقا از نوع موجودیتی باشد که با آن شروع به کار کردهایم. برای مثال اگر در این بین یک Select اضافه شود و فقط تنها تعدادی از خواص Blog واکشی شوند، از تمام Includeهای ذکر شده صرفنظر میشود؛ مانند کوئری ذیل:
var blogs = context.Blogs .Include(blog => blog.Posts) .Select(blog => new { Id = blog.BlogId, Url = blog.Url }) .ToList();
تنظیمات حذف آبشاری در رابطهی one-to-many
زمانیکه در رابطهی one-to-many قسمت principal (والد رابطه) و یا همان Blog در مثال جاری حذف میشود، سه اتفاق برای فرزندان آن میسر خواهند بود:
الف) Cascade : در این حالت ردیفهای فرزندان وابسته نیز حذف خواهند شد.
باید دقت داشت که حالت Cascade فقط برای موجودیتهایی اعمال میشود که توسط Context بارگذاری شده و در آن وجود دارند. اگر میخواهید سایر موجودیتهای مرتبط نیز با این روش حذف شوند، باید در سمت دیتابیس نیز تنظیماتی مانند ON DELETE CASCADE زیر نیز وجود داشته باشند:
CONSTRAINT [FK_Post_Blog_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blog] ([BlogId]) ON DELETE CASCADE
modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .OnDelete(DeleteBehavior.Cascade);
ج) Restrict: هیچ تغییری بر روی فرزندان رابطه رخ نمیدهد.
یک نکته:به صورت پیش فرض اگر رابطهی one-to-many، به Required تنظیم شود، حالت حذف آن cascade خواهد بود. در غیراینصورت برای حالتهای Optional، حالت SetNull تنظیم میگردد:
modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .IsRequired();
به علاوه باید دقت داشت، همان مباحث «تعیین اجباری بودن یا نبودن ستونها در EF Core» در قسمت قبل، در اینجا هم صادق است. برای مثال چون BlogId (کلید خارجی در کلاس Post) از نوع int است و نال پذیر نیست، بنابراین از دیدگاه EF Core یک فیلد اجباری درنظر گرفته میشود. به همین جهت است که در کدهای تولید شدهی توسط EF Core در ابتدای بحث، ذکر متد IsRequired و یا OnDelete را مشاهده نمیکنید.
بنابراین اگر میخواهید حالت SetNull را فعال کنید، باید این کلید خارجی را نیز نال پذیر و به صورت int? BlogId ذکر کنید تا optional درنظر گرفته شود.