Kerangka Entitas Audit menghapus entri yang mempertahankan nilai sebelumnya

Saya telah berjuang selama beberapa waktu dengan masalah yang terdiri dari audit entitas basis data secara umum ketika mereka disimpan. Saya memiliki proyek yang menggunakan EF 6 dan saya diharuskan membuat metode "non-invasif" untuk mengaudit entitas ketika ditambahkan, dimodifikasi, atau dihapus. Saya harus menyimpan JSON dari entitas yang dimasukkan, entitas yang dimodifikasi, atau entitas yang dihapus tanpa mengganggu aliran normal. Proyek ini memiliki implementasi Database First.

Solusi saya sederhana, tambahkan sebagian kelas entitas apa pun yang ingin diaudit oleh pemrogram lainnya dengan penerapan IAudit yang pada dasarnya merupakan antarmuka kosong untuk mendapatkan semua perubahan dari entitas yang mengimplementasikannya.

public interface IAudit {}

Saya memiliki entitas Mata Uang yang hanya mengimplementasikannya tanpa kode lain (saya dapat melakukan hal lain di masa mendatang tetapi saya tidak memerlukannya)

public partial class Currencies : IAudit

Saya mengganti metode SaveChanges untuk mencari entitas yang akan diaudit

public override int SaveChanges()
{
    ChangeTracker.DetectChanges();

    // This linq looks for new entities that were marked for audit
    CreateAuditLog(System.Data.Entity.EntityState.Added);
    CreateAuditLog(System.Data.Entity.EntityState.Modified);
    CreateAuditLog(System.Data.Entity.EntityState.Deleted);

    return base.SaveChanges();
}

Solusinya memanggil 3 kali CreateAuditLog karena dalam waktu dekat saya perlu menerapkan konfigurasi untuk mengaudit apa pun yang diputuskan pengguna, mungkin dari konfigurasi database yang diaktifkan/dinonaktifkan oleh pengguna.

Semuanya bekerja dengan sempurna, saya bisa mendapatkan entitas yang disimpan dalam keadaan yang ditentukan:

    private void CreateAuditLog(System.Data.Entity.EntityState state)
    {
        var auditedEntities = ChangeTracker.Entries<IAudit>()
            .Where(p => p.State == state)
            .Select(p => p.Entity);

       ... some code that do something else

       foreach (var auditedEntity in auditedEntities)
       {
          ... some information I required to add

         strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());

          ... some code to save audit information

       }

    }

Masalahnya adalah saya kehilangan setiap nilai dalam keadaan Dihapus, saya hanya mendapatkan ID, tidak ada informasi di properti kecuali ID dan tidak ada kemungkinan untuk mengekstraknya dengan cara apa pun. Saya mencari setiap solusi di StackOverflow dan situs web lain dan tidak ada yang dapat memulihkan informasi asli. Bagaimana saya bisa mendapatkan nilai yang dihapus sebelumnya untuk menyimpannya dengan cara yang sama seperti saya menyimpan entitas yang Ditambahkan dan Dimodifikasi?


person Maximiliano Rios    schedule 19.07.2016    source sumber
comment
Saya sedang mengerjakan perpustakaan yang mungkin bisa membantu. Lihatlah pustaka Audit.EntityFramework, ia memotong SaveChanges() dan dapat dikonfigurasi untuk memfilter entitas yang ingin Anda audit.   -  person thepirat000    schedule 13.09.2016


Jawaban (1)


Butuh beberapa hari bagi saya untuk mengetahuinya. Mungkin solusinya agak rumit tetapi saya mencoba beberapa opsi yang tidak terlalu rumit dan hasilnya tidak bagus.

Pertama, karena saya baru saja mengaudit Hapus dengan cara yang berbeda, saya memisahkan status Dihapus dari Ditambahkan dan Dimodifikasi yang berfungsi dengan baik tanpa perubahan. Status yang dihapus adalah kasus tertentu dan saya memperlakukannya seperti itu.

Pertama, saya perlu mendapatkan nilai asli dari database. Dalam keadaan Dihapus, mereka hilang, tidak ada kemungkinan untuk memulihkannya dari entitas. Dimungkinkan untuk mendapatkannya dengan kode berikut:

var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();

Hasilnya hanyalah kumpulan nilai properti DB (DbPropertyValues). Jika saya bisa mendapatkan nilai asli, saya menetapkan nilai asli dari entitas yang dihapus:

dbEntityEntry.OriginalValues.SetValues(databaseValues);

Baris ini hanya mengisi nilai asli entitas, tidak mengubah nilai saat ini sama sekali. Ini berguna untuk melakukannya karena diperlukan beberapa kode untuk memeriksa setiap properti dan mengaturnya sendiri, ini adalah jalan pintas yang menarik. Sekarang masalahnya saya tidak memiliki entitas untuk diserialkan, jadi saya memerlukan yang baru yang dalam kasus saya saya buat dengan refleksi karena saya tidak tahu jenisnya (saya menerima entitas yang mengimplementasikan IAudit)

Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);

Ini adalah entitas yang akan saya buat serial untuk menyimpan audit nanti.

Sekarang, bagian kompleksnya, saya perlu mendapatkan properti entitas dan mengisinya dengan refleksi dari nilai asli yang ditetapkan dalam entitas:

foreach (var propertyInfo in type.GetProperties())
{
    if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
    {
        var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
        auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
            Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
    }
}

Saya harus memeriksa tipe generik dan array untuk menghindari hubungan EF berikut yang tidak akan berfungsi dengan metode ini dan saya juga tidak memerlukannya (saya memerlukan objek, bukan keseluruhan pohon)

Setelah itu saya hanya perlu membuat serialisasi entitas yang dihapus yang diaudit:

strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());

Kodenya terlihat seperti ini:

string strJSON = string.Empty;
if (state == System.Data.Entity.EntityState.Deleted)
{
    var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();

    // Get original values from the database (the only option, in the delete method they're lost)
    DbEntityEntry dbEntityEntry = this.Entry(auditedEntity);
    if (databaseValues != null)
    {
        dbEntityEntry.OriginalValues.SetValues(databaseValues);
        var originalValues = this.Entry(auditedEntity).OriginalValues;
        Type type = auditedEntity.GetType();
        var auditDeletedEntity = Activator.CreateInstance(type);
        // Get properties by reflection
        foreach (var propertyInfo in type.GetProperties())
        {
            if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
            {
                var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
                auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
                    Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
            }
        }
        strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
    }
}
else
{
    strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
}

Mungkin ada cara yang lebih baik tetapi saya benar-benar menghabiskan banyak waktu untuk mencari opsi dan saya tidak dapat menemukan cara yang lebih baik. Setiap saran atau pengoptimalan dihargai.

person Maximiliano Rios    schedule 19.07.2016