در توسینسو تدریس کنید

و

با دانش خود درآمد کسب کنید

آموزش Entity Framework قسمت 6 : تعریف رابطه ها بخش 2

در بخش قبلی سری مقالات آموزشی Entity Framework Code First در انواع رابطه ها و نحوه تعریف آنها در Entity Framework صحبت کردیم. در ادامه این مقالات به بررسی تعریف رابطه با استفاده از Fluent API و Attribute ها خواهیم پرداخت. در هر قسمت ابتدا با Fluent API و سپس با Attribute ها اقدام به تعریف رابطه ها خواهیم کرد.

رابطه One-To-Many و Zero Or One-To-Many

این رابطه از دو طرف قابل تنظیم است. کلاس های قبلی را به یاد بیاورید:

public class Customer
{
    public int CustomerID { get; set; }
    public string Name { get; set; }
    public virtual ICollection Orders { get; set; }
}

public class Order
{
    public int OrderID { get; set; }
    public virtual Customer Customer { get; set; }
    public DateTime Date { get; set; }
}

برای تعریف این رابطه با Fluent API متد OnModelCreating را در کلاس Context مان Override کرده و داخل آن کدهای زیر را می نویسیم:

modelBuilder.Entity()
    .HasMany(customer => customer.Orders)
    .WithRequired(order => order.Customer);

با این کد می گوییم که کلاس Customer دارای چندین Order و کلاس Order دارای یک Customer می باشد. یعنی رابطه ما یک به چند است. این عمل به صورت عکس نیز قابل تعریف است:

modelBuilder.Entity()
    .HasRequired(order => order.Customer)
    .WithMany(customer => customer.Orders);

به متد HasRequired دقت کنید. اینجا چون رابطه ما One-To-Many هست از متد HasRequired استفاده می کنیم. یعنی Customer رای کلاس Order اجباری است.

در صورتی که بخواهیم رابطه به صورت Zero Or One-To-Many تعریف شود باید از متد HasOptional استفاده کنیم:

modelBuilder.Entity()
    .HasOptional(order => order.Customer)
    .WithMany(customer => customer.Orders);

اما جدول Orders ما در بانک اطلاعاتی، دارای یک ForeignKey می باشد. به صورت پیش فرض برای رابطه بالا، Entity Framework ستون Customer_Id را به عنوان کلید خارجی در نظر می گیرد. اما فرض کنیم که نام کلید خارجی ما در جدول Orders ستون CustomerID باشد. برای این که ما این ستون را به عنوان کلید خارجی در نظر بگیریم، از متد HasForeignKey استفاده می کنیم. لازمه این کار این است که یک Property با نام CustomerID به کلاس Order اضافه کنیم و سپس اقدام به تعریف رابطه کنیم:

تعریف کلاس Orders:

public class Order
{
    ...
    public int CustomerID { get; set; }
    ...
}

استفاده از متد HasForeignKey:

modelBuilder.Entity()
    .HasRequired(order => order.Customer)
    .WithMany(customer => customer.Orders)
    .HasForeignKey(order => order.CustomerID);

دقت کنید که اگر از متد HasOptional استفاده کردیم باشیم (رابطه ما Zero or One-To-Many باشد)، خصوصیت CustomerID در کلاس Orders باید Nullable تعریف شود:

    public int? CustomerID { get; set; }

حالا، اگر ما نخواهیم خصوصیت CustomerID داخل کلاسمان تعریف شود چه کاری باید انجام دهیم؟ چاره این کار استفاده از Attribute ها است. برای کلید تعیین کلید خارجی از ForeignKey استفاده می کنیم که بر روی خصوصیت Customer در کلاس Orders قرار می گیرد:

    [ForeignKey("CustomerID")]
    public virtual Customer Customer { get; set; }

متد WillCascadeOnDelete: این متد برای این استفاده میشه که تعیین کنیم وقتی یک Customer حذف شد آیا تمام Order های آن نیز حذف شود یا خیر. این متد یک پارامتر boolean میگیرد یا می توان به آن هیچ پارامتری پاس نداد. در صورتی که هیچ پارامتری به آن پاس ندیم، عملیات Cascade انجام می شود، ولی در صورت ارسال مقدار false به این متد، در صورتی که Customer دارای Order ای باشد، عملیات از عملیات حذف جلوگیری خواهد شد:

modelBuilder.Entity()
    .HasOptional(order => order.Customer)
    .WithMany(customer => customer.Orders)
    .WillCascadeOnDelete(true);

*اگر از Fluent API استفاده نکرده و تنها بخواهیم با استفاده از Attribute ها نوع رابطه که One یا Zero-Or-One باشد را تعیین کنیم، اگر هیچ Attribute ای روی خصوصیت Customer نگذاریم، رابطه از نوع Zero-Or-One در نظر گرفته خواهد شد، ولی اگر Required را روی خصوصیت Customer در کلاس Order قرار دهیم، رابطه از نوع One در نظر گرفته می شود.

رابطه Many-To-Many

کد زیر را در نظر بگیرید:

public class User
{
    public int UserID { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public virtual ICollection UserGroups { get; set; }
}

public class UserGroup
{
    public int UserGroupID { get; set; }
    public string GroupName { get; set; }
    public virtual ICollection Users { get; set; }
}

برای رابطه چند به چند داخل متد OnModelCreating کد زیر را می نویسیم:

modelBuilder.Entity()
    .HasMany(user => user.UserGroups)
    .WithMany(userGroup => userGroup.Users);

اما مهمترین دلیل استفاده از Fluent API تغییر نام جدول سول و تعیین نام ستون های آن است، به صورت پیش فرض نام جدول UsersUserGroups، ستون اول UserId و ستون دوم UserGroupId در نظر گرفته خواهد شد که می توان با استفاده از متد Map تغییرات مورد نظر را اعمال کرد. متد Map یک ورودی از نوع Action که پارامتر آن از نوع ManyToManyAssociationMappingConfiguration دارد که سه متد زیر را در اختیار ما قرار می دهد:

  1. ToTable: برای تعیین جدول ManyToMany
  2. MapLeftKey: برای تعیین کلید خارجی موجودیت اول
  3. MapRightKey: برای تعیین کلید خارجی موجودیت دوم

برای کلاس های بالا تعریف زیر را در نظر بگیرید:

modelBuilder.Entity()
    .HasMany(user => user.UserGroups)
    .WithMany(userGroup => userGroup.Users)
    .Map(configuration => configuration.MapLeftKey("UserID").MapLeftKey("UserGroupID").ToTable("UsersAttachedGroups"));

در اینجا نام جدول Many-To-Many را UsersAttachedGroups و ستون های آن را به ترتیب UserID و UserGroupID تعریف کردیم.

نکته ای که باید در نظر گرفت استفاده به جا از MapLeftKey و MapRightKey می باشد. به خاطر داشته باشید که همیشه MapLeftKey مربوط به کلید خارجی کلاسی است که به عنوان پارامتر جنریک در متد Entity استفاده شده و MapRightKey برای کلاس مقابل آن است

می توان کلید های خارجی را با استفاده از ForeignKey Attribute نیز مشخص کرد.

رابطه One-To-One

این رابطه به این صورت است که دو رابطه بین دو جدول بر اساس کلید های اولیه مشخص می شود. برای روشن شدن موضوع کلاس های زیر را در نظر بگیرید:

public class User
{
    public int UserID { get; set; }
    public UserProfile Profile { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
}

public class UserProfile
{
    public int UserProfileID { get; set; }
    public User User { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public byte Age { get; set; }
}

کلاس User دارای یک UserProfile و کلاس UserProfile دارای یک User است. در این رابطه که در کلاس بالا مشاهده می کنید، در جدول UserProfile ستون UserID هم به عنوان کلید اولیه و هم به عنوان کلید خارجی در نظر گرفته خواهد شد.

برای تعریف این رابطه با Fluent API کد زیر را در متد OnModelCreating بنویسید:

modelBuilder.Entity()
    .HasRequired(user => user.Profile)
    .WithRequiredDependent(profile => profile.User);

اگر بخواهیم خصوصیت Profile برای User اختیاری باشد به صورت زیر می نویسیم:

modelBuilder.Entity()
    .HasRequired(user => user.Profile)
    .WithOptional(profile => profile.User);

متدهای دیگری برای رابطه های One-To-One کاربرد دارند که عبارتند از RequiredPrincipal و RequiredDepend که در بخش های بعدی در مورد این متدها بیشتر توضیح خواهیم داد. همچنین نوع دیگری از رابطه ها وجود دارند با نام Complex Types که توضیحات مربوط به آن را نیز به بخش های بعد موکول می کنیم.

موفق و پیروز باشید...
عنوان
1 آموزش Entity Framework قسمت 1 : معرفی و شروع به کار رایگان
2 آموزش Entity Framework قسمت 2 : کلاسهای DbContext و DbSet رایگان
3 آموزش Entity Framework قسمت 3 : تعریف Mapping با Attribute ها رایگان
4 آموزش Entity Framework قسمت 4 : تعریف Mapping با Fluent API رایگان
5 آموزش Entity Framework قسمت 5 : تعریف رابطه ها بخش 1 رایگان
6 آموزش Entity Framework قسمت 6 : تعریف رابطه ها بخش 2 رایگان
زمان و قیمت کل 0″ 0
12 نظر
برنامه نویس

سلام من همه مقاله هارو خواندم عالی بود

کاشکی migration و همچنین نحوه آپلود روی سرور و بروز رسانی دیتابیس کدفرس روی سرور رو هم مگفتین

و یک نکته در مورد این مقاله

.Map(configuration => configuration.MapLeftKey("UserID").MapLeftKey("UserGroupID").ToTable("UsersAttachedGroups"));

فکر کنم باید MapLeftKey دوم به MapRightKey تغییر کنه (مطمئن نیستم)

حسین احمدی

سلام دوست عزیز، ممنون از لطفتون، Map ای که نوشته شده درسته، همیشه این فرمول رو به یاد داشته باشید، MapLeftKey اشاره می کنه به HasMany دوم و MapRightKey اشاره میکنه به HasMany اول.

امیر قراجه داغی

با سلام ، مقاله عالی بود .

خصوصا بحث رابطه ها ، به این واضحی در جای دیگری توضیح داده نشده ..

بحث Migrations رو هم امکانش باشه ادامه بدین

با تشکر

سبحان مظفری

درود بر شما

و سپاس برای مطلبتون من واقعا استفاده کردم خیلی ممنون

فقط یک سوال

اگر اجازه بدیم خود ef کیلد خارجی بسازه و خودمون نسازیم و مشخص نکنیم مشکلش چیه اینجوری؟

حسین احمدی

سلام و عرض ادب، مشکلی پیش نمیاد، تو اون حالت خود EF نام کلید خارجی و تنظیمات رو مشخص میکنه، اما در کل مشکلی پیش نمیاد.

EmadFhx

سلام مهندس احمدی

خیلی مچکرم برای آموزش رابطه ها واقعا فوق‌العاده بود فقط یک سوال داشتم من یک جای رو گیر کردم

من 2 تا موجودیت دارم و 1 جدول رابط

یک جدول به نام course و یک جدول به نام Student و یک جدول کمکی هم به نام selected course که ایدی course و student را برمیداره حالا من توسط متد WillCascadeOnDelete رابطه رو درست کردم داخل کانفیک selected course اما من وقتی یک موجودیت از student پاک میکنم این انتخاب واحد از داخل selected course حذف میشه اما وقتی یک درس رو از course حذف میکنم ارور میده و نمیزاره حذف کنم!

ارتباط :

Hasrequired(e=>e.student).Withmany(e=>e.course).WillCascadeOnDelete();

این هم نوع ارتباط جدول ها هست

حسین احمدی

سلام، وقت بخیر

پیام خطایی که دریافت می کنید چیه؟

EmadFhx

وقتی که موجودیتی رو از جدول course حذف میکنم این خطا رو میده :

An error occurred while updating the entries. See the inner exception for details

حسین احمدی

تو پنجره Exceptions یه قسمت هست به نام Inner Exception، اطلاعات اون خطا رو بزارید.

EmadFhx

این ارور داخل پنجره exception

An unhandled exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll

Additional information: An error occurred while updating the entries. See the inner exception for details.

و متن کاملش با جزئیات "

System.Data.Entity.Infrastructure.DbUpdateException was unhandled
  HResult=-2146233087
  Message=An error occurred while updating the entries. See the inner exception for details.
  Source=EntityFramework
  StackTrace:
       at System.Data.Entity.Internal.InternalContext.SaveChanges()
       at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
       at System.Data.Entity.DbContext.SaveChanges()
       at CodeFirstApplication.FrmCourse.button2_Click(Object sender, EventArgs e) in E:\MyProject\CodeFirstApplication\CodeFirstApplication\FrmCourse.cs:line 77
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.RunDialog(Form form)
       at System.Windows.Forms.Form.ShowDialog(IWin32Window owner)
       at System.Windows.Forms.Form.ShowDialog()
       at CodeFirstApplication.MainForm.button2_Click(Object sender, EventArgs e) in E:\MyProject\CodeFirstApplication\CodeFirstApplication\MainForm.cs:line 36
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.Run(Form mainForm)
       at CodeFirstApplication.Program.Main() in E:\MyProject\CodeFirstApplication\CodeFirstApplication\Program.cs:line 19
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 
       HResult=-2146233087
       Message=An error occurred while updating the entries. See the inner exception for details.
       Source=EntityFramework
       StackTrace:
            at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()
            at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.<Update>b__2(UpdateTranslator ut)
            at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func`2 updateFunction)
            at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update()
            at System.Data.Entity.Core.Objects.ObjectContext.<SaveChangesToStore>b__35()
            at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
            at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
            at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass2a.<SaveChangesInternal>b__27()
            at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
            at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
            at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)
            at System.Data.Entity.Internal.InternalContext.SaveChanges()
       InnerException: 
            Class=16
            ErrorCode=-2146232060
            HResult=-2146232060
            LineNumber=1
            Message=The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.SelectedCoures_dbo.Courses_Course_ID". The conflict occurred in database "Db_HighSchool", table "dbo.SelectedCoures", column 'Course_ID'.
The statement has been terminated.
            Number=547
            Procedure=""
            Server=.
            Source=.Net SqlClient Data Provider
            State=0
            StackTrace:
                 at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
                 at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
                 at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
                 at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
                 at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
                 at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
                 at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
                 at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
                 at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
                 at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<NonQuery>b__0(DbCommand t, DbCommandInterceptionContext`1 c)
                 at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
                 at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.NonQuery(DbCommand command, DbCommandInterceptionContext interceptionContext)
                 at System.Data.Entity.Internal.InterceptableDbCommand.ExecuteNonQuery()
                 at System.Data.Entity.Core.Mapping.Update.Internal.DynamicUpdateCommand.Execute(Dictionary`2 identifierValues, List`1 generatedValues)
                 at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()
            InnerException: 

error

حسین احمدی

سلام، وقت بخیر

Case ای که تعریف می کنید یک طرفه هست، یعنی از یک سمت اعمال میشه. کارکرد سیستم درسته و نباید Student حذف بشه، چون ممکنه Student یک سری Course های دیگه هم داشته باشه و حذفش منطقی نیست. درستش اینه که با حذف Student موجودیت های Course مربوطه حذف بشه.

ZimaSystem

مهندس این که یک properties را به صورت Virtual تعریف کنیم یا به صورت معمول چه فرقی دارند متوجه این موضوع نمیشم

نظر شما
برای ارسال نظر باید وارد شوید.
از سرتاسر توسینسو
تنظیمات حریم خصوصی
تائید صرفنظر
×

تو می تونی بهترین نتیجه رو تضمینی با بهترین های ایران بدست بیاری ، پس مقایسه کن و بعد خرید کن : فقط توی جشنواره پاییزه می تونی امروز ارزونتر از فردا خرید کنی ....