اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
هفت دقیقه
همواره حذف و به روز رسانی تعداد زیادی رکورد توسط EF، بسیار غیربهینه و کند بودهاست؛ از این جهت که یکی از روشهای انجام اینکار، کوئری گرفتن از رکوردهای مدنظر جهت حذف، سپس بارگذاری آنها در حافظه و در آخر حذف یکی یکی آنها بودهاست:
در اینجا در ابتدا، شیءای که قرار است حذف شود، از بانک اطلاعاتی کوئری گرفته میشود تا وارد سیستم Change Tracking شود. سپس از این سیستم ردیابی اطلاعات درون حافظهای، حذف خواهد شد و در نهایت این تغییرات به بانک اطلاعاتی اعمال میشوند. بنابراین در این مثال ساده، حداقل دوبار رفت و برگشت به بانک اطلاعاتی وجود خواهد داشت.
البته راه دومی نیز برای انجام اینکار وجود دارد:
در این مثال، رفت و برگشت ابتدایی، حذف شدهاست و با فرض معلوم بودن کلید اصلی رکورد مدنظر، آنرا وارد سیستم Change Tracking کرده و درنهایت آنرا حذف میکنیم. کار متد Remove در اینجا، علامتگذاری این شیء دارای Id، به صورت EntityState.Deleted است.
اکنون میتوان در EF 7.0، روش سومی را نیز به این لیست اضافه کرد که فقط یکبار رفت و برگشت به بانک اطلاعاتی را سبب میشود:
معرفی متدهای حذف و بهروز رسانی دستهای رکوردها در EF 7.0
EF 7.0 به همراه دو متد جدید ExecuteUpdate و ExecuteDelete (و همچنین نگارشهای async آنها) است که کار بهروز رسانی و یا حذف دستهای رکوردها را بدون دخالت سیستم Change tacking میسر میکنند. مزیت مهم این روش، عدم نیاز به کوئری گرفتن از بانک اطلاعاتی جهت بارگذاری رکوردهای مدنظر در حافظه و سپس حذف یکی یکی آنها است. فقط باید دقت داشت که چون این روش خارج از سیستم Change tracking صورت میگیرد، نتیجهی حاصل، دیگر با اطلاعات درون حافظهای سمت کلاینت، هماهنگ نخواهد بود و کار به روز رسانی دستی آنها بهعهدهی شماست.
بررسی نحوهی عملکرد ExecuteUpdate و ExecuteDelete با یک مثال
فرض کنید مدلهای موجودیتهای برنامه شامل کلاسهای زیر هستند:
که در اینجا یک کاربر میتواند دارای یک آدرس و چندین کتاب تعریف شده باشد؛ با این Context ابتدایی:
مثال 1: حذف دستهای تعدادی کتاب
در اینجا نحوهی استفاده از متد ExecuteDelete را مشاهده میکنید که به انتهای LINQ Query، اضافه شدهاست. در این مثال، تمام کتابهایی که در نامشان حرف 1 وجود دارد، حذف میشوند. این کوئری، به صورت زیر بر روی بانک اطلاعاتی اجرا میشود:
مهمترین مزیت این روش، عدم نیاز به بارگذاری و یا ساخت درون حافظهای لیست کتابهایی است که قرار است حذف شوند. کل این عملیات در یک رفت و برگشت ساده و سریع انجام میشود.
یک نکته: متد ExecuteDelete، تعداد رکوردهای حذف شده را نیز بازگشت میدهد.
مثال 2: حذف کاربران و تمام رکوردهای وابسته به آن
فرض کنید میخواهیم تعدادی از کاربران را از بانک اطلاعاتی حذف کنیم:
اگر این کوئری را با تنظیمات فعلی اجرا کنیم، با خطای زیر متوقف خواهیم شد:
عنوان میکند که یک کاربر، دارای تعدادی کتاب و آدرسی از پیش ثبت شدهاست و نمیتوان آنرا بدون حذف وابستگیهای آن، حذف کرد. اگر کاربری را حذف کنیم، کلیدهای خارجی ذکر شدهی در جداولی که این کلید خارجی را به همراه دارند، غیرمعتبر میشوند (و این کلید خارجی تعریف شده، نال پذیر هم نیست). برای رفع این مشکل، یا باید ابتدا در طی دستوراتی جداگانه، وابستگیهای ممکن را حذف کنیم و یا میتوان تنظیم cascade delete را به نحو زیر به تعریف جداول مرتبط اضافه کرد تا صدور یک دستور delete، به صورت خودکار سبب حذف وابستگیهای مرتبط نیز شود:
همانطور که ملاحظه میکنید، به متد OnModelCreating تنظیم cascade delete وابستگیهای جدول کاربران اضافه شدهاست. پس از این تنظیم، دستور مثال دوم، بدون مشکل اجرا شده و حذف یک کاربر، سبب حذف خودکار کتابها و آدرس او نیز میشود.
مثال 3: بهروز رسانی دستهای از کاربران
فرض کنید میخواهیم LastName تعدادی کاربر مشخص را به مقدار جدید Updated، تغییر دهیم:
برای اینکار، پس از مشخص شدن شرط کوئری در قسمت Where، کار به روز رسانی توسط متد ExecuteUpdate و سپس متد SetProperty صورت میگیرد. در اینجا در ابتدا مشخص میکنیم که کدام خاصیت قرار است به روز رسانی شود و پارامتر دوم آن، مقدار جدید را مشخص میکند. این کوئری به نحو زیر به بانک اطلاعاتی اعمال خواهد شد:
در اینجا میتوان در پارامتر دوم متد SetProperty، از مقدار فعلی سایر خواص نیز استفاده کرد:
که خروجی زیر را تولید میکند:
همچنین میتوان چندین متد SetProperty را نیز به صورت زنجیروار، جهت به روز رسانی چندین خاصیت و فیلد، ذکر کرد:
با این خروجی نهایی:
متد ExecuteUpdate، تعداد رکوردهای بهروز رسانی شده را نیز بازگشت میدهد.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EF7BulkOperations.zip
using var dbContext = new MyDbContext(); var objectToDelete = await dbContext.Objects.FirstAsync(o => o.Id == id); dbContext.Objects.Remove(objectToDelete); await dbContext.SaveChangesAsync();
البته راه دومی نیز برای انجام اینکار وجود دارد:
using var dbContext = new MyDbContext(); var objectToDelete = new MyObject { Id = id }; dbContext.Objects.Remove(objectToDelete); await dbContext.SaveChangesAsync();
اکنون میتوان در EF 7.0، روش سومی را نیز به این لیست اضافه کرد که فقط یکبار رفت و برگشت به بانک اطلاعاتی را سبب میشود:
await dbContext.Objects.Where(x => x.Id == id).ExecuteDeleteAsync();
معرفی متدهای حذف و بهروز رسانی دستهای رکوردها در EF 7.0
EF 7.0 به همراه دو متد جدید ExecuteUpdate و ExecuteDelete (و همچنین نگارشهای async آنها) است که کار بهروز رسانی و یا حذف دستهای رکوردها را بدون دخالت سیستم Change tacking میسر میکنند. مزیت مهم این روش، عدم نیاز به کوئری گرفتن از بانک اطلاعاتی جهت بارگذاری رکوردهای مدنظر در حافظه و سپس حذف یکی یکی آنها است. فقط باید دقت داشت که چون این روش خارج از سیستم Change tracking صورت میگیرد، نتیجهی حاصل، دیگر با اطلاعات درون حافظهای سمت کلاینت، هماهنگ نخواهد بود و کار به روز رسانی دستی آنها بهعهدهی شماست.
بررسی نحوهی عملکرد ExecuteUpdate و ExecuteDelete با یک مثال
فرض کنید مدلهای موجودیتهای برنامه شامل کلاسهای زیر هستند:
public class User { public int Id { get; set; } public required string FirstName { get; set; } public required string LastName { get; set; } public virtual List<Book> Books { get; set; } = new(); public virtual Address? Address { get; set; } } public class Book { public int Id { get; set; } public required string Type { get; set; } public required string Name { get; set; } public virtual User User { get; set; } = default!; public int UserId { get; set; } } public class Address { public int Id { get; set; } public required string Street { get; set; } public virtual User User { get; set; } = default!; public int UserId { get; set; } }
public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<User> Users { get; set; } = default!; public DbSet<Book> Books { get; set; } = default!; public DbSet<Address> Addresses { get; set; } = default!; }
مثال 1: حذف دستهای تعدادی کتاب
context.Books.Where(book => book.Name.Contains("1")).ExecuteDelete();
DELETE FROM [b] FROM [Books] AS [b] WHERE [b].[Name] LIKE N'%1%'
یک نکته: متد ExecuteDelete، تعداد رکوردهای حذف شده را نیز بازگشت میدهد.
مثال 2: حذف کاربران و تمام رکوردهای وابسته به آن
فرض کنید میخواهیم تعدادی از کاربران را از بانک اطلاعاتی حذف کنیم:
context.Users.Where(user => user.Id <= 500).ExecuteDelete();
DELETE FROM [u] FROM [Users] AS [u] WHERE [u].[Id] <= 500 The DELETE statement conflicted with the REFERENCE constraint "FK_Books_Users_UserId". The conflict occurred in database "EF7BulkOperations", table "dbo.Books", column 'UserId'.
public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<User> Users { get; set; } = default!; public DbSet<Book> Books { get; set; } = default!; public DbSet<Address> Addresses { get; set; } = default!; protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder .Entity<User>() .HasMany(user => user.Books) .WithOne(book => book.User) .OnDelete(DeleteBehavior.Cascade); modelBuilder .Entity<User>() .HasOne(user => user.Address) .WithOne(address => address.User) .HasForeignKey<Address>(address => address.UserId) .OnDelete(DeleteBehavior.Cascade); } }
مثال 3: بهروز رسانی دستهای از کاربران
فرض کنید میخواهیم LastName تعدادی کاربر مشخص را به مقدار جدید Updated، تغییر دهیم:
context.Users.Where(user => user.Id <= 400) .ExecuteUpdate(p => p.SetProperty(user => user.LastName, user => "Updated"));
UPDATE [u] SET [u].[LastName] = N'Updated' FROM [Users] AS [u] WHERE [u].[Id] <= 400
context.Users.Where(user => user.Id <= 300) .ExecuteUpdate(p => p.SetProperty(user => user.LastName, user => "Updated" + user.LastName));
UPDATE [u] SET [u].[LastName] = N'Updated' + [u].[LastName] FROM [Users] AS [u] WHERE [u].[Id] <= 300
context.Users.Where(user => user.Id <= 800) .ExecuteUpdate(p => p.SetProperty(user => user.LastName, user => "Updated" + user.LastName) .SetProperty(user => user.FirstName, user => "Updated" + user.FirstName));
UPDATE [u] SET [u].[FirstName] = N'Updated' + [u].[FirstName], [u].[LastName] = N'Updated' + [u].[LastName] FROM [Users] AS [u] WHERE [u].[Id] <= 800
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EF7BulkOperations.zip