idea muy, muy áspera:
foreach (var property in dbEntityEntry.Entity.GetType().GetProperties())
{
DbPropertyEntry propertyEntry = dbEntityEntry.Property(property.Name);
if (propertyEntry.IsModified)
{
Log.WriteAudit("Entry: {0} Original :{1} New: {2}", property.Name,
propertyEntry.OriginalValue, propertyEntry.CurrentValue);
}
}
no tengo ni idea de si esto realmente trabajar en detalle, pero esto es algo que me gustaría probar como un primer paso. Por supuesto, podría haber más de una propiedad que haya cambiado, por lo tanto, el bucle y quizás varias llamadas de WriteAudit
.
El material de reflexión dentro de SaveChanges podría convertirse en una pesadilla de rendimiento.
Editar
Tal vez sea mejor para acceder a la ObjectContext
subyacente. Entonces algo como esto es posible:
public class TestContext : DbContext
{
public override int SaveChanges()
{
ChangeTracker.DetectChanges(); // Important!
ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;
List<ObjectStateEntry> objectStateEntryList =
ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added
| EntityState.Modified
| EntityState.Deleted)
.ToList();
foreach (ObjectStateEntry entry in objectStateEntryList)
{
if (!entry.IsRelationship)
{
switch (entry.State)
{
case EntityState.Added:
// write log...
break;
case EntityState.Deleted:
// write log...
break;
case EntityState.Modified:
{
foreach (string propertyName in
entry.GetModifiedProperties())
{
DbDataRecord original = entry.OriginalValues;
string oldValue = original.GetValue(
original.GetOrdinal(propertyName))
.ToString();
CurrentValueRecord current = entry.CurrentValues;
string newValue = current.GetValue(
current.GetOrdinal(propertyName))
.ToString();
if (oldValue != newValue) // probably not necessary
{
Log.WriteAudit(
"Entry: {0} Original :{1} New: {2}",
entry.Entity.GetType().Name,
oldValue, newValue);
}
}
break;
}
}
}
}
return base.SaveChanges();
}
}
He usado esto en EF 4.0. No puedo encontrar el método correspondiente en GetModifiedProperties
(que es la clave para evitar el código de reflexión) en la API DbContext
.
Editar 2
Importante: Cuando se trabaja con entidades POCO el código anterior tiene que llamar DbContext.ChangeTracker.DetectChanges()
al principio. El motivo es que base.SaveChanges
se llama demasiado tarde aquí (al final del método). base.SaveChanges
llama a DetectChanges
internamente, pero como queremos analizar y registrar los cambios antes, debemos llamar al DetectChanges
manualmente para que EF pueda encontrar todas las propiedades modificadas y establecer los estados en el rastreador de cambios correctamente.
hay posibles situaciones en las que el código puede funcionar sin llamar DetectChanges
, por ejemplo, si se utilizan métodos DbContext/DbSet como Add
Remove
o después de las últimas modificaciones de las propiedades se hacen ya que estos métodos también se llaman DetectChanges
internamente. Pero si, por ejemplo, una entidad acaba de cargarse desde DB, se cambian algunas propiedades y luego se llama a este derivado SaveChanges
, la detección automática de cambios no ocurriría antes del base.SaveChanges
, lo que finalmente daría como resultado entradas de registro faltantes para las propiedades modificadas.
He actualizado el código anterior en consecuencia.
Esto funciona, pero parece que todas las propiedades se modifican de acuerdo con esto. Investigará más, pero está bien para la pregunta original. Creo que esto me da lo que necesito. –
Esto no funciona para mí. Los viejos valores siempre están vacíos. ¿Funciona esto solo para POCO y 4.1 y más? – Jonx
@Jonx: Sí, es EF 4.1 ('DbContext') y POCO. Sin embargo, puede hacer algo similar para <= EF 4.0 ('ObjectContext'), la única diferencia es que no sobrescribe' SaveChanges' sino que implementa un controlador de eventos ('OnSavingChanges' o similar, no recuerdo exactamente). – Slauma