tracker-enabled-dbcontext icon indicating copy to clipboard operation
tracker-enabled-dbcontext copied to clipboard

Fluent API for excluding fields does not work with base classes

Open talmroth opened this issue 8 years ago • 18 comments

We have a configuration system that we implemented as a single generic (many layers but all for <T> where T : configEntityBase). This are all just simple CRUD operations. We want to audit them all and we have a base class that has our primary key as well as LastModified date and LastModifiedBy.

I created a generic EntityTypeConfiguration class in hopes to removing all the base type fields from the tracking.

public class ConfigEntityTypeConfiguration<T> : EntityTypeConfiguration<T> where T : TrackedEntity
{
    public ConfigEntityTypeConfiguration()
    {
        EntityTracker.TrackAllProperties<T>()
                     .Except(e => e.LastModified)
                     .And(e => e.LastModifiedby)
                     .And(e => e.Created);
    }
}

Unfortunately it records those exclusions on the base class (because of the constrained generic I think). I looked a little at the code but I am not clear yet as to why it behaves this way or what the solution would be.

We have a very layered architecture and very specific rules about what can be done where and we don't want our entities project to have reference to this (and by dependency entity framework) so the attribute solution is not an option for us.

talmroth avatar Mar 02 '16 23:03 talmroth

"Unfortunately it records those exclusions on the base class (because of the constrained generic I think). "

What does that mean ?

On Thu 3 Mar, 2016 5:15 am talmroth, [email protected] wrote:

We have a configuration system that we implemented as a single generic (many layers but all for where T : configEntityBase). This are all just simple CRUD operations. We want to audit them all and we have a base class that has our primary key as well as LastModified date and LastModifiedBy.

I created a generic EntityTypeConfiguration class in hopes to removing all the base type fields from the tracking.

public class ConfigEntityTypeConfiguration<T> : EntityTypeConfiguration<T> where T : TrackedEntity { public ConfigEntityTypeConfiguration() { EntityTracker.TrackAllProperties<T>() .Except(e => e.LastModified) .And(e => e.LastModifiedby) .And(e => e.Created); } }

Unfortunately it records those exclusions on the base class (because of the constrained generic I think). I looked a little at the code but I am not clear yet as to why it behaves this way or what the solution would be.

We have a very layered architecture and very specific rules about what can be done where and we don't want our entities project to have reference to this (and by dependency entity framework) so the attribute solution is not an option for us.

— Reply to this email directly or view it on GitHub https://github.com/bilal-fazlani/tracker-enabled-dbcontext/issues/90.

bilal-fazlani avatar Mar 03 '16 04:03 bilal-fazlani

If you look at the TrackingDataStore.PropertyConfigStore collection after calling the above code (.Except.And.And) you will see more LastModified LastModifiedby and Created in the list twice. Once as the actual type (with a value of true) and a second time under the base type (with a value of false).

Later when the code runs that excludes the fields it looks it up by the actual type which has the default value of true to include it in the logs.

I added a test to my fork to show it fail.

https://github.com/talmroth/tracker-enabled-dbcontext/tree/genericTest

talmroth avatar Mar 04 '16 01:03 talmroth

Hmmmmm, okay... I understand now. So to make the test pass,

we would need something like :

EntityTracker.TrackAllProperties<T>(includeChildren: true)
                     .Except(e => e.LastModified)
                     .And(e => e.LastModifiedby)
                     .And(e => e.Created);

what do you think ?

bilal-fazlani avatar Mar 04 '16 15:03 bilal-fazlani

The following 2 changes fix my sample test (going from info.DeclaringType.FullName to typeof(T).FullName)

    internal static void SkipProperty(Expression<Func<T, object>> property)
    {
        PropertyInfo info = property.GetPropertyInfo();
        var newValue = new TrackingConfigurationValue(false, TrackingConfigurationPriority.High);

        TrackingDataStore.PropertyConfigStore.AddOrUpdate(
            new PropertyConfiguerationKey(info.Name, typeof(T).FullName),
            newValue,
            (existingKey, existingvalue) => newValue);
    }

    internal static void TrackProperty(Expression<Func<T, object>> property)
    {
        PropertyInfo info = property.GetPropertyInfo();
        var newValue = new TrackingConfigurationValue(true, TrackingConfigurationPriority.High);
        TrackingDataStore.PropertyConfigStore.AddOrUpdate(
            new PropertyConfiguerationKey(info.Name, typeof(T).FullName),
            newValue,
            (key, value) => newValue);
    }

I would think just these changes would be enough but I am unsure if there are other side effects.

The method that checks for the exclusion currently always uses the actual type (posted below), it doesn't find the declaring type of the property like the code above did before. So the above changes keeps these 2 in sync. The question is whether you want them to behave this way.

 public IEnumerable<AuditLogDetail> CreateLogDetails()
    {
        Type entityType = DbEntry.Entity.GetType().GetEntityType();

        foreach (string propertyName in PropertyNamesOfEntity())
        {
            if (PropertyTrackingConfiguration.IsTrackingEnabled(
                new PropertyConfiguerationKey(propertyName, entityType.FullName), entityType ) 
                && IsValueChanged(propertyName))
            {
                yield return new AuditLogDetail
                {
                    PropertyName = propertyName,
                    OriginalValue = OriginalValue(propertyName)?.ToString(),
                    NewValue = CurrentValue(propertyName)?.ToString(),
                    Log = _log
                };
            }
        }
    }

I don't know if entity framework supports a derived class hiding a property on the base class. If it did you would want to use declaring type on both sides I think.

talmroth avatar Mar 04 '16 23:03 talmroth

By the way. This has nothing to do with the generics as I had originally thought. It's all about the base class.

talmroth avatar Mar 04 '16 23:03 talmroth

Hello, we are trying yours library and it is working fine, but we have same Issue with base class. When we use annotations, everything is working fine but we would like to use FluentApi.

After talmroth's changes, it works. Can you tell us if talmroth's changes are right? Thank you.

RadekJanostik avatar Jun 02 '16 10:06 RadekJanostik

I am not sure. That needs to be tested specifically. Just to be sure, you want to configure using fluent api but just once for a parent class. Not for every child entity. Is that true ?

bilal-fazlani avatar Jun 03 '16 10:06 bilal-fazlani

Hi, no, problem is that it is not working even if I use it for both base class and child class. For example: EntityTracker.TrackAllProperties<BaseEntity>().Except(p => p.Updated); EntityTracker.TrackAllProperties<ChildEntity>().Except(p => p.Updated); Is not working. EntityTracker.TrackAllProperties<BaseEntity>().Except(p => p.Updated); Is not working EntityTracker.TrackAllProperties<ChildEntity>().Except(p => p.Updated); Is not working Property Updated always appeare int Log.

If i use same but with annotation, it is working as it is expected. Thank you. Rcz

RadekJanostik avatar Jun 03 '16 10:06 RadekJanostik

Oh, it is ignoring angle brackets: For example: EntityTracker.TrackAllProperties<BaseEntity>().Except(p => p.Updated); EntityTracker.TrackAllProperties<ChildEntity>().Except(p => p.Updated); Is not working. EntityTracker.TrackAllProperties<BaseEntity>().Except(p => p.Updated); Is not working EntityTracker.TrackAllProperties<ChildEntity>().Except(p => p.Updated); Is not working

RadekJanostik avatar Jun 03 '16 10:06 RadekJanostik

Where is this code ? In you global.asax ?

bilal-fazlani avatar Jun 03 '16 10:06 bilal-fazlani

This should not happen. It might be a bug in this library or something u are doing wrong.

bilal-fazlani avatar Jun 03 '16 10:06 bilal-fazlani

Ok, I've tested it again using your sample project SampleLogMaker: Base class: public abstract class Base { public DateTime Updated {get; set;} }

Blog: public class Blog : Base, ISoftDeletable

AuditConfig.cs internal static void Configure() { EntityTracker.TrackAllProperties<Blog>().Except(x=>x.Updated); EntityTracker.TrackAllProperties<Base>().Except(x=>x.Updated); }

After blog editation in db is property Updated. What am I doing wrong?

RadekJanostik avatar Jun 03 '16 10:06 RadekJanostik

I will try to reproduce this during weekend.

bilal-fazlani avatar Jun 03 '16 10:06 bilal-fazlani

Thank you

RadekJanostik avatar Jun 03 '16 10:06 RadekJanostik

I am sorry I am not getting time for this... I don't when I will get time

bilal-fazlani avatar Jun 16 '16 16:06 bilal-fazlani

Hi @bilal-fazlani, @talmroth , @radeczek

I ran into the same issue with base class fields always being logged, but have solved this by removing these records in the OnAuditLogGenerated event. Something like:

OnAuditLogGenerated += (sender, args) =>
{
      var auditLog = args.Log;
      var logDetailsAsArray = new AuditLogDetail[auditLog.LogDetails.Count]; 
      auditLog.LogDetails.CopyTo(logDetailsAsArray, 0);
      auditLog.LogDetails.Clear();
      auditLog.LogDetails = logDetailsAsArray.Where(x => !x.PropertyName.Equals("Modified") && !x.PropertyName.Equals("ModifiedBy")).ToList();
};

This works with no other changes required. :smiley:

tarunvd avatar Aug 29 '16 14:08 tarunvd

I tried this library in a demo project, and I'm having the same problems as the thread starter.

If I register the (abstract) base class in the EntityTracker using fluent API, crud to it doesn't get logged.

I don't want to pollute my POCO-s with dependencies on a 3rd party framework, so annotations are not an option for me.

andreasnilsen avatar Sep 23 '16 11:09 andreasnilsen

Hi All - Still having issues with this. Any new workaround or idea on how to exclude inherited properties from a base class?

mikeshyavitz avatar Feb 28 '18 21:02 mikeshyavitz