در این مقاله قصد داریم نحوه پیاده سازی ارتباط یک به چند را در Code First شرح می دهیم .در این مقاله قصد داریم از دو روش برای پیاده سازی ارتباط یک به چند استفاده کنیم
پیرو مقاله قبلی با عنوان رابطه یک به یک در روش Code First در این مقاله قصد داریم به توضیح و ایجاد رابطه یک به چند در Entity Framework بپردازیم .
همان طور که میدانید Entity Framework یک ORM است که کار ارتباط با منبع داده را برای ما ساده کرده است .برای استفاده از EF روش های مختلفی وجود دارد که یکی از این روش ها Code First است .دراین روش کلاس هایی که نوشته ایم به جداول تبدیل می شوند .از انجایی که بین جداول ارتباطات مختلفی وجود دارد بنابراین باید بین کلاس هایی که در روش code first ایجاد می کنیم هم رابطه برقرار کنیم تا به صورت مناسب به جداول ما نگاشت شوند .
همان طور که میدانید بین دو موجودیت می تواند ارتباطات مختلفی برقرار باشد که در زیر لیست شده اند
رابطه یک به یک
رابطه یک به چند
رابطه چند به چند
رابطه یک به یک
در این نوع ارتباط هر نمونه از موجودیت اول با یک و فقط یک نمونه از موجودیت دوم در ارتباط است .
رابطه یک به چند
در این نوع رابطه یک نمونه از موجودیت می تواند با یک یا چند نمونه از موجودیت دیگر در ارتباط باشد .
رابطه چند به چند
هر نمونه از موجودیت اول می تواند با یک یا چند نمونه از موجودیت دیگر ارتباط داشته باشد و برعکس.
برای درک رابطه یک به چند دو موجودیت People و PeopleAddress را در نظر بگیرید.بین آنها یک رابطه یک به چند وجود دارد .هر شخصی از جدول People می تواند یک یا چند آدرس داشته باشد .در داخل جدول People یک کلید اصلی به نام PeopleId وجود دارد .در داخل جدول PeopleAddress هم یک کلید اصلی وجود دارد ولی علاوه بر کلید اصلی یک کلید خارجی هم از جدول People باید تعریف کنیم .کلید اصلی در جدول People در داخل جدول PeopleAddress به عنوان کلید خارجی خواهد بود.
در شکل زیر ارتباط بین دو جدول و همچنین فیلد های آنها نمایش داده شده است .
بر اساس ارتباطی که بین اشیا وجود دارد و association هم یکی از آنهاست ، یک موجودیت می تواند با موجودیت دیگر در ارتباط باشد .در داخل ارتباط باید نوع موجودیت های دو سر ارتباط و همچنین چندگانگی آنها تعیین شود.همچنین باید نقش های اصلی و وابسته برای هر موجودیت هم در نظر گرفته شود.دو روش برای پیاده سازی ارتباط یک به چند وجود دارد که در زیر به آنها می پردازیم .
بر اساس Navigation Property ها EF تشخیص می دهد که دو سر ارتباط کدام است .اگر ارتباط یک به چند باشد یک سر ارتباط یک پراپرتی از نوع موجودیت دیگر است و سر دیگر ارتباط یک لیستی از موجودیت دیگر را تعریف می کنیم .اگر بین دو موجودیت چندین ارتباط وجود داشته باشد EF به مشکل برخواهد خورد .زیرا تشخیص نخواهد داد که دو سر ارتباط و همچنین چندگانی آنها چیست .صفت InverseProperty می تواند به ما کمک کند .
از InversProperty برای تعریف روابط دو طرفه استفاده می شود .
مثلا دو کلاس کتاب و نویسنده را در نظر بگیرید کلاس کتاب باید نویسنده مشخص باشد و هر نویسنده ایی می تواند چندین کتاب داشته باشد.
public class Book { public int ID {get; set;} public string Title {get; set;} [InverseProperty("Books")] public Author Author {get; set;} } public class Author { public int ID {get; set;} public string Name {get; set;} [InverseProperty("Author")] public virtual ICollection<Book> Books {get; set;} }
البته EF به صورت خودکار و با قراردادهایی که دارد ارتباطات را از طریق Navigation Property ها تشخیص می دهند .و نیازی به Inverse Property نیست .
کلاس های مربوط به people و People Address در زیر نشان داده شده است .
کلاس people
[Table("People")] public partial class People { public People() { this.PeopleAddress = newHashSet < PeopleAddress > (); } [DatabaseGenerated(DatabaseGeneratedOption.None)] [Key] public int PeopleId { get; set; } [Required] [StringLength(50)] public string FirstName { get; set; } [Required] [StringLength(50)] public string LastName { get; set; } public virtual ICollection < PeopleAddress > PeopleAddress { get; set; } }
کلاس People Address
[Table("PeopleAddress")] public partial class PeopleAddress { [Key] [Column(Order = 1)] [DatabaseGenerated(DatabaseGeneratedOption.None)] public int PeopleAddressId { get; set; } [Column(Order = 2)] [Key, ForeignKey("People")] public int PeopleId { get; set; } [Required] [StringLength(100)] public string AddressLine1 { get; set; } [Required] [StringLength(100)] public string AddressLine2 { get; set; } [StringLength(50)] public string City { get; set; } [StringLength(50)] public string State { get; set; } [StringLength(50)] public string Country { get; set; } public virtual PeoplePeople { get; set; } }
کلاس context ما که قرار است نقش دیتابیس را بازی کند در زیر آورده شده است
public partial class EFTestModel: DbContext { public EFTestModel(): base("name=entities") {} public virtual DbSet < People > People { get; set; } public virtual DbSet < PeopleAddress > PeopleAddress { get; set; } protected override void OnModelCreating(DbModelBuildermodelBuilder) {} }
طبق کدهایی که دیدید بین دو کلاس ارتباط یک به چند وجود دارد . PeopleId در داخل کلاس People به عنوان کلید اصلی است .اما در داخل کلاس PeopleAddress به عنوان کلید خارجی خواهد بود .بنابراین در داخل کلاس PeopleAddress این فیلد را با صفت ForeignKey آورده ایم .همچنین برای اینکه EF تشخیص دهد که دو سر ارتباط کیست و همچنین چندگانگی ارتباط را بفهمد در داخل کلاس People برای Navigation property این خط را آورده ایم .
public virtual ICollection < PeopleAddress > PeopleAddress { get; set;
و همچنین در داخل کلاس PeopleAddress پراپرتی زیر را تعریف می کنیم
[Column(Order = 2)] [Key, ForeignKey("People")] public int PeopleId { get; set; }
برای تست این نوع ارتباط در داخل برنامه کنسول کدهای زیر را نوشته ایم
static void Main(string[] args) { //Configure One to Many Relationship in Entity Framework Using Code First Approach People people; using(var context = newEntityModel.EFTestModel()) { people = context.People.FirstOrDefault(); int index = 1; Console.WriteLine("People Details"); Console.WriteLine("Name:" + string.Join(" ", newobject[] { people.FirstName, people.LastName })); Console.WriteLine("Addresses"); Console.WriteLine("---------"); for each(var address inpeople.PeopleAddress) { Console.WriteLine(index + string.Join(", ", newobject[] { address.AddressLine1, address.AddressLine2, address.City, address.State, address.Country })); index += 1; } } Console.ReadLine(); }
بعد از اجرا شدن برنامه شکل زیر را خواهید دید
روش دوم : استفاده از Fluent API
همان طور که در روش اول از صفات کلید اصلی و کلید خارجی استفاده کردیم می توانیم در این روش از HasKey برای تعریف کلید اصلی استفاده می کنیم و از HasForeignKey برای تعریف کلید خارجی استفاده می کنیم .برای اینکه تعیین کنی که یک پراپرتی نمی تواند Null باشد از HasRequired و یا WithRequired استفاده می کنیم .همچنین برای تعیین نوع ارتباط یک به چند از متدها HasMany و ارتباط چند به چند از WithMany و یا HasMany استفاده می کنیم .
برای تعیین ارتباط یک به چند کدهای زیر را در داخل متد OnModelBuilder یادداشت می کنیم.
protected override void OnModelCreating(DbModelBuildermodelBuilder) { modelBuilder.Entity<StudentAddress>() .HasRequired<Student>(s =>s.Student) .WithMany(s =>s.StudentAddress) .HasForeignKey(s =>s.StudentId); // Alternate possible way //modelBuilder.Entity<Student>() // .HasMany<StudentAddress>(s =>s.StudentAddress) // .WithRequired(s =>s.Student) // .HasForeignKey(s =>s.StudentId); }
برای تست کردن این روش دو کلاس strudent و StudentAddress را به صورت زیر تعریف کرده ایم
[Table("Student")] public partial class Student { public Student() { this.StudentAddress = newHashSet < StudentAddress > (); } [DatabaseGenerated(DatabaseGeneratedOption.None)] public int StudentId { get; set; } [Required] [StringLength(50)] public string FirstName { get; set; } [Required] [StringLength(50)] public string LastName { get; set; } public virtual ICollection < StudentAddress > StudentAddress { get; set; } } [Table("StudentAddress")] public partial class StudentAddress { [DatabaseGenerated(DatabaseGeneratedOption.None)] public int StudentAddressId { get; set; } public int StudentId { get; set; } [Required] [StringLength(100)] public string AddressLine1 { get; set; } [Required] [StringLength(100)] public string AddressLine2 { get; set; } [StringLength(50)] public string City { get; set; } [StringLength(50)] public string State { get; set; } [StringLength(50)] public string Country { get; set; } public virtual StudentStudent { get; set; } }
در داخل کنسول برنامه کدهای زیر نوشته شده است
static void Main(string[] args) { //Configure One to Many Relationship in Entity Framework Using Code First Approach Student student; using(var context = new EntityModel.EFTestModel()) { student = context.Student.FirstOrDefault(); int index1 = 1; Console.WriteLine("Student Details"); Console.WriteLine("Name:" + string.Join(" ", newobject[] { student.FirstName, student.LastName })); Console.WriteLine("Addresses"); Console.WriteLine("---------"); for each(var address instudent.StudentAddress) { Console.WriteLine(index1 + " " + string.Join(", ", newobject[] { address.AddressLine1, address.AddressLine2, address.City, address.State, address.Country })); index1 += 1; } } Console.ReadLine(); }
خروجی برنامه به صورت زیر است .