简体   繁体   中英

Audit.NET connected object save issue

I'm using Audit.NET with the EntityFramework extension, and everything was running fine when I was tracking just 1 entity.

Now I'm tracking another entity as well that is connected to that first entity, and when I try to save it, the audit save function throws a Reflection error

System.Reflection.TargetException: 'Object does not match target type.'

The structure of my classes is like so:

public class FirstClass{
  public int ID{get;set;}
  //Some props here
  public SecondClass SecondClass{get;set}
}

public class SecondClass{
  public int ID{get;set;}
  public int FirstClassId{get;set;}
  public MySpecialClass JustForData{get;set;}
  //Some other props here
}

Then I just modeled my Audit classes to be the exact same but with added audit fields

public class AuditClass{
  public Guid AuditId{get;set;}
  public string AuditMessage{get;set;}
}

public class FirstClassAudit : AuditClass{
  public int ID{get;set;}
  //Some props here
  //No SecondClass prop here
}

public class SecondClassAudit: AuditClass{
  public int ID{get;set;}
  public int FirstClassId{get;set;}
  public MySpecialClass JustForData{get;set;}
  //Some other props here
}

And then left out the reference to SecondClass in FirstClassAudit

Both of my classes are in the DbContext, the audit classes are each mapped to a separate table. I added the mappings for both classes under an AuditTypeExplicitMapper, which I debugged through without issue. And yet I still get an error on the SaveChanges function

This does not seem to happen when I leave the SecondClass reference as null when I save

EDIT: Some more information

Audit.NET config:

Audit.Core.Configuration.Setup()
                .UseEntityFramework(
                ef => ef
                    .AuditTypeExplicitMapper(m => m
                    .Map<FirstClass, FirstClassAudit>((frst, auditFrst) =>
                    {
                        //Map the tag fields in here
                        auditFrst.Tag = frst.Installation.Tag;
                        //Some more props here
                    })
                    .Map<SecondClass, SecondClassAudit>()
                    .AuditEntityAction((ev, ent, auditEntity) =>
                    {
                        ((AuditClass)auditEntity).AuditMessage = ent.Action;
                    }))
                );

Save function in DbContext:

public override int SaveChanges()
        {
            return Helper.SaveChanges(auditContext, () => base.SaveChanges());
        }

EDIT 2: Stack trace

at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index) at Audit.EntityFramework.Providers.EntityFrameworkDataProvider.CreateAuditEntity(Type definingType, Type auditType, EventEntry entry) at Audit.EntityFramework.Providers.EntityFrameworkDataProvider.InsertEvent(AuditEvent auditEvent) at Audit.Core.AuditScope.SaveEvent(Boolean forceInsert) at Audit.Core.AuditScope.Save() at Audit.EntityFramework.DbContextHelper.SaveScope(IAuditDbContext context, AuditScope scope, EntityFrameworkEvent event) at Audit.EntityFramework.DbContextHelper.SaveChanges(IAuditDbContext context, Func`1 baseSaveChanges) at MyDbContex t.SaveChanges() in [MyLocalPath]\\MyDbContext.cs:line 132 at FirstClassRepository.UpdateFirstClass(Int32 id, FirstClassDto first) in [MyLocalPath]\\FirstClassRepository.cs:line 209 at FirstClassManager.UpdateFirstClass(Int32 id, FirstClassDto dto) in [MyLocalPath]\\FirstClassManager.cs:line 244 at FirstClassController.<>c__DisplayClass20_0.b__0() in [MyLocalPath]\\FirstClassController.cs:line 249

EDIT: After fiddling around a bit more I got the error to say what type it was by adding 'MySpecialClass' to the mappings

System.ArgumentException: 'Object of type 'MySpecialClass' cannot be converted to type 'AuditMySpecialClass'.'

This class is an Owned Type within my datacontext, this may have something to do with it, maybe not.

Right now the error seems to get thrown before it gets to the user defined action that you can add in the mapping, possibly Audit.NET is trying to map these things before the user defined action?

With the latest version of Audit.EntityFramework (15.0.2), you can now ignore property matching only for certain audit types, as follows:

Audit.Core.Configuration.Setup()
    .UseEntityFramework(ef => ef
        .AuditTypeExplicitMapper(m => m
          .Map<FirstClass, FirstClassAudit>((frst, auditFrst) =>
          {
            auditFrst.Tag = frst.Installation.Tag;
          })
          .Map<SecondClass, SecondClassAudit>()
          .AuditEntityAction((ev, ent, auditEntity) =>
          {
            ((AuditClass)auditEntity).AuditMessage = ent.Action;
          }))
        .IgnoreMatchedProperties(t => t == typeof(FirstClassAudit)) // <-- Ignore prop. matching for FirstClassAudit
    );

So, I found a solution. It's not 100% how I would like, but it works.

The problem was with my "MySpecialClass" objects as they were owned types withing EFCore, they generated their own separate events, which confused Audit.NET

So I added [AuditIgnore] above the "MySpecialClass" declaration and added IgnoreMatchedProperties to the configuration

[AuditIgnore]
public class MySpecialClass
{
  public Unit? UnitOfMeasure { get; set; }

  public float? Value { get; set; }
}
Audit.Core.Configuration.Setup()
                .UseEntityFramework(
                ef => ef
                    .AuditTypeExplicitMapper(m => m
                    .Map<FirstClass, FirstClassAudit>((frst, auditFrst) =>
                    {
                        MapMatchedProperties(frst, auditFrst);
                        //Map the tag fields in here
                        auditFrst.Tag = frst.Installation.Tag;
                        //Some more props here
                    })
                    .Map<SecondClass, SecondClassAudit>((scnd, auditScnd)=>
                    {
                        MapMatchedProperties(scnd, auditScnd);
                    })
                    .AuditEntityAction((ev, ent, auditEntity) =>
                    {
                        ((AuditClass)auditEntity).AuditMessage = ent.Action;
                    }))
                    .IgnoreMatchedProperties()
                );

Also I added my own mapping function "MapMatchedProperties" to correctly map every single field with special exceptions for "MySpecialClass"

private static void MapMatchedProperties(object source, object destination)
        {
            var sourceType = source.GetType();
            var destinationType = destination.GetType();

            var sourceFields = sourceType.GetProperties();
            var destinationFields = destinationType.GetProperties();

            foreach (var field in sourceFields)
            {
                var destinationField = destinationFields.FirstOrDefault(f => f.Name.Equals(field.Name));

                if (destinationField != null && (destinationField.PropertyType == field.PropertyType))
                {
                    //Normal field
                    var sourceValue = field.GetValue(source);

                    destinationField.SetValue(destination, sourceValue);
                } else if(destinationField != null && (destinationField.PropertyType == typeof(AuditMySpecialClass) && field.PropertyType== typeof(MySpecialClass)))
                {
                    //MySpecialClass field
                    var destinationMeasure = new AuditMySpecialClass();

                    var sourceValue = (MySpecialClass)field.GetValue(source);

                    if (sourceValue != null || sourceValue.IsEmpty())
                    {
                        destinationMeasure.UnitOfMeasure = sourceValue.UnitOfMeasure;
                        destinationMeasure.Value = sourceValue.Value;
                    }

                    destinationField.SetValue(destination, destinationMeasure);
                }
            }
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM