در مطلب «بررسی تفصیلی رابطه Many-to-Many در EF Code first» نحوهی مدلسازی رابطهی چند به چند را در EF 6.x بررسی کردیم. یک چنین رابطهای که به همراه مدیریت خودکار join table آن است (جدول BlogPostsJoinTags در شکل زیر)، در EF Core 1.0 RTM پشتیبانی نمیشود.
البته همیشه درخواست کنترل این جدول واسط که کاملا از دیدگاه ORMها (تمام آنها) مخفی است، وجود داشتهاست و قرار است این پشتیبانی توسط مفهوم ویژهای به نام shadow properties به نگارشهای بعدی EF Core اضافه شود.
اما فعلا در نگارش اول آن، توصیه شدهاست که رابطهی many-to-many را به صورت دو رابطهی one-to-many مدلسازی کنید که در ادامه آنرا بررسی خواهیم کرد. بنابراین پیشنیاز آن مطالعهی مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطهی One-to-Many» میباشد.
مدلسازی موجودیتهای یک رابطهی چند به چند در EF Core 1.0 RTM توسط Fluent API
در اینجا نحوهی مدلسازی یک رابطهی چند به چند را توسط دو رابطهی one-to-many مشاهده میکنید. تنها تفاوت آن با EF 6.x، قید صریح جدول واسط BlogPostsJoinTags است که یک چنین جدولی در EF 6.x به صورت خودکار تشکیل شده و مدیریت میشود و کاملا از دید برنامه مخفی است. اما در اینجا (در نگارش اول EF Core) نیاز است این جدول واسط را از حالت مخفی خارج کرد و سپس دو رابطهی یک به چند را به جداول مطالب و تگهای آنها تشکیل داد.
مزیت این حالت، دسترسی کامل به طراحی جدول واسط، توسط برنامه است. بنابراین اگر به هر دلیلی نیاز به افزودن خواص بیشتری به این جدول ویژه دارید، اکنون امکان آن میسر است.
پس از آن باید Context برنامه را نیز جهت ذکر صریح رابطهی یک به چند، ویرایش کرد. با متدهای HasOne و WithMany در مطلب قبل «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطهی One-to-Many» آشنا شدیم و علت نیاز به ذکر صریح آنها، وجود بیش از یک سر رابطه در جدول واسط BlogPostsJoinTags است. در این حالت یافتن InversePropertyها توسط EF Core میسر نیست و حتما باید از یک طرف شروع و سمت دیگر را مشخص کرد.
به علاوه در اینجا تعریف یک composite key را هم بر روی خواص کلید خارجی جدول واسط مشاهده میکنید. وجود این کلید ترکیبی سبب خواهد شد که ملزم به ثبت هر دو Id (کلیدهای جداول مطلب و تگ) در حین ثبت در این جدول شویم (یا قید اجباری هر دو طرف رابطه).
مدلسازی موجودیتهای یک رابطهی چند به چند در EF Core 1.0 RTM توسط Data Annotations
در حالت مدلسازی توسط ویژگیها، ذکر InversePropertyها و همچنین ForeignKeyها مقداری واضحتر به نظر میرسند. به علاوه، یک سری از تنظیمات هم معادل data annotations ایی ندارند؛ مانند composite key تعریف شدهی بر روی خواص جدول واسط و همچنین ایندکسهای تعریف شده، که حتما باید توسط Fluent API تنظیم شوند.
همانطور که در مطلب «شروع به کار با EF Core 1.0 - قسمت 6 - تعیین نوعهای داده و ویژگیهای آنها» نیز عنوان شد، علت تنظیم MaxLength به عدد 450 این است که بتوان بر روی این ستون ایندکس ایجاد کرد.
نحوهی ثبت اطلاعات در دو رابطهی یک به چند به همراه جدول واسط
در EF 6.x، کار مقدار دهی Idهای جدول واسط به صورت خودکار انجام میشود. در اینجا این مقدار دهی را باید به صورت صریح انجام داد:
نحوهی واکشی اطلاعات به هم مرتبط در دو رابطهی یک به چند به همراه جدول واسط
در مورد متدهای Include و ThenInclude در مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطهی One-to-Many» پیشتر بحث شد.
در اینجا اگر میخواهیم به لیست تمام برچسبهای اولین مطلب دسترسی پیدا کنیم، ابتدا باید رابطهی جدول واسط را Include کنیم. سپس چون در همین سطح میخواهیم به سطح بعدی تگهای مرتبط برسیم، باید از متد الحاقی جدید ThenInclude استفاده کرد. در غیراینصورت در سطر بعدی، post1.BlogPostsJoinTags نال خواهد بود و همچنین حاوی لیست تگها نیز نخواهد بود.
یک نکتهی تکمیلی
وضعیت پشتیبانی از رابطهی many-to-many را همانند EF 6.x در EF Core، در اینجامیتوانید پیگیری کنید.
البته همیشه درخواست کنترل این جدول واسط که کاملا از دیدگاه ORMها (تمام آنها) مخفی است، وجود داشتهاست و قرار است این پشتیبانی توسط مفهوم ویژهای به نام shadow properties به نگارشهای بعدی EF Core اضافه شود.
اما فعلا در نگارش اول آن، توصیه شدهاست که رابطهی many-to-many را به صورت دو رابطهی one-to-many مدلسازی کنید که در ادامه آنرا بررسی خواهیم کرد. بنابراین پیشنیاز آن مطالعهی مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطهی One-to-Many» میباشد.
مدلسازی موجودیتهای یک رابطهی چند به چند در EF Core 1.0 RTM توسط Fluent API
در اینجا نحوهی مدلسازی یک رابطهی چند به چند را توسط دو رابطهی one-to-many مشاهده میکنید. تنها تفاوت آن با EF 6.x، قید صریح جدول واسط BlogPostsJoinTags است که یک چنین جدولی در EF 6.x به صورت خودکار تشکیل شده و مدیریت میشود و کاملا از دید برنامه مخفی است. اما در اینجا (در نگارش اول EF Core) نیاز است این جدول واسط را از حالت مخفی خارج کرد و سپس دو رابطهی یک به چند را به جداول مطالب و تگهای آنها تشکیل داد.
مزیت این حالت، دسترسی کامل به طراحی جدول واسط، توسط برنامه است. بنابراین اگر به هر دلیلی نیاز به افزودن خواص بیشتری به این جدول ویژه دارید، اکنون امکان آن میسر است.
public class Tags { public Tags() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } public string Name { get; set; } public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
public class BlogPostsJoinTags { public virtual BlogPosts BlogPost { get; set; } public int BlogPostId { get; set; } public virtual Tags Tag { get; set; } public int TagId { get; set; } }
public class BlogPosts { public BlogPosts() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } public string Title { get; set; } public string Body { get; set; } public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
به علاوه در اینجا تعریف یک composite key را هم بر روی خواص کلید خارجی جدول واسط مشاهده میکنید. وجود این کلید ترکیبی سبب خواهد شد که ملزم به ثبت هر دو Id (کلیدهای جداول مطلب و تگ) در حین ثبت در این جدول شویم (یا قید اجباری هر دو طرف رابطه).
public class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BlogPosts>(entity => { entity.Property(e => e.Title) .IsRequired() .HasMaxLength(450); }); modelBuilder.Entity<Tags>(entity => { entity.Property(e => e.Name) .IsRequired() .HasMaxLength(450); }); modelBuilder.Entity<BlogPostsJoinTags>(entity => { entity.HasKey(e => new { e.TagId, e.BlogPostId }) .HasName("PK_dbo.BlogPostsJoinTags"); entity.HasIndex(e => e.BlogPostId) .HasName("IX_BlogPostId"); entity.HasIndex(e => e.TagId) .HasName("IX_TagId"); entity.HasOne(d => d.BlogPost) .WithMany(p => p.BlogPostsJoinTags) .HasForeignKey(d => d.BlogPostId) .HasConstraintName("FK_dbo.BlogPostsJoinTags_dbo.BlogPosts_BlogPostId"); entity.HasOne(d => d.Tag) .WithMany(p => p.BlogPostsJoinTags) .HasForeignKey(d => d.TagId) .HasConstraintName("FK_dbo.BlogPostsJoinTags_dbo.Tags_TagId"); }); } public virtual DbSet<BlogPosts> BlogPosts { get; set; } public virtual DbSet<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } public virtual DbSet<Tags> Tags { get; set; } }
مدلسازی موجودیتهای یک رابطهی چند به چند در EF Core 1.0 RTM توسط Data Annotations
در حالت مدلسازی توسط ویژگیها، ذکر InversePropertyها و همچنین ForeignKeyها مقداری واضحتر به نظر میرسند. به علاوه، یک سری از تنظیمات هم معادل data annotations ایی ندارند؛ مانند composite key تعریف شدهی بر روی خواص جدول واسط و همچنین ایندکسهای تعریف شده، که حتما باید توسط Fluent API تنظیم شوند.
public class Tags { public Tags() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } [Required] [MaxLength(450)] public string Name { get; set; } [InverseProperty("Tag")] public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
public class BlogPostsJoinTags { [ForeignKey("BlogPostId")] [InverseProperty("BlogPostsJoinTags")] public virtual BlogPosts BlogPost { get; set; } public int BlogPostId { get; set; } [ForeignKey("TagId")] [InverseProperty("BlogPostsJoinTags")] public virtual Tags Tag { get; set; } public int TagId { get; set; } }
public class BlogPosts { public BlogPosts() { BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>(); } public int Id { get; set; } [Required] [MaxLength(450)] public string Title { get; set; } public string Body { get; set; } [InverseProperty("BlogPost")] public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } }
public class MyDBDataContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<BlogPostsJoinTags>(entity => { entity.HasKey(e => new { e.TagId, e.BlogPostId }) .HasName("PK_dbo.BlogPostsJoinTags"); entity.HasIndex(e => e.BlogPostId) .HasName("IX_BlogPostId"); entity.HasIndex(e => e.TagId) .HasName("IX_TagId"); }); } public virtual DbSet<BlogPosts> BlogPosts { get; set; } public virtual DbSet<BlogPostsJoinTags> BlogPostsJoinTags { get; set; } public virtual DbSet<Tags> Tags { get; set; } }
نحوهی ثبت اطلاعات در دو رابطهی یک به چند به همراه جدول واسط
در EF 6.x، کار مقدار دهی Idهای جدول واسط به صورت خودکار انجام میشود. در اینجا این مقدار دهی را باید به صورت صریح انجام داد:
var post = new BlogPosts { ... }; context.BlogPosts.Add(post); var tag = new Tags { ... }; context.Tags.Add(tag); var postTag = new BlogPostsJoinTags { Tag = tag, BlogPost = post }; context.PostsTags.Add(postTag); context.SaveChanges();
نحوهی واکشی اطلاعات به هم مرتبط در دو رابطهی یک به چند به همراه جدول واسط
در مورد متدهای Include و ThenInclude در مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطهی One-to-Many» پیشتر بحث شد.
BlogPosts post1 = this.BlogPosts .Include(blogPosts => blogPosts.BlogPostsJoinTags) .ThenInclude(joinTags => joinTags.Tag) .First(blogPosts => blogPosts.Id == 1); IEnumerable<Tags> post1Tags = post1.BlogPostsJoinTags.Select(x => x.Tag);
یک نکتهی تکمیلی
وضعیت پشتیبانی از رابطهی many-to-many را همانند EF 6.x در EF Core، در اینجامیتوانید پیگیری کنید.