Permasalahan dan Business Requirement
Proses pembuatan sistem wajib hukumnya mengikuti business requirement yang telah ditentukan. Terkadang, requirement tersebut mewajibkan untuk tidak menghapus data secara permanen di database. Berbagai alasan menjadi pertimbangan, seperti
- Tracing riwayat
- Backup data
- Lebih mudah debugging code (percaya ga percaya 🤣).
Pendekatan Pemecahan Permasalahan
Pendekatan yang dapat dilakukan adalah menggunakan metode soft delete. Metode ini sebenarnya hanya menyembunyikan data pada saat sistem berjalan dengan menambahkan flag true-false pada kolom IsDeleted di setiap table.
Metode Soft Delete dengan Global Query Filter
Step 1: buat base interface dan builder nya
Hal yang pertama dilakukan adalah membuat interface dan builder sebagai basis untuk model yang akan menerapkan fitur soft delete.
Buat file IEntityBase.cs
dan IEntityBaseBuilder
pada folder Models\EntityBases
.
IEntityBase.cs
namespace SoftDeleteTutorial.Models.EntityBases;
public interface IEntityBase
{
Guid Id { get; set; } //ditaruh di interface karena setiap model butuh Id
bool IsDeleted { get; set; } //flagging utk soft delete
}
IEntityBaseBuilder.cs
namespace SoftDeleteTutorial.Models.EntityBases;
public interface IEntityBaseBuilder<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : class
{
}
Step 2: buat abstract model dan builder yang mengimplement interface
Lankah selanjutnya adalah membuat abstract basis model yang mengimplement interface pada step 1. Model yang akan dibuat adalah EntityBase.cs
dan builder modelnya EntityBaseBuilder.cs
.
EntityBase
namespace SoftDeleteTutorial.Models.EntityBases;
public abstract class EntityBase : IEntityBase //abstract class mengimplementasikan interface base
{
public Guid Id { get; set; }
public bool IsDeleted { get; set; }
}
EntityBaseBuilder
namespace SoftDeleteTutorial.Models.EntityBases;
public abstract class EntityBaseBuilder<TEntity> : IEntityBaseBuilder<TEntity> where TEntity : class, IEntityBase //implementasi base interface
{
//konfigurasi global query filter
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.HasQueryFilter(t => !t.IsDeleted); //filter data yang IsDeleted = 0 (false)
}
}
step 3: cara menggunakan EntityBase pada Model
Cara penggunaannya cukup simple yaitu dengan mengimplementasi interface EnitityBase dan EntityBaseBuilder pada model yang ingin menggunakan fitur soft delete. Contoh, kita membuat model Student.cs
dan StudentBuilder.cs
. Simpan file tersebut pada folder Models\Entities
.
Student.cs
namespace SoftDeleteTutorial.Models.Entities;
public class Student : EntityBase //implement dari abstract class
{
//tidak perlu proprty Id karena sudah ada di EntityBase
public string Name { get; set; } = string.Empty;
public string Nim { get; set; } = string.Empty;
}
StudentBuilder.cs
namespace SoftDeleteTutorial.Models.Entities;
public class StudentBuilder : EntityBaseBuilder<Student> //implement EntityBaseBuilder utk class model yang diinginkan
{
// override konfigurasi yang sebelumnya telah ada query filternya
public override void Configure(EntityTypeBuilder<Student> builder)
{
base.Configure(builder);
builder.Property(s => s.Name).HasMaxLength(100);
builder.Property(s => s.Nim).HasMaxLength(10);
builder.HasIndex(s => s.Nim).IsUnique();
}
}
step 4: buat Ekstensi untuk tracking hapus data
Hal yang diharapkan saat soft delete adalah otomatis set IsDelete = true ketika ada penghapusan data. Kita tidak ingin setiap ada penghapusan data harus set IsDeleted=true. Oleh karena itu, kita perlu membuat ekstensi yang akan mentracking setiap penghapusan data. Kita beri nama ChangeTrackerExtensions.cs
yang ditaruh pada folder Extentions
.
ChangeTrackerExtensions.cs
using SoftDeleteTutorial.Models.EntityBases;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace SoftDeleteTutorial.Extensions;
public static class ChangeTrackerExtensions
{
public static void SetAuditProperties(this ChangeTracker changeTracker)
{
changeTracker.DetectChanges();
IEnumerable<EntityEntry> entities = changeTracker.Entries().Where
(
t =>
t.Entity is IEntityBase
&& (t.State == EntityState.Deleted)
);
foreach (EntityEntry entry in entities)
{
IEntityBase entity = (IEntityBase)entry.Entity;
if (entry.State == EntityState.Deleted)
{
entity.IsDeleted = true; //jika ada penghapusan data maka IsDeleted=true
entry.State = EntityState.Modified;
}
}
}
}
step 5: override method savechange di ApplicationDbContext
Beberapa method yang harus dioverride ada 4 yaitu
SaveChangesAsync(CancellationToken cancellationToken = default)
SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
SaveChanges()
SaveChanges(bool acceptAllChangesOnSuccess)
Method tersebut akan ditambah ekstensi ChangeTracker yang sudah dibuat pada step 4.
AppDbContext.cs
namespace SoftDeleteTutorial.Models;
public class AppDbContext
{
public AppDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Student> Students { get; set; } = default!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
new StudentBuilder().Configure(modelBuilder.Entity<Student>());
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ChangeTracker.SetAuditProperties();
return await base.SaveChangesAsync(cancellationToken);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
ChangeTracker.SetAuditProperties();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
public override int SaveChanges()
{
ChangeTracker.SetAuditProperties();
return base.SaveChanges();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
ChangeTracker.SetAuditProperties();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
}
step 6: sudah selesai ❤️
Unique dan Index Coloumn
Jika menggunakan soft delete ada beberapa hal yang harus disesuaikan terutama untuk kolom yang unique atau berstatus index. Soft delete yang tidak menghapus secara fisik, artinya jika ada data baru yang ditambahkan dengan data yang sama bisa jadi kena error dulicate key. Contohnya, property Nim pada model Student adalah unique dengan data new Student {Name="Rahmat", Nim="123"}
. Lalu dihapus tuh student atas nama Rahmat. Nah, jika ada data dengan Nim="123" maka akan error karena dianggap duplicate key. Oleh karena itu kita perlu mengecek ketika insert data Student dengan IgnoreQueryFilter()
. Contoh kodingan seperti berikut.
private async Task<Student> InsertStudent(StudentInput input, CancellationToken cancellationToken)
{
//IgnoreQueryFilter() untuk mengambil semua data tanpa melihat flag IsDeleted.
var student = await _dbContext.Students.IgnoreQueryFilters().SingleOrDefaultAsync(s => s.Nim == input.Nim, cancellationToken);
if (student != null && !student.IsDeleted)
throw new BusinessLogicException($"NIM {input.Nim} sudah terdaftar. Silahkan daftar mahasiswa lainnya!");
else if (student != null && student.IsDeleted)
{
student.IsDeleted = false; //*membangkitkan* yang sudah terhapus
await _dbContext.SaveChangesAsync(cancellationToken);
return student;
}
var newStudent = new Student { Name = input.Name, Nim = input.Nim };
await _dbContext.Students.AddAsync(newStudent, cancellationToken);
await _dbContext.SaveChangesAsync(cancellationToken);
return newStudent;
}
Saran dan Masukan
Jika terdapat pertanyaan pada artikel ini bisa bertanya di kolom komentar. Saya juga sangat menerima jika ada masukan dan kritik, siapa tahu anda bisa kasih saran yang lebih baik atau mendapati kesalahan code. Terimakasih semuanya 😊👍
Top comments (1)
Interesting..