I have tried many different ways and looked at different posts, but still haven't come across a solution for this way of auditing. Below is my DBContext template file. I customised it by adding the OnContextCreated()
partial method and assign the SavingChanges
event to my OnSavingChanges
event handler.
namespace ARSystem.Models
{
public partial class ARSEntities : ObjectContext
{
public ARSEntities()
: base("name=ARSEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public string UserName { get; set; }
List<DBAudit> auditTrailList = new List<DBAudit>();
public enum AuditActions
{
I,
U,
D
}
partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(OnSavingChanges);
}
public void OnSavingChanges(object sender, EventArgs e)
{
IEnumerable<ObjectStateEntry> changes = this.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified);
foreach (ObjectStateEntry stateEntryEntity in changes)
{
if (!stateEntryEntity.IsRelationship &&
stateEntryEntity.Entity != null &&
!(stateEntryEntity.Entity is DBAudit))
{//is a normal entry, not a relationship
DBAudit audit = this.AuditTrailFactory(stateEntryEntity, UserName);
auditTrailList.Add(audit);
}
}
if (auditTrailList.Count > 0)
{
foreach (var audit in auditTrailList)
{//add all audits
this.AddToDBAudit(audit);
}
}
}
private DBAudit AuditTrailFactory(ObjectStateEntry entry, string UserName)
{
DBAudit audit = new DBAudit();
audit.AuditId = Guid.NewGuid().ToString();
audit.RevisionStamp = DateTime.Now;
audit.TableName = entry.EntitySet.Name;
audit.UserName = UserName;
if (entry.State == EntityState.Added)
{//entry is Added
audit.NewData = GetEntryValueInString(entry, false);
audit.Actions = AuditActions.I.ToString();
}
else if (entry.State == EntityState.Deleted)
{//entry in deleted
audit.OldData = GetEntryValueInString(entry, true);
audit.Actions = AuditActions.D.ToString();
}
else
{//entry is modified
audit.OldData = GetEntryValueInString(entry, true);
audit.NewData = GetEntryValueInString(entry, false);
audit.Actions = AuditActions.U.ToString();
IEnumerable<string> modifiedProperties = entry.GetModifiedProperties();
//assing collection of mismatched Columns name as serialized string
audit.ChangedColumns = XMLSerializationHelper.XmlSerialize(modifiedProperties.ToArray());
}
return audit;
}
private string GetEntryValueInString(ObjectStateEntry entry, bool isOrginal)
{
if (entry.Entity is EntityObject)
{
object target = CloneEntity((EntityObject)entry.Entity);
foreach (string propName in entry.GetModifiedProperties())
{
object setterValue = null;
if (isOrginal)
{
//Get orginal value
setterValue = entry.OriginalValues[propName];
}
else
{
//Get orginal value
setterValue = entry.CurrentValues[propName];
}
//Find property to update
PropertyInfo propInfo = target.GetType().GetProperty(propName);
//update property with orgibal value
if (setterValue == DBNull.Value)
{//
setterValue = null;
}
propInfo.SetValue(target, setterValue, null);
}//end foreach
XmlSerializer formatter = new XmlSerializer(target.GetType());
XDocument document = new XDocument();
using (XmlWriter xmlWriter = document.CreateWriter())
{
formatter.Serialize(xmlWriter, target);
}
return document.Root.ToString();
}
return null;
}
public EntityObject CloneEntity(EntityObject obj)
{
DataContractSerializer dcSer = new DataContractSerializer(obj.GetType());
MemoryStream memoryStream = new MemoryStream();
dcSer.WriteObject(memoryStream, obj);
memoryStream.Position = 0;
EntityObject newObject = (EntityObject)dcSer.ReadObject(memoryStream);
return newObject;
}
public DbSet<Student> Students { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<aspnet_Applications> aspnet_Applications { get; set; }
public DbSet<aspnet_Membership> aspnet_Membership { get; set; }
public DbSet<aspnet_Roles> aspnet_Roles { get; set; }
public DbSet<aspnet_SchemaVersions> aspnet_SchemaVersions { get; set; }
public DbSet<aspnet_Users> aspnet_Users { get; set; }
public DbSet<vw_aspnet_Applications> vw_aspnet_Applications { get; set; }
public DbSet<vw_aspnet_MembershipUsers> vw_aspnet_MembershipUsers { get; set; }
public DbSet<vw_aspnet_Roles> vw_aspnet_Roles { get; set; }
public DbSet<vw_aspnet_Users> vw_aspnet_Users { get; set; }
public DbSet<vw_aspnet_UsersInRoles> vw_aspnet_UsersInRoles { get; set; }
public DbSet<Cours> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Modules> Modules { get; set; }
public DbSet<EnrollmentsByCourse> EnrollmentsByCourse { get; set; }
public DbSet<EnrollmentsByCourseAudit> EnrollmentsByCourseAudit { get; set; }
public DbSet<DBAudit> DBAudit { get; set; }
}
}
However, when I compile, i get the error message that:
Error 1 'ARSystem.Models.ARSEntities.OnModelCreating(System.Data.Entity.DbModelBuilder)': no suitable method found to override C:\Users\mngum\Documents\Visual Studio 2010\Projects\ARSystem\ARSystem\Models\ARSystem.Context.cs 35 33 ARSystem
I cannot see the OnContextCreated method in the DBContext metadata class but i can find it in the edmx designer. Please let me know how i can implement the OnContextCreated()
method such that I can override the SavingChanges
event for auditing purposes.
DbContext
does not have an OnContextCreated
event but that's not a problem because you don't need it to achieve the same. Instead with DbContext
the SaveChanges
method is overridable. So instead of your OnSavingChanges
event handler you use:
public override int SaveChanges()
{
// custom code...
return base.SaveChanges();
}
This method will be called whenever you call ARSEntities.SaveChanges()
and you can perform custom actions before you call the base.SaveChanges()
of the base DbContext
(ARSEntities
must be derived from DbContext
of course.)
You also can access the underlying ObjectContext
from the DbContext
:
public override int SaveChanges()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
// use methods and properties of ObjectContext now like
// objectContext.ObjectStateManager, etc.
// custom code...
return base.SaveChanges();
}
Here was a similar question and answer about change auditing with EF 4.1/DbContext
:
Entity Framework 4.1 DbContext Override SaveChanges to Audit Property Change