tracker-enabled-dbcontext
tracker-enabled-dbcontext copied to clipboard
Fluent API for excluding fields does not work with base classes
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.
"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.
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
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 ?
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.
By the way. This has nothing to do with the generics as I had originally thought. It's all about the base class.
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.
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 ?
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
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
Where is this code ? In you global.asax ?
This should not happen. It might be a bug in this library or something u are doing wrong.
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?
I will try to reproduce this during weekend.
Thank you
I am sorry I am not getting time for this... I don't when I will get time
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:
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.
Hi All - Still having issues with this. Any new workaround or idea on how to exclude inherited properties from a base class?