Rekam Jejak Perubahan Data pada ADO.NET Entity Data Model

  • 1
Baru-baru ini saya mengerjakan sebuah aplikasi web ASP.NET MVC dengan ADO.NET Entity Data Model sebagai sumber database utamanya. Pada aplikasi web ini, dikehendaki agar setiap rekam jejak perubahan datanya dapat tercatat dengan lengkap, yakni: apa yang berubah, jenis perubahan (insert, update atau delete), siapa yang merubah, kapan dirubah, serta kondisi data sebelum dan sesudah perubahan.

Konsep yang akan saya tuliskan ini bukan murni konsep dari saya sendiri, namun rangkuman dari beberapa tulisan-tulisan dari para penulis yang lebih ahli di ASP.NETMSDN dan StackOverflow, yang kesemuanya berbahasa Inggris (maaf, lupa mencatat sumber-sumbernya). Tulisan ini adalah rangkuman dan sebagian terjemahan dari tulisan aslinya, untuk membantu sesama programmer pemula yang belum terlalu fasih berbahasa Inggris, khususnya teman-teman di likungan kerja dan daerah saya. Semoga bermanfaat.


Menyimpan Rekam Jejak Perubahan Data


Pada project-project sebelumnya, untuk menyimpan rekam jejak perubahan data, saya melakukannya dengan menambahkan beberapa baris kode pada setiap Action yang melakukan operasi perubahan pada data.
Public Class BukuController
    Inherits Controller

    Private db As New MyWebAppEntities

    'tidak ada perubahan data pada action ini...
    Function Index() As ActionResult
        Return View()
    End Function

    'belum ada perubahan data...
    Function Add() As ActionResult
        Return View()
    End Function

    'terjadi perubahan data: INSERT
    <HttpPost>
    <ValidateAntiForgeryToken>
    Function Add(<Bind(Include:="Judul,PengarangId,JenisId,Keterangan")> buku As TabelBuku) As ActionResult
        If ModelState.IsValid Then
            db.TabelBuku.Add(buku)

            Dim log As New TabelLog
            log.NamaTabel = "Buku"
            log.Tipe = "INSERT"
            log.DirubahOleh = User.Identity.Name
            log.DirubahPada = Now
            log.DataLama = ""
            log.DataBaru = JsonConvert.SerializeObject(buku, MyJsonSerializerSetting)
            db.TabelLog.Add(log)

            db.SaveChanges()
            Return RedirectToAction("Index")
        End If
        Return View(buku)
    End Function

    'belum ada perubahan data...
    Function Edit(id As Integer?) As ActionResult
        If IsNothing(id) Then Return RedirectToAction("Index")
        Dim buku As TabelBuku = db.TabelBuku.Find(id)
        If IsNothing(buku) Then Return View("NotFound")
        Return View(buku)
    End Function

    'terjadi perubahan data: UPDATE
    <HttpPost>
    <ValidateAntiForgeryToken>
    Function Edit(<Bind(Include:="Id,Judul,PengarangId,JenisId,Keterangan")> buku As TabelBuku) As ActionResult
        If ModelState.IsValid Then
            db.Entry(buku).State = Entity.EntityState.Modified

            Dim log As New TabelLog
            log.NamaTable = "Buku"
            log.Tipe = "UPDATE"
            log.DirubahOleh = User.Identity.Name
            log.DirubahPada = Now
            log.DataLama = JsonConvert.SerializeObject(db.Entry(buku).GetDatabaseValues, MyJsonSerializerSetting)
            log.DataBaru = JsonConvert.SerializeObject(db.Entry(buku).CurrentValues, MyJsonSerializerSetting)
            db.TabelLog.Add(log)

            db.SaveChanges()
            Return RedirectToAction("Index")
        End If
        Return View(buku)
    End Function

    'belum ada perubahan data...
    Function Delete(id As Integer?) As ActionResult
        If IsNothing(id) Then Return RedirectToAction("Index")
        Dim buku As TabelBuku = db.TabelBuku.Find(id)
        If IsNothing(buku) Then Return View("NotFound")
        Return View(buku)
    End Function

    'terjadi perubahan data: DELETE
    <HttpPost>
    <ActionName("Delete")>
    <ValidateAntiForgeryToken>
    Function DeleteConfirmed(id As Integer) As ActionResult
        Dim buku As TabelBuku = db.TabelBuku.Find(id)

        Dim log As New TabelLog
        log.NamaTable = "Buku"
        log.Tipe = "DELETE"
        log.DirubahOleh = User.Identity.Name
        log.DirubahPada = Now
        log.DataLama = JsonConvert.SerializeObject(buku, MyJsonSerializerSetting)
        log.DataBaru = ""
        db.TabelLog.Add(log)

        db.TabelBuku.Remove(buku)
        db.SaveChanges()
        Return View("Index")
    End Function

End Class
Metode ini membuat saya harus menulis beberapa baris kode pada setiap Action dari Controller yang melakukan operasi perubahan data, satu per satu secara manual. Anda bisa bayangkan, bila aplikasi web yang sedang dibangun terdiri dari banyak Controller dan lebih banyak lagi Action. Ini menjadi sangat melelahkan dan tidak efektif.


Override fungsi SaveChanges() pada Entity


Operasi perubahan data (penyimpanan data) yang sebenarnya terjadi pada fungsi SaveChanges() dari entity. Kita dapat melakukan override terhadap fungsi ini dan memasukkan beberapa baris kode untuk menyimpan rekam jejak perubahan data yang kita inginkan. Berikut adalah contoh final code yang saya gunakan, sebagai bahan referensi.
Partial Class MyWebAppEntities

    Public Overrides Function SaveChanges() As Integer
        'periksa apakah ada perubahan data. bila ada, lanjutkan...
        If Me.ChangeTracker.HasChanges Then
            'daftar nama tabel yang TIDAK akan direkam perubahannya
            Dim excludeName() As String = {"TabelLog","TabelAktifitas","TabelLainnya"}
            Dim aTime As DateTime = Now
            Dim aUser As String = ""
            Dim ipAddr As String = ""
            'entity ini kita gunakan pada aplikasi web, maka HttpContext.Current pasti selalu ada
            If Not IsNothing(HttpContext.Current) Then
                aUser = HttpContext.Current.User.Identity.Name
                ipAddr = HttpContext.Current.Request.UserHostAddress
            End If

            'periksa setiap entry yang berstatus Added, Deleted dan Modified pada entity
            For Each e As Entity.Infrastructure.DbEntityEntry In Me.ChangeTracker.Entries.Where(Function(f) f.State = Entity.EntityState.Added Or f.State = Entity.EntityState.Deleted Or f.State = Entity.EntityState.Modified)
                'lanjutkan bila nama tabel dari entry ini (e.Entity.GetType.Name) tidak ada dalam daftar excludeName (yang TIDAK direkam perubahannya)
                If Not excludeName.Contains(e.Entity.GetType.Name) Then
                    'membuat log baru...
                    Dim log As New TabelLog
                    With log
                        .NamaTabel = e.Entity.GetType.Name
                        .DirubahOleh = aUser
                        .DirubahPada = aTime
                        .IpAddress = ipAddr
                        .DataLama = ""
                        .DataBaru = ""
                    End With
                    'periksa status perubahan data, lakukan perekaman yang sesuai...
                    Select Case e.State
                        Case Entity.EntityState.Added
                            log.Tipe = "INSERT"
                            log.DataBaru = JsonConvert.SerializeObject(e.CurrentValues, MyJsonSerializerSetting)
                        Case Entity.EntityState.Modified
                            log.Tipe = "UPDATE"
                            log.DataLama = JsonConvert.SerializeObject(e.GetDatabaseValues, MyJsonSerializerSetting)
                            log.DataBaru = JsonConvert.SerializeObject(e.CurrentValues, MyJsonSerializerSetting)
                        Case Entity.EntityState.Deleted
                            log.Tipe = "DELETE"
                            log.DataLama = JsonConvert.SerializeObject(e.GetDatabaseValues, MyJsonSerializerSetting)
                    End Select
                    Me.TabelLog.Add(log)
                End If
            Next
        End If
        'rutin penyimpanan data yang sebenarnya
        Return MyBase.SaveChanges()
    End Function

End Class
Dengan meng-override fungsi SaveChanges() seperti di atas, kita memeriksa perubahan data terlebih dahulu, lalu membuat rekaman perubahannya dan memasukkannya dalam entity agar ikut tersimpan kemudian. Perhatikan bahwa ada perbedaan sumber data yang direkam antara EntityState.Added, EntityState.Modified dan EntityState.Deleted. Perbedaan utama, sangat tampak khususnya pada EntityState.Modified, bahwa sumber data untuk log.DataLama diambil dari:
e.GetDatabaseValues
sedangkan sumber data untuk log.DataBaru diambil dari:
e.CurrentValues


Tantangan selanjutnya dalam menyimpan rekam jejak perubahan data ini adalah memilih metode yang digunakan untuk melakukan serialisasi data. Pada contoh di atas, saya menggunakan Json.NET dari Newtonsoft yang secara built-in telah ada pada Visual Studio 2013 untuk menyimpan data dalam format JSON. Perlu dicatat bahwa fungsi:
Newtonsoft.Json.JsonConvert(Object)
akan melakukan serialisasi object secara recursive (object di dalam object pun akan ikut proses serialisasi). Menggunakan fungsi ini pada ADO.NET Entity membuat JsonConvert(object) ikut melakukan serialisasi pada object terkait lain yang memiliki relasi database terhadap object utama. Ini mungkin bisa diterima, bergantung kepada situasi dimana Anda membutuhkannya. Namun untuk project ini, berhubung saya hanya membutuhkan serialisasi object tanpa mengikutkan object lain di dalamnya (yang terkait melalui relasi database), saya menggunakan fungsi:
Newtonsoft.Json.JsonConvert(Object, JsonSerializerSettings)
dimana pada Module lain saya membuat JsonSerializerSettings dengan menggunakan ContractResolver khusus untuk tidak mengikutkan property bertipe object pada proses serialisasi. Topik ini akan saya coba perdalam dalam tulisan berikutnya.

Saran, usul, pendapat dan/atau rekomendasi lainnya akan sangat membantu.

1 komentar :

  1. vpn_movies/vpn_movies/vpn_movies/movies/vpn_movies/pokflix.svg
    vpn_movies/vpn_movies/vpn_movies/mpmp4_movies/vpn_movies/vpn_movies/pokflix.svg. youtube to mp3\

    BalasHapus