public class Blog { public int BlogId { get; set; } public string Url { get; set; } public BlogImage BlogImage { get; set; } } public class BlogImage { public int BlogImageId { get; set; } public byte[] Image { get; set; } public string Caption { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } }
خوب EF بر اساس توانایی خود در تشخیص ویژگی کلید خارجی، یکی از موجوديت ها را به عنوان وابسته(dependent) انتخاب می کند. اگر موجودیت اشتباهی به عنوان وابسته انتخاب شود، میتوانید از Fluent API برای اصلاح آن استفاده کنید.
هنگام پیکربندی رابطه با Fluent API، از متدهای HasOne و WithOne استفاده میکنیم.
هنگام پیکربندی کلید خارجی، باید نوع موجودیت وابسته را مشخص کنید - به پارامتر generic ارائه شده به HasForeignKey در قطعه کد زیر توجه کنید. در یک رابطه یک به چند، واضح است که موجودیت دارای ناوبری مرجع، وابسته و موجودیتي که مجموعه دارد، اصلی است. اما در رابطه یک به یک اینطور نیست - از این رو نیاز به تعریف صریح آن است.
internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<BlogImage> BlogImages { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasOne(b => b.BlogImage) .WithOne(i => i.Blog) .HasForeignKey<BlogImage>(b => b.BlogForeignKey); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public BlogImage BlogImage { get; set; } } public class BlogImage { public int BlogImageId { get; set; } public byte[] Image { get; set; } public string Caption { get; set; }= public int BlogForeignKey { get; set; } public Blog Blog { get; set; } }
سمت وابسته(dependent) به طور پیش فرض اختیاری(optional) در نظر گرفته می شود، اما می تواند در بصورت الزامي(required) پیکربندی شود. یک سناریوی رایج برای این،reference owned typeها هستند که به طور پیش فرض از table splitting استفاده می کنند.
modelBuilder.Entity<Order>( ob => { ob.OwnsOne( o => o.ShippingAddress, sa => { sa.Property(p => p.Street).IsRequired(); sa.Property(p => p.City).IsRequired(); }); ob.Navigation(o => o.ShippingAddress) .IsRequired(); });
با این پیکربندی، ستون های مربوط به ShippingAddress به عنوان غیر قابل تهی شدن(non-nullable) در پایگاه داده علامت گذاری می شوند.
اگر از non-nullable reference types استفاده می کنید، فراخوانی IsRequired ضروری نیست.
public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public ICollection<Tag> Tags { get; set; } } public class Tag { public string TagId { get; set; } public ICollection<Post> Posts { get; set; } }
روشی که این رابطه در پایگاه داده پیاده سازی می شود توسط یک جدول join است که حاوی کلیدهای خارجی برای هر دو Post و Tag است.
به عنوان مثال این چیزی است که EF در یک پایگاه داده رابطه ای برای مدل فوق ایجاد می کند.
CREATE TABLE [Posts] ( [PostId] int NOT NULL IDENTITY, [Title] nvarchar(max) NULL, [Content] nvarchar(max) NULL, CONSTRAINT [PK_Posts] PRIMARY KEY ([PostId]) ); CREATE TABLE [Tags] ( [TagId] nvarchar(450) NOT NULL, CONSTRAINT [PK_Tags] PRIMARY KEY ([TagId]) ); CREATE TABLE [PostTag] ( [PostsId] int NOT NULL, [TagsId] nvarchar(450) NOT NULL, CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]), CONSTRAINT [FK_PostTag_Posts_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([PostId]) ON DELETE CASCADE, CONSTRAINT [FK_PostTag_Tags_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([TagId]) ON DELETE CASCADE );
در اینجا، EF یک نوع موجودیت را برای نشان دادن join table ایجاد می کند که به عنوان join entity type نامیده می شود.
یش از یک رابطه چند به چند میتواند در مدل وجود داشته باشد، بنابراین به join entity type باید یک نام منحصر به فرد داده شود، در این مورد PostTag.
پیکر بندی Join entity type :
این عمل را می توان از طریق UsingEntity انجام داد :
modelBuilder .Entity<Post>() .HasMany(p => p.Tags) .WithMany(p => p.Posts) .UsingEntity(j => j.ToTable("PostTags"));
داده های آزمایشی (Model seed data) برای join entity type:
modelBuilder .Entity<Post>() .HasData(new Post { PostId = 1, Title = "First" }); modelBuilder .Entity<Tag>() .HasData(new Tag { TagId = "ef" }); modelBuilder .Entity<Post>() .HasMany(p => p.Tags) .WithMany(p => p.Posts) .UsingEntity(j => j.HasData(new { PostsPostId = 1, TagsTagId = "ef" }));
داده های اضافی(پراپرتی ها/ویژگی ها) را می توان در join entity type ذخیره کرد، اما برای این کار بهتر است یک نوع CLR سفارشی ایجاد کنید. هنگام پیکربندی رابطه با یک join entity type سفارشی، هر دو کلید خارجی باید به صراحت مشخص شوند :
internal class MyContext : DbContext { public MyContext(DbContextOptions<MyContext> options) : base(options) { } public DbSet<Post> Posts { get; set; } public DbSet<Tag> Tags { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>() .HasMany(p => p.Tags) .WithMany(p => p.Posts) .UsingEntity<PostTag>( j => j .HasOne(pt => pt.Tag) .WithMany(t => t.PostTags) .HasForeignKey(pt => pt.TagId), j => j .HasOne(pt => pt.Post) .WithMany(p => p.PostTags) .HasForeignKey(pt => pt.PostId), j => { j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP"); j.HasKey(t => new { t.PostId, t.TagId }); }); } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public ICollection<Tag> Tags { get; set; } public List<PostTag> PostTags { get; set; } } public class Tag { public string TagId { get; set; } public ICollection<Post> Posts { get; set; } public List<PostTag> PostTags { get; set; } } public class PostTag { public DateTime PublicationDate { get; set; } public int PostId { get; set; } public Post Post { get; set; } public string TagId { get; set; } public Tag Tag { get; set; } }
پیکربندی Joining relationships :
توجه داشته باشید EF از دو رابطه یک به چند در join entity type برای نشان دادن رابطه چند به چند استفاده می کند. می توانید این روابط را در آرگومان های UsingEntity پیکربندی کنید:
modelBuilder.Entity<Post>() .HasMany(p => p.Tags) .WithMany(p => p.Posts) .UsingEntity<Dictionary<string, object>>( "PostTag", j => j .HasOne<Tag>() .WithMany() .HasForeignKey("TagId") .HasConstraintName("FK_PostTag_Tags_TagId") .OnDelete(DeleteBehavior.Cascade), j => j .HasOne<Post>() .WithMany() .HasForeignKey("PostId") .HasConstraintName("FK_PostTag_Posts_PostId") .OnDelete(DeleteBehavior.ClientCascade));
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core - قسمت دوازدهم
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core
بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core