علاوه بر مواردی که ذکر شد، حذف منطقی میتواند به عنوان روشی برای حذف مطرح شود؛ به این صورت که حذف یک رکورد، در دو مرحله صورت گیرد:
- مرحله اول، حذف منطقی: کاربر اقدام به حذف رکورد مورد نظر را میکند. بعد از حذف، خبری از نمایش رکورد مربوطه نخواهد بود .
- مرحله دوم، حذف فیزیکی: مدیر اصلی میتواند تصمیم بگیرد که رکوردهای حذف منطقی شده واقعا حذف شوند یا خیر. فقط مدیر اصلی و سایر افردای که دسترسی حذف مرحله دوم را داشته باشند، از روند دو مرحلهای حذف با خبر هستند.
در ادامه قصد داریم به مزایا و معایب حذف منطقی و روشهایی برای مدیریت آن بپردازیم.
پیشهاد میکنم سری مقالات مفید تحلیل سیستم مدیریت محتوا DTNCMS را حتما مطالعه نمایید.
برای اینکه بخواهیم حذف منطقی را پیاده سازی نماییم، نیاز داریم به هر رکورد، فیلدی اضافه شود تا از طریق آن مشخص نماییم که آیا رکورد حذف شده است یا خیر. برای این منظور باید فیلدی از نوع boolean به تمام کلاسها (جداول) اضافه شود. میتوانیم این فیلد را به صورت زیر تعریف کنیم:
public bool IsDeleted { get; set; }
public interface ISoftDelete { bool IsDeleted { get; set; } }
public class Post :ISoftDelete { }
بعد از افزودن فیلد فوق، نیاز داریم تا در تمام کوئریها شرطی را اضافه نماییم تا فقط رکوردهایی را از دیتابیس واکشی کند که حذف نشدهاند. یعنی فیلد فوق برابر False باشد. در ادامه روشهایی برای این هدف بیان خواهند شد.
روش هایی برای فیلتر رکوردهای حذف شده
1- افزودن فیلتر زیر در تمامی کوئریها:
where (IsDeleted=false && ...)
2- نوشتن یک متد الحاقی:
برای جلوگیری از تکرار شرط فوق میتوان یک متد الحاقی را به صورت زیر پیاده سازی نمود و در تمامی شرطها، آن را فراخوانی کرد:
public static class EntityFrameworkExtentions { public static ObservableCollection<TEntity> Alive<TEntity>(this DbSet<TEntity> set) where TEntity : class, ISoftDelete { var data = set.Where(e => e.IsDeleted == false); return new ObservableCollection<TEntity>(data); } }
3-استفاده از کتابخانه EntityFramework.DynamicFilte
ابتدا اقدام به نصب بسته آن نمایید:
Install-Package EntityFramework.DynamicFilters
modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);
مشکلاتی پیرامون حذف منطقی
- کلاس User و Post را در نظر بگیرد که یک User چندین Post دارد. حال اگر حذف، فیزیکی باشد و کاربر اقدام به حذف User مورد نظر کند، با خطای زیر مواجه میشود:
The DELETE statement conflicted with the REFERENCE constraint ....
1- در سرویسهای مربوط به کلاسهایی که از ISoftDelet ارث بری کردهاند، متدی تحت عنوان CanDelete، به صورت زیر تعریف شود:
public bool CanDelete(user model) { return !model.posts.Any() && ! model.news.Any(); }
2- برای جلوگیری از تکرار قطعه کد فوق، میتوان از روش زیر استفاده کرد:
- یک Attribute سفارشی را به صورت زیر تعریف نمایید:
[AttributeUsage(AttributeTargets.Property)] public class MustBeEmptyToDeleteAttribute : Attribute { }
public class User { public int Id { get; set; } public bool IsDeleted { get; set; } [MustBeEmptyToDelete] public virtual ICollection<Post> Posts { get; set; } [MustBeEmptyToDelete] public virtual ICollection<File> Files { get; set; } // etc... }
public static class EntityExtensions { public static bool CanDelete(this object entity) { return entity.GetType().GetProperties() .Where(x => x.IsDefined(typeof(MustBeEmptyToDeleteAttribute))) .Select(x => x.GetValue(entity)) .OfType<IEnumerable<object>>() .All(x => !x.Any()); }
همانطور که بیان شد، در حذف منطقی فقط رکورد مورد نظر به روز رسانی میشود. برای این منظور میتوان دو متد را همانند زیر در نظر گرفت و هر کدام که مورد نیاز بود، فراخوانی شود:
public void MarkAsSoftDeleted<TEntity>(TEntity entity) where TEntity : ISoftDelete { Entry(entity).State = EntityState.Modified; // set IsDelete=true // here you can set other logs like who deleted ,when ,... } public void MarkAsDeleted<TEntity>(TEntity entity) where TEntity : class { Entry(entity).State = EntityState.Deleted; }
پیشنهادها
- اگر از حذف منطقی استفاده میکنید، امکانی را در سیستم قرار دهید تا در صورت تمایل رکوردهای حذف منطقی را بتوان حذف کرد (تهیه backup و حذف)، حذف منطقی در دراز مدت حجم دیتابیس را بالا میبرد.
- تا حد امکان به کاربران استفاده کننده، وجود امکان حذف منطقی را اطلاع ندهید. اطلاع از این امر شاید باعث عدم دقت افراد استفاده کننده شود.