Open dmeierotto opened 8 years ago
Thank you, this is an excellent idea and without a doubt very useful.
This will for sure be implemented on the next major version of the Audit Feature.
Hello @dmeierotto ,
I forget to say you could already implement it yourself by using custom class See: https://github.com/zzzprojects/EntityFramework-Plus/wiki/EF-Audit-%7C-Entity-Framework-Audit-Trail-Context-and-Track-Changes#audit-customization
Let me know if you need some help to implement it.
As said, this is for sure a suggestion we keep in mind to include by default in the next major version.
Best Regards,
Jonathan
Hello,
Could you please give an example how to implement it? I'm having issue. I currently have AuditEntry and AuditEntryProperty tables in my db (code first). I added new custom classes like this:
public class CustomAuditEntry : AuditEntry { public Guid Changeset { get; set; } }
public class CustomAuditEntryProperty : AuditEntryProperty { public Guid Changeset { get; set; } }
When i add new migration, it adds AuditEntry table again, and it creates table CustomAuditEntry which only has Changeset column? Should CustomAuditEntry table has all properties of AuditEntry table plus Changeset column?
Here is the snippet of migration script:
CreateTable( "Core.AuditEntry", c => new { AuditEntryID = c.Int(nullable: false, identity: true), EntitySetName = c.String(maxLength: 255), EntityTypeName = c.String(maxLength: 255), State = c.Int(nullable: false), StateName = c.String(maxLength: 255), CreatedBy = c.String(maxLength: 255), CreatedDate = c.DateTime(nullable: false), }) .PrimaryKey(t => t.AuditEntryID);
`CreateTable(
"Core.CustomAuditEntry",
c => new
{
AuditEntryID = c.Int(nullable: false),
Changeset = c.Guid(nullable: false),
})
.PrimaryKey(t => t.AuditEntryID)
.ForeignKey("Core.AuditEntry", t => t.AuditEntryID)
.Index(t => t.AuditEntryID);`
`CreateTable(
"Core.CustomAuditEntryProperty",
c => new
{
AuditEntryPropertyID = c.Int(nullable: false),
Changeset = c.Guid(nullable: false),
})
.PrimaryKey(t => t.AuditEntryPropertyID)
.ForeignKey("Core.AuditEntryProperty", t => t.AuditEntryPropertyID)
.Index(t => t.AuditEntryPropertyID);
DropTable("Core.AuditEntry");`
Then I did update-database i got error because AuditEntry already exist. Am I doing something wrong?
Hello @quang-nguyen ,
Please try to create your own issue topic next time, it's easier to follow request this way.
Then I did update-database i got error because AuditEntry already exist. Am I doing something wrong?
This one seems more a migration error, so I'm not sure what to answer. If your database is new, simply delete everything.
When i add new migration, it adds AuditEntry table again, and it creates table CustomAuditEntry which only has Changeset column
Yes, by default it's normal 4 audit tables is created. However, you can change the mapping to use instead TPH so only two table will be created. See the example below which use TPH:
using System.Data.Entity;
using System.Windows.Forms;
namespace Z.EntityFramework.Plus.Lab
{
public partial class Form_Request_Audit_CustomAudit : Form
{
public Form_Request_Audit_CustomAudit()
{
InitializeComponent();
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
(context as EntityContext).AuditEntries.AddRange(audit.Entries);
AuditManager.DefaultConfiguration.AuditEntryFactory = args =>
new CustomAuditEntry {CustomProperty = 1};
AuditManager.DefaultConfiguration.AuditEntryPropertyFactory = args =>
new CustomAuditEntryProperty {CustomProperty = 2};
using (var ctx = new EntityContext())
{
var audits = new Audit();
audits.CreatedBy = "ZZZ Projects"; // Optional
ctx.EntitySimples.Add(new EntitySimple {ColumnInt = 2});
ctx.SaveChanges(audits);
}
}
public class EntityContext : DbContext
{
public EntityContext() : base("CodeFirstEntities")
{
}
// ... context code ...
public DbSet<AuditEntry> AuditEntries { get; set; }
public DbSet<AuditEntryProperty> AuditEntryProperties { get; set; }
public DbSet<EntitySimple> EntitySimples { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// INTERNAL MAPPING: ensure unique name...
modelBuilder.Entity<EntitySimple>().ToTable(GetType().DeclaringType.FullName.Replace(".", "_") + "_" + typeof(EntitySimple).Name);
modelBuilder.Entity<AuditEntry>().ToTable(GetType().DeclaringType.FullName.Replace(".", "_") + "_" + typeof(AuditEntry).Name);
modelBuilder.Entity<AuditEntryProperty>().ToTable(GetType().DeclaringType.FullName.Replace(".", "_") + "_" + typeof(AuditEntryProperty).Name);
// MAP audit using TPH
modelBuilder.Entity<AuditEntry>().Map<CustomAuditEntry>(x => x.Requires("Discriminator").HasValue("CustomAuditEntry"));
modelBuilder.Entity<AuditEntryProperty>().Map<CustomAuditEntryProperty>(x => x.Requires("Discriminator").HasValue("CustomAuditEntryProperty"));
base.OnModelCreating(modelBuilder);
}
}
}
public class EntitySimple
{
public int ID { get; set; }
public int ColumnInt { get; set; }
}
public class CustomAuditEntry : AuditEntry
{
public int CustomProperty { get; set; }
}
public class CustomAuditEntryProperty : AuditEntryProperty
{
public int CustomProperty { get; set; }
}
}
I would suggest a feature of changesets for your audits. This would create a Changeset Id once per save. This would make it easier to see all changes related to a certain entity(e.g. if I save a whole graph, I can just display all changes in the changeset where a given property was affected).
Thanks again for producing a great library.