ตรวจสอบเอนทิตีกรอบลบรายการรักษาค่าก่อนหน้านี้

ฉันดิ้นรนมาระยะหนึ่งแล้วกับปัญหาที่ประกอบด้วยการตรวจสอบเอนทิตีฐานข้อมูลทั่วไปเมื่อบันทึกแล้ว ฉันมีโปรเจ็กต์ที่ใช้ EF 6 และจำเป็นต้องสร้างวิธีการ "ไม่รุกราน" เพื่อตรวจสอบเอนทิตีเมื่อมีการเพิ่ม แก้ไข หรือลบ ฉันต้องจัดเก็บ JSON ของเอนทิตีที่แทรก เอนทิตีที่แก้ไข หรือเอนทิตีที่ถูกลบ โดยไม่รบกวนโฟลว์ปกติ โครงการนี้มีการใช้งาน Database First

วิธีแก้ปัญหาของฉันนั้นง่ายมาก เพิ่มคลาสบางส่วนของเอนทิตีใด ๆ ที่โปรแกรมเมอร์ที่เหลือต้องการตรวจสอบการใช้งาน IAudit ซึ่งโดยพื้นฐานแล้วเป็นอินเทอร์เฟซว่างเปล่าเพื่อรับการเปลี่ยนแปลงทั้งหมดจากเอนทิตีที่ใช้งานมัน

public interface IAudit {}

ฉันมีเอนทิตีสกุลเงินที่เพิ่งใช้งานโดยไม่ต้องใช้รหัสอื่น (ฉันสามารถทำอะไรอย่างอื่นได้ในอนาคต แต่ฉันไม่ต้องการมัน)

public partial class Currencies : IAudit

ฉันแทนที่วิธีการ SaveChanges เพื่อค้นหาเอนทิตีที่จะตรวจสอบ

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();
}

โซลูชันจะเรียก CreateAuditLog ถึง 3 เท่า เนื่องจากในอนาคตอันใกล้นี้ ฉันจำเป็นต้องใช้การกำหนดค่าเพื่อตรวจสอบสิ่งที่ผู้ใช้ตัดสินใจ อาจมาจากการกำหนดค่าฐานข้อมูลที่ผู้ใช้เปิดใช้งาน/ปิดใช้งาน

ทุกอย่างทำงานได้อย่างสมบูรณ์แบบ ฉันสามารถบันทึกเอนทิตีในสถานะที่ระบุได้:

    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

       }

    }

ปัญหาคือฉันสูญเสียทุกค่าในสถานะ Deleted ฉันรับเฉพาะ ID เท่านั้น ไม่มีข้อมูลในคุณสมบัติยกเว้น ID และไม่มีความเป็นไปได้ใด ๆ ที่จะแยกมันออกมาในทางใดทางหนึ่ง ฉันมองหาโซลูชันทุกรายการใน StackOverflow และเว็บไซต์อื่นๆ และไม่มีอะไรที่จะกู้คืนข้อมูลต้นฉบับได้ ฉันจะรับค่าที่ถูกลบก่อนหน้านี้เพื่อจัดเก็บในลักษณะเดียวกับที่ฉันจัดเก็บเอนทิตีที่เพิ่มและแก้ไขได้อย่างไร


person Maximiliano Rios    schedule 19.07.2016    source แหล่งที่มา
comment
ฉันกำลังทำงานในห้องสมุดที่อาจช่วยได้ ดูที่ไลบรารี Audit.EntityFramework ซึ่งขัดขวาง SaveChanges() และสามารถ กำหนดค่าให้กรองเอนทิตีที่คุณต้องการตรวจสอบ   -  person thepirat000    schedule 13.09.2016


คำตอบ (1)


ฉันใช้เวลาสองสามวันในการคิดออก อาจเป็นวิธีแก้ปัญหาที่ค่อนข้างซับซ้อน แต่ฉันลองใช้ตัวเลือกที่ซับซ้อนน้อยกว่าหลายตัวแต่ก็ไม่ได้ผลลัพธ์ที่ดี

ก่อนอื่น เนื่องจากฉันแค่ตรวจสอบ Delete ด้วยวิธีอื่น ฉันจึงแยกสถานะ Deleted ออกจาก Added และ Modified ซึ่งทำงานได้ดีโดยไม่มีการเปลี่ยนแปลง สถานะที่ถูกลบเป็นกรณีเฉพาะ และฉันปฏิบัติต่อมันเช่นนั้น

ก่อนอื่น ฉันต้องได้รับค่าดั้งเดิมจากฐานข้อมูล ในสถานะถูกลบ ข้อมูลเหล่านั้นหายไปแล้ว ไม่มีความเป็นไปได้ในการกู้คืนจากเอนทิตี สามารถรับได้ด้วยรหัสต่อไปนี้:

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

ผลลัพธ์เป็นเพียงการรวบรวมค่าคุณสมบัติ DB (DbPropertyValues) หากฉันสามารถรับค่าดั้งเดิมได้ ฉันจะตั้งค่าดั้งเดิมจากเอนทิตีที่ถูกลบ:

dbEntityEntry.OriginalValues.SetValues(databaseValues);

บรรทัดนี้เพียงเติมค่าดั้งเดิมของเอนทิตี แต่ไม่ได้แก้ไขค่าปัจจุบันเลย การทำเช่นนี้มีประโยชน์เพราะต้องใช้โค้ดเพื่อตรวจสอบทุกคุณสมบัติและตั้งค่าเอง ถือเป็นทางลัดที่น่าสนใจ ตอนนี้ ปัญหาคือ ฉันไม่มีเอนทิตีในการซีเรียลไลซ์ ดังนั้นฉันจึงต้องการเอนทิตีใหม่ ซึ่งในกรณีของฉันฉันสร้างโดยการไตร่ตรองเพราะฉันไม่รู้ประเภท (ฉันได้รับเอนทิตีที่ใช้ IAudit)

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

นี่คือเอนทิตีที่ฉันจะซีเรียลไลซ์เพื่อจัดเก็บการตรวจสอบในภายหลัง

ตอนนี้ ส่วนที่ซับซ้อน ฉันต้องได้รับคุณสมบัติของเอนทิตีและเติมเต็มด้วยการสะท้อนจากค่าดั้งเดิมที่ตั้งไว้ในเอนทิตี:

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 });
    }
}

ฉันต้องตรวจสอบประเภททั่วไปและอาเรย์เพื่อหลีกเลี่ยงการปฏิบัติตามความสัมพันธ์ของ EF ที่จะใช้งานไม่ได้กับวิธีนี้และฉันก็ไม่ต้องการด้วย (ฉันต้องการวัตถุไม่ใช่ทั้งแผนผัง)

หลังจากนั้นฉันเพียงแค่ต้องทำให้เอนทิตีที่ถูกลบที่ได้รับการตรวจสอบเป็นอนุกรม:

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

รหัสมีลักษณะดังนี้:

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());
}

อาจมีวิธีที่ดีกว่านี้ แต่ฉันใช้เวลาอย่างจริงจังเพื่อค้นหาตัวเลือกต่างๆ และฉันไม่พบสิ่งใดที่ดีไปกว่านี้อีกแล้ว ข้อเสนอแนะหรือการเพิ่มประสิทธิภาพใด ๆ ที่ชื่นชม

person Maximiliano Rios    schedule 19.07.2016