Skip to content

Instantly share code, notes, and snippets.

@leniency
Created August 23, 2012 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leniency/76f5ea2195adad162d56 to your computer and use it in GitHub Desktop.
Save leniency/76f5ea2195adad162d56 to your computer and use it in GitHub Desktop.
AuxDbContext
/// <summary>
/// Delegate definition for when the database context has been successfully saved.
/// </summary>
public delegate void OnContextSaved();
/// <summary>
/// Database context extensions. Provides additional functionality.
/// </summary>
/// <remarks>
/// <list type="1">
/// <listheader>Additions</listheader>
/// <item>Extra <see cref="System.Data.Entity.Validation.DbEntityValidationException"/> message descriptions.</item>
/// <item>OnSaved event handler. Allows hooks into a successful save.</item>
/// <item>Audit history. Provides a storage model for audit tracking.</item>
/// </list>
/// </remarks>
public abstract class AuxDbContext : DbContext
{
/// <summary>
/// OnSaved Event - called after a successful SaveChanges()
/// </summary>
public event OnContextSaved OnSaved;
/// <summary>
/// Initialize a new instance of Aux.Data.AuxDbContext
/// </summary>
/// <param name="connection">The connection string.</param>
public AuxDbContext(string connection)
: base(connection)
{
// Add an additional OnSaving Event.
// This checks any modified entities to see if they have the
// [Audit] attribute on them for history tracking.
((IObjectContextAdapter)this).ObjectContext.SavingChanges += OnSaving;
}
/// <summary>
/// Saves all changes made in this context to the underlying database.
/// </summary>
/// <returns>The number of objects written to the underlying database.</returns>
public override int SaveChanges()
{
int result;
try
{
result = base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Catch any validation issues and reformat their error.
// The default error gives nothing after the fact so attempt to give
// some more meaningful information in the exceptoin message.
var messages = ex.EntityValidationErrors
.Where(e => !e.IsValid)
.Select(errors => errors
.ValidationErrors.ToString(", ", e => " { " + errors.Entry.Entity.GetType().Name + "." + e.PropertyName + ": " + e.ErrorMessage + " } "));
throw new DbEntityValidationException("Validation failed for one or more entities: " + messages.ToString("\r\n"), ex);
}
if (OnSaved != null)
{
OnSaved();
}
return result;
}
/// <summary>
/// Check for audit tracking while saving a record
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnSaving(object sender, EventArgs args)
{
// Only look at entities that were marked as modified
foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Modified))
{
var properties = entry.Entity.GetType().GetProperties();
// Check each entity property for things with the Audit attribute.
foreach (var property in properties)
{
var audit = property.GetCustomAttributes(typeof(AuditAttribute), true);
// Has a single audit property (multiple not allowed, nor does it make sense)
if (audit != null && audit.Length == 1)
{
var member = entry.Member(property.Name);
// Get the current value.
string current = (member.CurrentValue != null)
? member.CurrentValue.ToString()
: null;
string original = String.Empty;
// Simple property or complex type
if (member is DbPropertyEntry)
{
original = ((member as DbPropertyEntry).OriginalValue != null)
? (member as DbPropertyEntry).OriginalValue.ToString()
: null;
// Check if it really changed, sometimes
// (member as DbPropertyEntry).IsModified throws false positive.
if (original == current)
{
continue; // no change
}
}
else if (member is DbReferenceEntry)
{
// Original values not available for references
original = "{ Reference Type - Original not available. }";
}
var h = new History
{
Changed = DateTime.Now,
User = Thread.CurrentPrincipal.Identity.Name,
OriginalValue = original,
NewValue = current,
Column = member.Name,
Table = (this.IsProxy(entry.Entity)
? entry.Entity.GetType().BaseType
: entry.Entity.GetType())
.FullName,
EntityId = History.FormatEntityKey(this.GetEntityKey(entry.Entity).EntityKeyValues.ToDictionary(k => k.Key, v => v.Value))
};
// Ensure that we have something for the user name
if (String.IsNullOrWhiteSpace(h.User))
{
h.User = "{ Anonymous.User }";
}
Set<History>().Add(h);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment