Preview:
#AuditEntity
 public class AuditEntity
 {
     [Key]
     public Guid Id { get; set; }
     public string OldValues { get; set; }
     public string NewValues { get; set; }
     public DateTimeOffset DateTimeOffset { get; set; }
     public EntityState EntityState { get; set; }
     public string ByUser { get; set; }
     public Nullable<Guid> AuditMetaDataId { get; set; }
     [ForeignKey(nameof(AuditMetaDataId))]
     public virtual AuditMetaDataEntity AuditMetaData { get; set; }

 }
--------------------------------------------------------
 #AuditMetaDataEntity
public class AuditMetaDataEntity  
{
    [Key]
    public Guid Id { get; set; }
    public Guid HashPrimaryKey { get; set; }
    public string SchemaTable { get; set; }
    public string ReadablePrimaryKey { get; set; }
    public string Schema { get; set; }
    public string Table { get; set; }
    public string DisplayName { get; set; }
}
--------------------------------------------------------
#extensions
 internal static class InternalExtensions
 {
     internal static bool ShouldBeAudited(this EntityEntry entry)
     {
         return entry.State != EntityState.Detached && entry.State != EntityState.Unchanged &&
                !(entry.Entity is AuditEntity) && !(entry.Entity is AuditMetaDataEntity) &&
         entry.IsAuditable();
     }
     internal static bool IsAuditable(this EntityEntry entityEntry)
     {
         AuditableAttribute enableAuditAttribute = (AuditableAttribute)Attribute.GetCustomAttribute(entityEntry.Entity.GetType(), typeof(AuditableAttribute));
         return enableAuditAttribute != null;
     }

     internal static bool IsAuditable(this PropertyEntry propertyEntry)
     {
         Type entityType = propertyEntry.EntityEntry.Entity.GetType();
         PropertyInfo propertyInfo = entityType.GetProperty(propertyEntry.Metadata.Name);
         bool disableAuditAttribute = Attribute.IsDefined(propertyInfo, typeof(NotAuditableAttribute));

         return IsAuditable(propertyEntry.EntityEntry) && !disableAuditAttribute;
     }
 }

 public static class Extensions
 {
     public static string ToReadablePrimaryKey(this EntityEntry entry)
     {
         IKey primaryKey = entry.Metadata.FindPrimaryKey();
         if (primaryKey == null)
         {
             return null;
         }
         else
         {
             return string.Join(",", (primaryKey.Properties.ToDictionary(x => x.Name, x => x.PropertyInfo.GetValue(entry.Entity))).Select(x => x.Key + "=" + x.Value));
         }
     }

     public static Guid ToGuidHash(this string readablePrimaryKey)
     {
         using (SHA512 sha512 = SHA512.Create())
         {
             byte[] hashValue = sha512.ComputeHash(Encoding.Default.GetBytes(readablePrimaryKey));
             byte[] reducedHashValue = new byte[16];
             for (int i = 0; i < 16; i++)
             {
                 reducedHashValue[i] = hashValue[i * 4];
             }
             return new Guid(reducedHashValue);
         }
     }
 }
--------------------------------------------------------
#Attribute
 [AttributeUsage(AttributeTargets.Class)]
 public sealed class AuditableAttribute : Attribute
 { 
 
 }

 [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
 public sealed class NotAuditableAttribute : Attribute
 {
 
 }
--------------------------------------------------------
#AuditEntry
    internal class AuditEntry
    {
        private string _readablePrimaryKey;
        public string ReadablePrimaryKey
        {
            get
            {
                if (string.IsNullOrEmpty(_readablePrimaryKey))
                    _readablePrimaryKey = Entry.ToReadablePrimaryKey();
                return _readablePrimaryKey;
            }
            set
            {
                _readablePrimaryKey = value;
            }
        }
        public Guid HashReferenceId { get; set; }
        public EntityEntry Entry { get; }
        public string TableName { get; set; }
        public string DisplayName { get; set; }
        public string SchemaName { get; set; }
        public Dictionary<string, object> OldValues { get; } = new Dictionary<string, object>();
        public Dictionary<string, object> NewValues { get; } = new Dictionary<string, object>();
        public EntityState EntityState { get; set; }
        public string ByUser { get; set; }
        public List<PropertyEntry> TemporaryProperties { get; } = new List<PropertyEntry>();
        public bool HasTemporaryProperties => TemporaryProperties.Any();

        public AuditEntry(EntityEntry entry, ICurrentUser currentUser)
        {
            Entry = entry;
            ReadablePrimaryKey = Entry.ToReadablePrimaryKey();
            HashReferenceId = ReadablePrimaryKey.ToGuidHash();
            TableName = entry.Metadata.GetTableName();
            DisplayName = entry.Metadata.DisplayName();
            SchemaName = entry.Metadata.GetSchema()!=null ? entry.Metadata.GetSchema():"db" ;
            EntityState = entry.State;
            ByUser = currentUser == default ? "Unknown" : currentUser.UserName;

            foreach (PropertyEntry property in entry.Properties)
            {
                if (property.IsAuditable())
                {
                    if (property.IsTemporary)
                    {
                        TemporaryProperties.Add(property);
                        continue;
                    }

                    string propertyName = property.Metadata.Name;

                    switch (entry.State)
                    {
                        case EntityState.Added:
                            NewValues[propertyName] = property.CurrentValue;
                            break;

                        case EntityState.Deleted:
                            OldValues[propertyName] = property.OriginalValue;
                            break;

                        case EntityState.Modified:
                            if (property.IsModified)
                            {
                                OldValues[propertyName] = property.OriginalValue;
                                NewValues[propertyName] = property.CurrentValue;
                            }
                            break;
                    }
                }
            }
        }

        public void Update()
        {
            // Get the final value of the temporary properties
            foreach (var prop in TemporaryProperties)
            {
                NewValues[prop.Metadata.Name] = prop.CurrentValue;
            }

            if (TemporaryProperties != default && TemporaryProperties.Count(x => x.Metadata.IsKey()) > 0)
            {
                ReadablePrimaryKey = Entry.ToReadablePrimaryKey();
                HashReferenceId = ReadablePrimaryKey.ToGuidHash();
            }
        }

        public AuditMetaDataEntity ToAuditMetaDataEntity()
        {
            AuditMetaDataEntity auditMetaData = new AuditMetaDataEntity();
            auditMetaData.DisplayName = DisplayName;
            auditMetaData.Schema = SchemaName;
            auditMetaData.Table = TableName;
            auditMetaData.SchemaTable = $"{(!string.IsNullOrEmpty(SchemaName) ? SchemaName + "." : "")}{TableName}";
            auditMetaData.ReadablePrimaryKey = ReadablePrimaryKey;
            auditMetaData.HashPrimaryKey = HashReferenceId;

            return auditMetaData;
        }

        public AuditEntity ToAuditEntity(AuditMetaDataEntity auditMetaData)
        {
            AuditEntity audit = new AuditEntity();
            audit.OldValues = OldValues.Count == 0 ? "  " : JsonConvert.SerializeObject(OldValues);
            audit.NewValues = NewValues.Count == 0 ? "  " : JsonConvert.SerializeObject(NewValues);
            audit.EntityState = EntityState;
            audit.DateTimeOffset = DateTimeOffset.UtcNow;
            audit.ByUser = ByUser;
            audit.AuditMetaData = auditMetaData;

            return audit;
        }

        public AuditEntity ToAuditEntity()
        {
            AuditEntity audit = new AuditEntity();
            audit.OldValues = OldValues.Count == 0 ? null : JsonConvert.SerializeObject(OldValues);
            audit.NewValues = NewValues.Count == 0 ? null : JsonConvert.SerializeObject(NewValues);
            audit.EntityState = EntityState;
            audit.DateTimeOffset = DateTimeOffset.UtcNow;
            audit.ByUser = ByUser;

            return audit;
        }
    }
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter