فرشید عزیزی
فرشید عزیزی
خواندن ۱۵ دقیقه·۲ سال پیش

دوره آموزشی Entity FrameWork Core - قسمت دهم

دوره آموزشی Entity FrameWork Core - قسمت نهم

روابط(Relationships) :

یک رابطه(Relationship) نحوه ارتباط دو موجودیت را با یکدیگر تعریف می کند. در یک پایگاه داده رابطه ای، این با یک محدودیت کلید خارجی(foreign key constraint) نشان داده می شود.

یادآوری : Constraint های sql شامل موارد زیر میشوند :
* NOT NULL(مشخص میکند که فیلد، مقادیر null را نمی پذیرد .)
* UNIQUE(مقدار فیلد می بایست در تمامی رکورد های یک جدول منحصر به فرد باشد.)
* PRIMARY KEY(کلید اصلی یک constraint است هیچ دو رکوردی در جدول دارای مقدار یکسان در کلید اصلی نیستند .)
* FOREIGN KEY(رابطه بین جداول را مشخص می کند هنگامی که رابطه والد و فرزندی بین رکورد های دو جدول برقرار است foreign key constraint اجاره نمیدهد رکورد والدی حذف شود که دارای فرزند است و یا اینکه رکورد فرزندی درج شود بدون اینکه فاقد کلید اصلی والد خود باشد.)
* CHECK(جهت محدود کردن مقادیر ورودی در فیلد.)
* DEFAULT(تعیین مقدار پیشفرض برای فیلد. اگر مقداری در فیلد وارد نشود این مقدار بصورت خودکار در فیلد درج می شود)


تعریف اصطلاحات(Definition of terms) :

تعدادی اصطلاح برای توصیف روابط وجود دارد:

  • موجودیت وابسته(Dependent entity): موجودیتی است که دارای ویژگی های کلید خارجی(foreign key) است. گاهی اوقات به عنوان "فرزند(child)" رابطه نامیده می شود.
  • موجودیت اصلی(Principal entity): موجودیتی است که دارای ویژگی های کلید اصلی/جایگزین است. گاهی اوقات به عنوان "والد(parent)" رابطه نامیده می شود.
  • کلید اصلی(Principal key): ویژگی هایی که به طور منحصر به فردT موجودیت اصلی را شناسایی می کنند. این ممکن است کلید اصلی(primary key) یا یک کلید جایگزین(alternate key) باشد.
  • کلید خارجی(Foreign key): خواص موجود در موجودیت وابسته(Dependent entity) که برای ذخیره مقادیر کلید اصلی موجودیت مرتبط(Principal entity) استفاده می شود.
  • پراپرتی ناوبری(Navigation property): پراپرتی تعریف شده بر روی موجودیت اصلی و/یا وابسته که به موجودیت مرتبط ارجاع می دهد.
    1) Collection navigation property : یک پراپرتی ناوبری که حاوی ارجاع به مجموعه ای از موجودیت های مرتبط است.
    2) Reference navigation property : یک پراپرتی ناوبری که یک ارجاع به یک موجودیت مرتبط دارد.
    3) Inverse navigation property: هنگام بحث در مورد یک پراپرتی ناوبری خاص، این اصطلاح به پراپرتی ناوبری در انتهای دیگر رابطه اشاره دارد.
  • رابطه خودارجاعی(Self-referencing relationship): رابطه ای که در آن نوع وابسته و موجودیت اصلی یکسان هستند.

قطعه کد زیر رابطه یک به چند بین blog و post را نشان می دهد :

public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } }
  • موجودیت post : یک موجودیت وابسته (Dependent entity) است.
  • موجودیت blog : یک موجودیت اصلی(Principal entity) است.
  • پراپرتی Blog.BlogId کلید اصلی(principal key) است.
  • پراپرتی Post.BlogId کلید خارجی (Foreign key) است.
  • پراپرتی Post.Blog یک پراپرتی ناوبری مرجع(Reference navigation property) است.
  • پراپرتی Post.Blog نیز یک پراپرتی ناوبری معکوس(Inverse navigation property) Blog.Posts است (و بالعکس)
  • پراپرتی Blog.Posts یک پراپرتی ناوبری مجموعه ای(Collection navigation property) است.

قراردادها (Conventions) :

به طور پیش فرض، زمانی که یک پراپرتی ناوبری در یک entity type کشف شود، یک رابطه ایجاد می شود. یک پراپرتی در صورتی یک پراپرتی ناوبری در نظر گرفته می شود که نوع مورد نظر به عنوان یک scalar type توسط ارائه دهنده پایگاه داده فعلی نگاشت نشود.

اما scalar type چیست ؟
پراپرتی های نوع اولیه(primitive type properties) را ویژگی های اسکالر می نامند. هر ویژگی اسکالر به ستونی در جدول پایگاه داده نگاشت می شود که یک داده واقعی را ذخیره می کند. به عنوان مثال StudentID، StudentName، DateOfBirth، Photo، Height، Weight ویژگی های اسکالر در کلاس موجودیت Student هستند.

روابطی که طبق قرارداد کشف می شوند همیشه کلید اصلی موجودیت اصلی را هدف قرار می دهند. برای هدف قرار دادن یک کلید جایگزین(alternate key)، پیکربندی اضافی باید با استفاده از Fluent API انجام شود.

روابط کاملاً تعریف شده(Fully defined relationships) :

متداول ترین الگوی روابط این است که ویژگی های ناوبری(navigation properties) در هر دو انتهای رابطه و یک ویژگی کلید خارجی(foreign key) در کلاس موجودیت وابسته(dependent entity) تعریف شده باشد.

  • اگر یک جفت ویژگی ناوبری بین دو entity type یافت شود، آنگاه آنها به عنوان ویژگی های ناوبری معکوس(inverse navigation properties) همان رابطه پیکربندی می شوند.
  • اگر موجودیت وابسته حاوی ویژگی با نامی باشد که با یکی از این الگوها مطابقت دارد، به عنوان کلید خارجی پیکربندی می شود:
  • <navigation property name><principal key property name>
  • <navigation property name>Id
  • <principal entity name><principal key property name>
  • <principal entity name>Id
public class Blog //Principal entity { public int BlogId { get; set; } //principal key public string Url { get; set; } public List<Post> Posts { get; set; } //Collection navigation property } public class Post //Dependent entity { public int PostId { get; set; } //principal key public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } //Foreign key public Blog Blog { get; set; } //Inverse navigation property And Reference navigation property }

اگر Post.BlogId از نوعی باشد که با کلید اصلی سازگار نباشد، به عنوان کلید خارجی پیکربندی نمی‌شود.(type ها می بایست یکسان باشد.)

رابطه با ویژگی ناوبری واحد (Relationship with Single navigation property) :

گنجاندن تنها یک ویژگی ناوبری (بدون ناوبری معکوس و بدون ویژگی کلید خارجی) برای داشتن یک رابطه تعریف شده توسط قرارداد کافی است. شما همچنین می توانید یک ویژگی ناوبری واحد و یک ویژگی کلید خارجی داشته باشید.

public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } }

محدودیت ها (Limitations) :

هنگامی که چندین ویژگی ناوبری بین دو entity type تعریف شده است (یعنی بیش از یک جفت navigations که به یکدیگر اشاره می کنند) روابط نشان داده شده توسط ویژگی های ناوبری مبهم هستند. برای رفع ابهام باید آنها را به صورت دستی پیکربندی کنید.

حذف آبشاری (Cascade delete) :

طبق قرارداد، cascade delete برای روابط مورد نیاز( Required Relationships) روی Cascade و برای روابط اختیاری(Optional Relationships) برروی ClientSetNull تنظیم می شود.
cascade یعنی موجودیت های وابسته نیز حذف می شوند.
ClientSetNull به این معنی است که موجودیت های وابسته که در حافظه بارگذاری نمی شوند، بدون تغییر باقی می مانند و باید به صورت دستی حذف شوند، یا به روز شوند تا به یک موجودیت اصلی معتبر اشاره کنند. برای موجودیت هایی که در حافظه بارگذاری می شوند، EF Core سعی می کند خصوصیات کلید خارجی را null کند.

روابط اجباری و اختیاری(Required and optional relationships)
می‌توانید از Fluent API برای پیکربندی اینکه آیا رابطه الزامی است یا اختیاری استفاده کنید. در نهایت این کنترل می کند که آیا ویژگی کلید خارجی الزامی یا اختیاری است. این زمانی که از کلید خارجی حالت سایه
( shadow frogin key ) استفاده می کنید بسیار مفید است. اگر دارای یک ویژگی کلید خارجی در کلاس موجودیت خود هستید، الزامی بودن رابطه بر اساس الزامی یا اختیاری بودن پراپرتی کلید خارجی تعیین می‌شود.(یک پراپرتی در صورتی اختیاری در نظر گرفته می شود که معتبر باشد که حاوی null باشد. اگر null یک مقدار معتبر برای تخصیص به یک ویژگی نباشد، به عنوان یک ویژگی الزامی در نظر گرفته می شود. هنگام نگاشت به یک طرح پایگاه داده رابطه‌ای، ویژگی‌های مورد نیاز به‌عنوان ستون‌های غیر قابل تهی و ویژگی‌های اختیاری به‌عنوان ستون‌های nullable ایجاد می‌شوند.)

ویژگی های کلید خارجی بر روی نوع موجودیت وابسته قرار دارند، بنابراین اگر به صورت الزامی پیکربندی شوند به این معنی است که هر موجودیت وابسته باید یک موجودیت اصلی متناظر داشته باشد.


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.IsRequired();
}
فراخوانی IsRequired(false) نیز ویژگی کلید خارجی را اختیاری می کند مگر اینکه به شکل دیگری پیکربندی شده باشد.


حذف آبشاري (Cascade delete) :

می‌توانید از Fluent API برای پیکربندی رفتار حذف آبشاری برای یک رابطه مشخص استفاده کنید.

protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .OnDelete(DeleteBehavior.Cascade); }

پیکربندی دستی (Manual configuration) :

  • با استفاده از Fluent API :
    برای پیکربندی یک رابطه در Fluent API، با شناسایی ویژگی‌های ناوبری که رابطه را تشکیل می‌دهند، شروع می‌کنید. HasOne یا HasMany ویژگی ناوبری را در نوع موجودیتی که پیکربندی را روی آن شروع می کنید، شناسایی می کند. سپس یک تماس را با WithOne یا WithMany زنجیره‌ای می‌کنید تا ناوبری معکوس را شناسایی کنید. HasOne/WithOne برای ویژگی های ناوبری مرجع و HasMany/WithMany برای ویژگی های ناوبری مجموعه استفاده می شود.
internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public Blog Blog { get; set; } }
  • با استفاده از Data Annotations :
    می توانید از Data Annotationها برای پیکربندی نحوه جفت شدن ویژگی های پیمایش موجودیت های وابسته و اصلی استفاده کنید. این معمولاً زمانی انجام می شود که بیش از یک جفت ویژگی ناوبری بین دو نوع موجودیت وجود داشته باشد.
public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int AuthorUserId { get; set; } public User Author { get; set; } public int ContributorUserId { get; set; } public User Contributor { get; set; } } public class User { public string UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [InverseProperty(&quotAuthor&quot)] public List<Post> AuthoredPosts { get; set; } [InverseProperty(&quotContributor&quot)] public List<Post> ContributedToPosts { get; set; } }

شما فقط می‌توانید از [Required] در ویژگی‌های موجودیت وابسته استفاده کنید تا بر ضرورت رابطه تأثیر بگذارد. [Required] در مسیریابی موجودیت اصلی معمولاً نادیده گرفته می‌شود، اما ممکن است باعث شود که موجودیت به واحد وابسته تبدیل شود.

The data annotations [ForeignKey] and [InverseProperty] are available in the System.ComponentModel.DataAnnotations.Schema namespace.
[Required] is available in the System.ComponentModel.DataAnnotations namespace.


رابطه با ویژگی ناوبری واحد (Relationship with Single navigation property) :

اگر فقط یک ویژگی ناوبری دارید، پس overloadهای بدون پارامتر WithOne و WithMany برای آن وجود دارد. این نشان می دهد که از نظر مفهومی یک reference یا collectionی در یک سوی رابطه وجود دارد، اما هیچ ویژگی ناوبری دیگری در کلاس مرتبط وجود ندارد.

internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasMany(b => b.Posts) .WithOne(); modelBuilder.Entity<Blog>() .Navigation(b => b.Posts) .UsePropertyAccessMode(PropertyAccessMode.Property); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } }

توجه داشته باشید پیکربندی فوق برای ایجاد یک پراپرتی ناوبری قابل استفاده نیست. این فقط برای پیکربندی یک navigation property که قبلاً با تعریف یک رابطه یا از یک قرارداد ایجاد شده است استفاده می شود.

پیکربندی کلید خارجی (Foreign key) :

  • با استفاده از Fluent API برای simple key :
    می توانید از Fluent API برای پیکربندی اینکه کدام پراپرتی باید به عنوان ویژگی کلید خارجی برای یک رابطه معین استفاده شود استفاده کنید:
internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .HasForeignKey(p => p.BlogForeignKey); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogForeignKey { get; set; } public Blog Blog { get; set; } }
  • با استفاده از Fluent API برای composite key :
    می‌توانید از Fluent API برای پیکربندی اینکه کدام ویژگی‌ها باید به عنوان ویژگی‌های کلید خارجی ترکیبی برای یک رابطه معین استفاده شوند، استفاده کنید:
internal class MyContext : DbContext { public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => new { c.State, c.LicensePlate }); modelBuilder.Entity<RecordOfSale>() .HasOne(s => s.Car) .WithMany(c => c.SaleHistory) .HasForeignKey(s => new { s.CarState, s.CarLicensePlate }); } } public class Car { public string State { get; set; } public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } public List<RecordOfSale> SaleHistory { get; set; } } public class RecordOfSale { public int RecordOfSaleId { get; set; } public DateTime DateSold { get; set; } public decimal Price { get; set; } public string CarState { get; set; } public string CarLicensePlate { get; set; } public Car Car { get; set; } }
  • با استفاده از Data Annotations :
    می توانید از Data Annotations برای پیکربندی اینکه کدام ویژگی باید به عنوان ویژگی کلید خارجی برای یک رابطه معین استفاده شود استفاده کنید. این معمولاً زمانی انجام می شود که ویژگی کلید خارجی طبق قرارداد کشف نشده باشد:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } [ForeignKey(&quotBlogForeignKey&quot)] public int BlogForeignKey { get; set; } public Blog Blog { get; set; } }


کلید خارجی سایه (Shadow foreign key) :

می توانید از overload متد HasForeignKey(...) برای پیکربندی یک ویژگی سایه به عنوان یک کلید خارجی استفاده کنید. توصیه می کنیم قبل از استفاده از آن به عنوان کلید خارجی، ویژگی shadow را به صراحت به مدل اضافه کنید.

internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // Add the shadow property to the model modelBuilder.Entity<Post>() .Property<int>(&quotBlogForeignKey&quot); // Use the shadow property as a foreign key modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .HasForeignKey(&quotBlogForeignKey&quot); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public Blog Blog { get; set; } }


رابطه بدون ویژگی ناوبری (relationship Without navigation property) :

لزوماً نیازی به ارائه پراپرتی ناوبری ندارید. شما به سادگی می توانید یک کلید خارجی در یک طرف رابطه ارائه دهید.

internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>() .HasOne<Blog>() .WithMany() .HasForeignKey(p => p.BlogId); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } }


کلید اصلی (Principal key) :

اگر می‌خواهید کلید خارجی به ویژگی دیگری غیر از کلید اصلی اشاره کند، می‌توانید از Fluent API برای پیکربندی ویژگی کلید اصلی برای رابطه استفاده کنید. ویژگی ای که به عنوان کلید اصلی پیکربندی می کنید به طور خودکار به عنوان یک کلید جایگزین تنظیم می شود.

  • برای حالت simple key :
internal class MyContext : DbContext { public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<RecordOfSale>() .HasOne(s => s.Car) .WithMany(c => c.SaleHistory) .HasForeignKey(s => s.CarLicensePlate) .HasPrincipalKey(c => c.LicensePlate) } } public class Car { public int CarId { get; set; } public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } public List<RecordOfSale> SaleHistory { get; set; } } public class RecordOfSale { public int RecordOfSaleId { get; set; } public DateTime DateSold { get; set; } public decimal Price { get; set; } public string CarLicensePlate { get; set; } public Car Car { get; set; } }
  • برای حالت Composite key :
internal class MyContext : DbContext { public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<RecordOfSale>() .HasOne(s => s.Car) .WithMany(c => c.SaleHistory) .HasForeignKey(s => new { s.CarState, s.CarLicensePlate }) .HasPrincipalKey(c => new { c.State, c.LicensePlate }); } } public class Car { public int CarId { get; set; } public string State { get; set; } public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } public List<RecordOfSale> SaleHistory { get; set; } } public class RecordOfSale { public int RecordOfSaleId { get; set; } public DateTime DateSold { get; set; } public decimal Price { get; set; } public string CarState { get; set; } public string CarLicensePlate { get; set; } public Car Car { get; set; } }

توجه داشته باشید ترتیبی که ویژگی های کلید اصلی را مشخص می کنید باید با ترتیبی که برای کلید خارجی مشخص شده اند مطابقت داشته باشد.


بیشتر بخوانید : دوره آموزشی Entity FrameWork Core - قسمت يازدهم

بیشتر بخوانید : دوره آموزشی Entity FrameWork Core

بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core

https://zarinp.al/farshidazizi

entity framework coreef coreASP.NET COREآموزش برنامه نويسيدوره اموزشی
Software Engineer
شاید از این پست‌ها خوشتان بیاید