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

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

ذخیره داده ها(Saving Data) :

هر context instance دارای یک ChangeTracker است که مسئول پیگیری تغییراتی است که باید در پایگاه داده نوشته شوند. همانطور که در نمونه هایی از کلاس های موجودیت خود تغییراتی ایجاد می کنید، این تغییرات در ChangeTracker ثبت می شود و سپس با فراخوانی SaveChanges در پایگاه داده نوشته می شود. database provider مسئول ترجمه تغییرات به عملیات خاص پایگاه داده است (به عنوان مثال، دستورات INSERT، UPDATE و DELETE برای یک پایگاه داده رابطه ای).

در ادامه با نحوه افزودن، اصلاح و حذف داده‌ها با استفاده از کلاس‌های entity و context آشنا می شویم.

افزودن داده ها (Adding Data) :

از متد DbSet.Add برای افزودن نمونه های جدید از کلاس های موجودیت خود استفاده کنید. با فراخوانی SaveChanges، داده ها در پایگاه داده درج خواهند شد.

using (var context = new BloggingContext())
{
    var blog = new Blog { Url = &quothttp://example.com&quot };
    context.Blogs.Add(blog);
    context.SaveChanges();
}


بروزرسانی داده ها (Updating Data) :

همانطورکه در پست های قبلی نیز به آن اشاره شد EF به طور خودکار تغییرات ایجاد شده در موجودیت ها را که توسط context ردیابی می شود شناسایی می کند. این شامل موجودیت هایی است که از پایگاه داده بارگیری/پرس و جو می کنید و موجودیت هایی که قبلاً به پایگاه داده اضافه و ذخیره شده اند.

به سادگی مقادیر اختصاص داده شده به property را تغییر دهید و سپس SaveChanges را فراخوانی کنید.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.First();
    blog.Url = &quothttp://example.com/blog&quot
    context.SaveChanges();
}


حذف داده ها(Deleting Data) :

از متد DbSet.Remove برای حذف نمونه هایی از کلاس های موجودیت خود استفاده کنید.

اگر موجودیت از قبل در پایگاه داده وجود داشته باشد، در طول SaveChanges حذف خواهد شد. اگر موجودیت هنوز در پایگاه داده ذخیره نشده باشد (یعنی به عنوان added ردیابی شود) از context حذف می شود و با فراخوانی SaveChanges دیگر درج نمی شود.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.First();
    context.Blogs.Remove(blog);
    context.SaveChanges();
}


چندین عملیات در یک SaveChanges :

می توانید چندین عملیات Add/Update/Remove را در یک تماس با SaveChanges ترکیب کنید.

برای اکثر ارائه دهندگان پایگاه داده، SaveChanges به صورت transactional است. این بدان معنی است که همه عملیات یا موفق خواهند شد یا شکست می خورند و عملیات هرگز به طور جزئی اعمال نمی شوند.
using (var context = new BloggingContext())
{
    // seeding database
    context.Blogs.Add(new Blog { Url = &quothttp://example.com/blog&quot });
    context.Blogs.Add(new Blog { Url = &quothttp://example.com/another_blog&quot });
    context.SaveChanges();
}
using (var context = new BloggingContext())
{
    // add
    context.Blogs.Add(new Blog { Url = &quothttp://example.com/blog_one&quot });
    context.Blogs.Add(new Blog { Url = &quothttp://example.com/blog_two&quot });
    // update
    var firstBlog = context.Blogs.First();
    firstBlog.Url = &quot&quot
    // remove
    var lastBlog = context.Blogs.OrderBy(e => e.BlogId).Last();
    context.Blogs.Remove(lastBlog);
    context.SaveChanges();
}


ذخیره داده های مرتبط(Saving Related Data) :

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

اضافه کردن یک گراف از موجودیت های جدید(Adding a graph of new entities) :

اگر چندین موجودیت مرتبط جدید ایجاد کنید، افزودن یکی از آنها به context باعث می شود بقیه نیز اضافه شوند.

در مثال زیر، blog و سه پست مرتبط همگی در پایگاه داده درج شده اند. پست ها پیدا و اضافه می شوند، زیرا از طریق ویژگی ناوبری Blog.Posts قابل دسترسی هستند.

using (var context = new BloggingContext())
{
    var blog = new Blog
    {
        Url = &quothttp://blogs.msdn.com/dotnet&quot,
        Posts = new List<Post>
        {
            new Post { Title = &quotIntro to C#&quot },
            new Post { Title = &quotIntro to VB.NET&quot },
            new Post { Title = &quotIntro to F#&quot }
        }
    };
    context.Blogs.Add(blog);
    context.SaveChanges();
}

از ویژگی EntityEntry.State برای تنظیم وضعیت فقط یک موجودیت استفاده کنید. برای مثال :

context.Entry(blog).State=EntityState.Modified

افزودن یک موجودیت مرتبط(Adding a related entity) :

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

در مثال زیر، موجودیت پست درج شده است زیرا به ویژگی Posts موجودیت Blog که از پایگاه داده واکشی شده است اضافه شده است.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Include(b => b.Posts).First();
    var post = new Post { Title = &quotIntro to EF Core&quot };
    blog.Posts.Add(post);
    context.SaveChanges();
}


تغییر روابط(Changing relationships) :

اگر navigation property یک موجودیت را تغییر دهید، تغییرات مربوطه در ستون کلید خارجی در پایگاه داده اعمال می شود.

در مثال زیر، موجودیت post به‌روزرسانی می‌شود تا به موجودیت blog جدید تعلق داشته باشد، زیرا ویژگی Blog navigation آن به blog تنظیم شده است. توجه داشته باشید که blog همچنین در پایگاه داده درج می شود زیرا یک موجودیت جدید است که توسط ویژگی ناوبری موجودیتی که قبلاً توسط context (پست) ردیابی شده است، ارجاع داده می شود.

using (var context = new BloggingContext())
{
    var blog = new Blog { Url = &quothttp://blogs.msdn.com/visualstudio&quot };
    var post = context.Posts.First();
    post.Blog = blog;
    context.SaveChanges();
}


حذف روابط (Removing relationships) :

می توانید یک رابطه را با تنظیم یک reference navigation روی null یا حذف موجودیت مرتبط از collection navigation حذف کنید.

با توجه به cascade delete behavior پیکربندی شده در رابطه، حذف یک رابطه می تواند اثرات جانبی بر موجودیت وابسته داشته باشد.

به طور پیش فرض، برای روابط required، یک cascade delete behavior پیکربندی شده است و موحودیت فرزند/وابسته از پایگاه داده حذف خواهد شد. برای روابط اختیاری، cascade delete به طور پیش‌فرض پیکربندی نشده است، اما ویژگی کلید خارجی روی null تنظیم می‌شود.

در مثال زیر، یک cascade delete بر روی رابطه بین Blog و Post پیکربندی شده است، بنابراین موجودیت پست از پایگاه داده حذف می شود.

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Include(b => b.Posts).First();
    var post = blog.Posts.First();
    blog.Posts.Remove(post);
    context.SaveChanges();
}


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

خوب Entity Framework Core (EF Core) روابطی را با استفاده از کلیدهای خارجی(foreign keys) نشان
می دهد. یک موجودیت با یک کلید خارجی، فرزند یا موجودیت وابسته در رابطه است. ارزش کلید خارجی این موجودیت باید با مقدار کلید اصلی (یا یک مقدار کلید جایگزین/alternate key) principal/parent مرتبط مطابقت داشته باشد.

اگر موجودیت اصلی/parent حذف شود، مقادیر کلید خارجی وابستگان/فرزندان دیگر با کلید اصلی یا جایگزین هیچ یک از principal/parentها مطابقت نخواهد داشت. این یک حالت نامعتبر است و باعث نقض محدودیت ارجاعی(referential constraint violation) در اکثر پایگاه های داده می شود.

دو گزینه برای جلوگیری از نقض محدودیت ارجاعی (referential constraint violation) وجود دارد:

  • مقادیر FK را روی null قرار دهید.
  • موجودیت های وابسته/فرزند را حذف کنید.

گزینه اول فقط برای روابط اختیاری معتبر است که خاصیت کلید خارجی (و ستون پایگاه داده ای که به آن نگاشت شده است) باید null باشد.

گزینه دوم برای هر نوع رابطه معتبر است و به "حذف آبشاری/Cascade Delete" معروف است.


وقتی رفتارهای آبشاری(cascading behaviors) اتفاق می افتد :

زمانی که یک موجودیت dependent/child دیگر نمی تواند با principal/parent فعلی خود مرتبط باشد، حذف های آبشاری مورد نیاز است. این ممکن است به این دلیل اتفاق بیفتد که principal/parent حذف شده است، یا زمانی اتفاق بیفتد که principal/parent هنوز وجود داشته باشد اما dependent/child دیگر با آن ارتباط نداشته باشد.


حذف principal/parent :

این مدل ساده را در نظر بگیرید که در آن بلاگ principal/parent در رابطه با پست است که dependent/child است. Post.BlogId یک ویژگی کلید خارجی است که مقدار آن باید با کلید اصلی Blog.Id بلاگی که پست به آن تعلق دارد مطابقت داشته باشد.

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<Post> Posts { get; } = new List<Post>();
}
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

طبق قرارداد، این رابطه به عنوان یک required پیکربندی می شود، زیرا ویژگی کلید خارجی Post.BlogId غیر قابل تهی(non-nullable) است. روابط مورد نیاز برای استفاده از حذف های آبشاری به طور پیش فرض پیکربندی شده اند.

هنگام حذف یک وبلاگ، تمام پست ها به صورت آبشاری حذف می شوند. مثلا:

using var context = new BlogsContext();
var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First();
context.Remove(blog);
context.SaveChanges();


قطع رابطه(Severing a relationship) :

به جای حذف بلاگ، می توانیم رابطه بین هر پست و بلاگ آن را قطع کنیم. این کار را می توان با تنظیم ناوبری مرجع Post.Blog برای هر پست به صورت null انجام داد:

using var context = new BlogsContext();
var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First();
foreach (var post in blog.Posts)
{
    post.Blog = null;
}
context.SaveChanges();

این رابطه همچنین می تواند با حذف هر پست از Blog.Posts ،collection navigation قطع شود:

using var context = new BlogsContext();
var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First();
blog.Posts.Clear();
context.SaveChanges();

حذف موجودیت هایی که دیگر با هیچ principal/dependent مرتبط نیستند به عنوان "حذف یتیمان/deleting orphans" شناخته می شود.

حذف آبشاری و حذف یتیمان ارتباط نزدیکی با هم دارند. هر دو منجر به حذف نهادهای dependent/child در صورت قطع رابطه با required principal/parent آنها می شود. برای حذف آبشاری، این جداسازی به این دلیل اتفاق می‌افتد که خود principal/parent حذف شده است. برای یتیمان، موجودیت principal/parent هنوز وجود دارد، اما دیگر با نهادهای dependent/child مرتبط نیست.


جایی که رفتارهای آبشاری(cascading behaviors) اتفاق می افتد :

رفتارهای آبشاری را می توان در موارد زیر اعمال کرد:

  • موجودیت هایی که توسط DbContext فعلی ردیابی می شوند.
  • موجودیت های ذخیره شده در پایگاه داده که در context بارگذاری نشده اند.

حذف آبشاری موجودیت های ردیابی شده(Cascade delete of tracked entities) :

خوب EF Core همیشه رفتارهای آبشاری پیکربندی شده را برای موجودیت های ردیابی شده اعمال می کند. این بدان معنی است که اگر برنامه همه موجودیت‌های dependent/child مرتبط را در DbContext بارگذاری کند، همانطور که در مثال‌های بالا نشان داده شده است، رفتارهای آبشاری بدون توجه به نحوه پیکربندی پایگاه داده به درستی اعمال می‌شوند.

حذف آبشاری در پایگاه داده (Cascade delete in the database) :

بسیاری از سیستم های پایگاه داده نیز رفتارهای آبشاری را ارائه می دهند که هنگام حذف یک موجودیت در پایگاه داده ایجاد می شوند. EF Core این رفتارها را بر اساس رفتار حذف آبشاری در مدل EF Core هنگامی که پایگاه داده با استفاده از migrationهای EnsureCreated یا EF Core ایجاد می شود، پیکربندی می کند. به عنوان مثال، با استفاده از مدل بالا، جدول زیر برای پست ها هنگام استفاده از SQL Server ایجاد می شود:

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    [Content] nvarchar(max) NULL,
    [BlogId] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]) ON DELETE CASCADE
);

توجه داشته باشید که محدودیت کلید خارجی(foreign key constraint) که رابطه بین بلاگ ها و پست ها را تعریف می کند با ON DELETE CASCADE پیکربندی شده است.
اگر بدانیم که پایگاه داده به این صورت پیکربندی شده است، می‌توانیم یک بلاگ را بدون بارگذاری پست اول حذف کنیم و پایگاه داده تمام پست‌های مربوط به آن وبلاگ را حذف خواهد کرد. مثلا:

using var context = new BlogsContext();
var blog = context.Blogs.OrderBy(e => e.Name).First();
context.Remove(blog);
context.SaveChanges();

توجه داشته باشید که هیچ Include برای پست ها وجود ندارد، بنابراین آنها بارگذاری نمی شوند. SaveChanges در این مورد فقط blog را حذف می کند، زیرا این تنها موجودیتی است که ردیابی می شود.

اگر محدودیت کلید خارجی در پایگاه داده برای حذف های آبشاری پیکربندی نشده باشد، منجر به یک استثنا
می شود. با این حال، در این مورد، پست‌ها توسط پایگاه داده حذف می‌شوند، زیرا هنگام ایجاد آن با ON DELETE CASCADE پیکربندی شده است.

پایگاه‌های داده معمولاً راهی برای حذف خودکار orphanها ندارند. این به این دلیل است که در حالی که EF Core روابط را با استفاده از ناوبری و همچنین کلیدهای خارجی نشان می دهد، پایگاه های داده فقط دارای کلیدهای خارجی هستند و هیچ ناوبری ندارند. این بدان معنی است که معمولاً نمی توان یک رابطه را بدون بارگیری هر دو طرف در DbContext قطع کرد.

در حال حاضر EF Core از حذف های آبشاری در in-memory database پشتیبانی نمی کند.

هنگام soft-deleting موجودیت ها، cascade delete را در پایگاه داده پیکربندی نکنید. این ممکن است باعث شود که موجودیت ها به‌جای حذف نرم، به‌طور تصادفی واقعاً حذف شوند.


محدودیت های آبشاری پایگاه داده(Database cascade limitations) :

برخی از پایگاه های داده، به ویژه SQL Server، دارای محدودیت هایی در رفتارهای آبشاری هستند که چرخه ها را تشکیل می دهند. برای مثال مدل زیر را در نظر بگیرید:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<Post> Posts { get; } = new List<Post>();
    public int OwnerId { get; set; }
    public Person Owner { get; set; }
}
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
    public int AuthorId { get; set; }
    public Person Author { get; set; }
}
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<Post> Posts { get; } = new List<Post>();
    public Blog OwnedBlog { get; set; }
}

این مدل دارای سه رابطه است که همه آنها required است و بنابراین برای حذف آبشاری بر اساس قرارداد پیکربندی شده است:

  • حذف یک وبلاگ باعث حذف تمام پست های مرتبط می شود.
  • حذف نویسنده پست‌ها باعث می‌شود که پست‌های نوشته شده به صورت آبشاری حذف شوند.
  • حذف مالک وبلاگ باعث حذف آبشاری وبلاگ می شود.

همه اینها منطقی است. اما تلاش برای ایجاد پایگاه داده SQL Server با این آبشارها به استثنای زیر منجر
می شود:

Microsoft.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Posts_Person_AuthorId' on table 'Posts' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

دو راه برای مدیریت این وضعیت وجود دارد:

  • یک یا چند رابطه را به گونه ای تغییر دهید که cascade delete نشود.
  • پایگاه داده را بدون یک یا چند مورد از این حذف های آبشاری پیکربندی کنید، سپس مطمئن شوید که همه موجودیت های وابسته بارگذاری شده اند تا EF Core بتواند رفتار آبشاری را انجام دهد.

با در نظر گرفتن اولین رویکرد با مثال خود، می‌توانیم رابطه مالک وبلاگ را با دادن یک ویژگی کلید خارجی nullable به آن اختیاری کنیم:

public int? BlogId { get; set; }

یک رابطه اختیاری به بلاگ اجازه می دهد بدون مالک وجود داشته باشد، به این معنی که حذف آبشاری دیگر به طور پیش فرض پیکربندی نخواهد شد. این بدان معنی است که دیگر چرخه ای در اقدامات آبشاری وجود ندارد و پایگاه داده می تواند بدون خطا در SQL Server ایجاد شود.

در عوض با اتخاذ رویکرد دوم، می‌توانیم رابطه مالک وبلاگ را برای حذف آبشاری required پیکربندی کنیم، اما این پیکربندی را فقط برای موجودیت‌های ردیابی شده اعمال کنیم، نه پایگاه داده:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Blog>()
        .HasOne(e => e.Owner)
        .WithOne(e => e.OwnedBlog)
        .OnDelete(DeleteBehavior.ClientCascade);
}

حالا چه اتفاقی می‌افتد اگر هم یک شخص و هم وبلاگ متعلق به او را بارگذاری کنیم، سپس آن شخص را حذف کنیم؟

using var context = new BlogsContext();
var owner = context.People.Single(e => e.Name == &quotajcvickers&quot);
var blog = context.Blogs.Single(e => e.Owner == owner);
context.Remove(owner);
context.SaveChanges();

در اینجا EF Core حذف مالک را آبشاری می کند تا وبلاگ نیز حذف شود.

با این حال، اگر بلاگ هنگام حذف مالک بارگیری نمی شود:

using var context = new BlogsContext();
var owner = context.People.Single(e => e.Name == &quotajcvickers&quot);
context.Remove(owner);
context.SaveChanges();

سپس یک استثنا به دلیل نقض محدودیت کلید خارجی در پایگاه داده ایجاد می شود:

Microsoft.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "FK_Blogs_People_OwnerId". The conflict occurred in database "Scratch", table "dbo.Blogs", column 'OwnerId'. The statement has been terminated.


ـCascading nulls :

روابط اختیاری دارای ویژگی‌های nullable foreign key هستند که به ستون‌های پایگاه داده nullable نگاشت شده‌اند. این به این معنی است که مقدار کلید خارجی را می توان زمانی که principal/parent فعلی حذف یا از dependent/child جدا شد، روی null تنظیم کرد.

بیایید دوباره به مثال‌های مربوط به وقتی رفتارهای آبشاری اتفاق می‌افتند نگاهی بیندازیم، اما این بار با یک رابطه اختیاری که با nullable Post.BlogId foreign key property نمایش داده می‌شود:

public int? BlogId { get; set; }

این ویژگی کلید خارجی برای هر پست زمانی که وبلاگ مربوط به آن حذف می شود، به صورت nullتنظیم
می شود. به عنوان مثال، این کد که مانند قبل است:

using var context = new BlogsContext();
var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First();
context.Remove(blog);
context.SaveChanges();

به همین ترتیب، اگر رابطه با استفاده از یکی از مثال های بالا قطع شود:

using var context = new BlogsContext();
var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First();
foreach (var post in blog.Posts)
{
    post.Blog = null;
}
context.SaveChanges();

ویا :

using var context = new BlogsContext();
var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First();
blog.Posts.Clear();
context.SaveChanges();

سپس با فراخوانی SaveChanges، پست ها با مقادیر null foreign key به روز می شوند.


پیکربندی cascading behaviors :

رفتارهای آبشاری در هر رابطه با استفاده از متد OnDelete در OnModelCreating پیکربندی می شوند. مثلا:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Blog>()
        .HasOne(e => e.Owner)
        .WithOne(e => e.OwnedBlog)
        .OnDelete(DeleteBehavior.ClientCascade);
}
موجودیت های شرکت کننده در این رابطه می توانند Principal entity/parent entity یا Dependent Entity/ child entity باشند. موجودیت اصلی موجودیتی است که می تواند به تنهایی وجود داشته باشد. موجودیت وابسته به موجودیت اصلی بستگی دارد.
به عنوان مثال، رابطه بین کارمند و واحد دپارتمان را در نظر بگیرید. واحد دپارتمان، نهاد اصلی است زیرا نیازی به نهاد کارمند ندارد. اما کارمندان باید متعلق به یک دپارتمانباشند. بنابراین موجودیت کارمند یک نهاد وابسته است.
کلید خارجی "چسب" بین نهاد اصلی و نهاد وابسته به آن است. می تواند الزامی یا اختیاری باشد. اگر کلید خارجی اختیاری باشد، می‌توانیم مقدار NULL را در آن ذخیره کنیم.
پایگاه داده نباید به ما اجازه دهد که رکورد والد را زمانی که رکورد فرزند دارد حذف کنیم. باید یکپارچگی داده ها را حفظ کند. این کار را با استفاده از "اقدامات مرجع/Referential actions" انجام می دهد. آنها اقداماتی هستند که پایگاه داده هنگام حذف یک رکورد والد انجام می دهد.
به عنوان مثال، در یک پایگاه داده SQL Server چهار عمل مرجع را تعریف می کند :
ON DELETE CASCADE,
ON DELETE SET NULL,
ON DELETE NO ACTION ,
ON DELETE SET DEFAULT
هنگامی که یک رکورد والد را حذف می کنیم، پایگاه داده یکی از اقدامات زیر را بر اساس اقدامات مرجع انجام
می دهد.
هنگامی که یک رکورد والد را حذف می کنیم، پایگاه داده یکی از اقدامات زیر را بر اساس اقدامات مرجع انجام
می دهد :
ON DELETE CASCADE: رکوردهای child را حذف می کند. (یعنی آبشار)
ON DELETE SET NULL: مقدار NULL را به کلید خارجی در جدول Child (یعنی SetNull) اختصاص می دهد.
ON DELETE NO ACTION: هیچ کاری انجام نمی دهد. پایگاه داده در صورت نقض کلید خارجی خطایی ایجاد
می کند. (Restrict/ NoAction)
ON DELETE SET DEFAULT: مقدار پیش فرض را تعیین می کند. اگر مقدار پیش فرض قوانین کلید خارجی را نقض کند، پایگاه داده با خطا مواجه می شود.

خوب Entity Framework Core نیز اقدامات ارجاعی را تعریف می کند. به آنها به عنوان رفتارهای حذف و یا Delete behaviors اشاره می شود.

در اینجا Entity Framework Core دو مجموعه از رفتارهای حذف را تعریف می کند:

  • رفتارهایی که به پایگاه داده نگاشت می شوند.
  • رفتارهایی که به پایگاه داده نگاشت نمی شوند و با پیشوند Client شروع می‌شوند.

از متد OnDelete برای مشخص کردن اقدامی که باید هنگام حذف principal روی یک موجودیت وابسته در یک رابطه انجام شود استفاده می‌شود.

این اقدامات مشخص می‌کنند که وقتی ردیف والد را حذف می‌کنیم، با ردیف‌های مرتبط چه کار کنیم.

گزینه های موجود برای استفاده عبارتند از:

  • حذف ردیف های مرتبط (Cascade / ClientCascade)،
  • به روز رسانی کلید خارجی آن به NULL (SetNull / ClientSetNull) یا انجام هیچ کاری (Restrict/NoAction/ClientNoAction).

متد OnDelete یک DeleteBehavior enum را به عنوان پارامتر می گیرد:

  • ـ Cascade / ClientCascade: وابستگان باید حذف شوند.
  • ـRestrict/NoAction/ClientNoAction : موجودیت وابسته تحت تأثیر قرار نمی گیرند.
  • ـSetNull / ClientSetNull : مقادیر کلید خارجی در ردیف های وابسته باید به NULL به روز شوند.


کمی بالاتر گفتیم Entity Framework Core دو مجموعه از رفتارهای حذف را تعریف می کند بیایید آنها را بررسی کنیم.

رفتارهایی که به پایگاه داده نگاشت می شوند:

در اینجا Cascade، SetNull، NoAction و Restrict در واقع actionهایی هستند که به اقدامات ارجاعی پایگاه داده نگاشت می شوند.

وقتی از رفتارهای بالا برای پیکربندی رابطه استفاده می‌کنید و از EF Core Migration برای ایجاد پایگاه داده استفاده می‌کنید، رفتار آبشاری زیر برای شما تنظیم می‌شود.

اگر پایگاه داده از EF Core migrations ایجاد شده باشد، behaviour مشخص شده به طور خودکار تنظیم
می شود.در غیر اینصورت، باید آنها را به صورت دستی در پایگاه داده پیکربندی کنید.


مثال زیر را در نظر بگیرید:

public class Author
{
    public int AuthorId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public ICollection<Book> Books { get; set; }
}

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public int AuthorId { get; set; }
    public Author Author{ get; set; }
}

با استفاده از Fluent API :

protected override void  OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>()
        .HasMany(p => p.Author)
        .WithOne(t => t.Book)
        .OnDelete(DeleteBehavior.Cascade)
}

نتایج آن به این صورت خواهد بود :

CREATE TABLE [Books] (
[BookID] int NOT NULL IDENTITY,
[Title] nvarchar(max) NULL,
[AuthorId] int NOT NULL,
CONSTRAINT [PK_Books] PRIMARY KEY ([BookID]),
CONSTRAINT [FK_Books_Authors_AuthorID] FOREIGN KEY ([AuthorID]) REFERENCES
[Authors] ([AuthorID]) ON DELETE CASCADE
);

رفتارهایی که به پایگاه داده نگاشت نمی شوند و با پیشوند Client شروع می‌شوند :

اگر از ClientCascade، ClientSetNull یا ClientNoAction استفاده می کنید، پس Migration اقدامات ارجاعی را در پایگاه داده ایجاد نمی کند.
این تنها تفاوت آنهاست.

و اما رفتاری حذف در موجودیت های ردیابی شده(Tracked Entities) :

رفتار حذف(Delete behavior) فقط روی موجودیت هایی که ما در حافظه بارگذاری می کنیم (Tracked Entities) کار می کند. هیچ تاثیری بر موجودیت هایی که بارگذاری نمی کنیم نخواهد داشت.

برای اینکه پایگاه داده بتواند موجودیت‌های مرتبط را حذف کند، باید اقدامات مرجع مانند CASCADE یا
SET NULL را هنگام تنظیم ForeignKey تنظیم کنیم.

به عنوان مثال، پرس و جوی زیر را در نظر بگیرید که در آن Author را با شناسه 3 حذف می کنیم. این پرس و جو هیچ تاثیری بر رکوردهای Book نخواهد داشت زیرا بارگذاری نمی شوند. هنگامی که SaveChanges را فراخوانی
می کنیم، DBContext پرس و جو را برای حذف Author ارسال می کند.

var author = db.Authors.Where(c => c.AuthorID == 3).FirstOrDefault();
db.Authors.Remove(author);
db.SaveChanges();

در پرس و جوی زیر، ما با استفاده از eagerly load موجودیت های مرتبط را با متد Include بارگذاری می کنیم. EF اکنون در حال ردیابی رکوردهای Author و Book است.

var author = db.Authors
.Where(c => c.AuthorID == 3).Include(c=>c.Books).FirstOrDefault();
 db.Authors.Remove(author);
 db.SaveChanges();

حالا بسته به نحوه تنظیم DeleteBehavior در اینجا DbContext یکی از کارهای زیر را انجام می دهد:

  1. DeleteBehavior.Cascade/DeleteBehavior.ClientCascade
    Delete the Book Entities
  2. DeleteBehavior.ClientSetNull/DeleteBehavior.SetNull:
    Updates the AuthorID of Book record to NULL
  3. DeleteBehavior.NoAction
    Updates the AuthorID of Book record to NULL
  4. DeleteBehavior.ClientNoAction:
    Does nothing.
  5. DeleteBehavior.Restrict:
    Updates the AuthorID of Book record to NULL


تمام اقدامات فوق در سمت client اتفاق می افتد. وقتی SaveChanges را فراخوانی می کنیم، DBContext پرس و جو را برای حذف Author و همچنین حذف/به روز رسانی رکوردهای Book ردیابی شده ارسال می کند.

نحوه برخورد با موجودیت ردیابی نشده به delete behavior پایگاه داده بستگی دارد.

اگر هر یک از نتایج عملیات FOREIGN KEY constraint را نقض کند، ردیابی شده یا ردیابی نشده باشد، پایگاه داده با خطا مواجه خواهد شد.


خوب بیایید رفتاری های حذف/Delete behavior را بیشتر بررسی کنیم :

  • رفتار Cascade / ClientCascade :
    وقتی ما والد را حذف می کنیم، Context موجودیت هایی را که ردیابی می کند حذف می کند.گزینه Cascade یک migration script با ON DELETE CASCADE ایجاد می کند، در حالی که ClientCascade یک migration script با ON DELETE NO ACTION ایجاد می کند.Cascade رفتار پیش‌فرض است، زمانی که از رابطه required استفاده می‌کنیم، یعنی کلید خارجی غیر قابل تهی است.
    اگر ON DELETE CASCADE در پایگاه داده راه اندازی شده باشد، موجودیت های ردیابی نشده حذف
    می شوند. در غیر این صورت منجر به نقض کلید خارجی می شود.

    همه پایگاه های داده Cascade را پشتیبانی نمی کنند یا به طور کامل پشتیبانی نمی کنند. به خصوص اگر چرخه هایی(cycles) در روابط وجود داشته باشد. از ClientCascade استفاده کنید و همه موجودیت های مرتبط را قبل از حذف والد بارگیری کنید. contextـ، Cascade Delete را در سمت client انجام می دهد.
  • رفتار SetNull / ClientSetNul :
    وقتی والد را حذف می‌کنیم، Context مقادیر ویژگی‌های کلید خارجی در موجودیت‌های وابسته را در موجودیت‌هایی که ردیابی می‌کند null می‌کند.
    ClientSetNull رفتار پیش‌فرض را هنگام استفاده از رابطه اختیاری نشان می‌دهد، یعنی کلید خارجی nullable است.

    گزینه SetNull یک migration script با ON DELETE SET NULL ایجاد می کند، در حالی که ClientSetNull یک migration script با ON DELETE NO ACTION ایجاد می کند.

    اگر ON DELETE SET NULL در پایگاه داده تنظیم شده باشد، موجودیت های ردیابی نشده نیز در صورت وجود روی NULL تنظیم می شوند. در غیر این صورت منجر به نقض کلید خارجی می شود.
  • رفتار NoAction :
    وقتی والد را حذف می‌کنیم، Context مقادیر ویژگی‌های کلید خارجی در موجودیت‌های وابسته را در موجودیت‌هایی که ردیابی می‌کند null می‌کند.

    هیچ migration script ایجاد نمی کند.
    پایگاه داده در صورت هرگونه نقض FOREIGN KEY با خطا مواجه می شود.
  • رفتار ClientNoAction :
    وقتی ما والد را حذف می کنیم، Context موجودیت هایی را که ردیابی می کند، تغییر نمی دهد یا حذف
    نمی کند.
    هیچ migration script ایجاد نمی کند.
    پایگاه داده در صورت هرگونه نقض FOREIGN KEY با خطا مواجه می شود.
  • رفتار Restrict :
    وقتی والد را حذف می‌کنیم، Context مقادیر ویژگی‌های کلید خارجی موجودیت‌های وابسته را در موجودیت‌هایی که ردیابی می‌کند، null می‌کند.
    گزینه Restrict یک migration script با ON DELETE NO ACTION ایجاد می کند.
پایگاه داده در صورت هرگونه نقضFOREIGN KEY با خطا مواجه می شود.

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

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

https://zarinp.al/farshidazizi