The code you have provided for your SaveChanges()
works just fine. I've verified this in a test project as I've used something similar for a base auditing. This means the problem you're seeing will most likely be something with how you are updating your entity.
For example, updating an entity like this works just fine:
public void UpdateWidget(int widgetId, string name)
{
using(var context = new AppDbContext())
{
var widget = context.Widgets.Single(x => x.WidgetId == widgetId);
widget.Name = name;
context.SaveChanges();
}
}
If the passed in name differs from the Widget's existing name, the tracked entity will be recognized as modified, and your SaveChanges
override will show the original value equals the old value while the CurrentValues will have the new name value. If the name didn't actually change, it won't even come up as a modifed record.
A common pitfall devs hit is when passing around serialized entities. For example:
public void UpdateWidget(Widget widget)
{
using(var context = new AppDbContext())
{
context.Widgets.Attach(widget);
context.Entry(widget).State = EntityState.Modified;
context.SaveChanges();
}
}
In this case widget
is a detached entity that has been modified, say by a bound model in MVC and passed back to the Controller to be saved. In this case the entity will be marked as Modified after it's attached, but the change tracker has not captured the change because the entity was not being tracked by the saving context, or possibly any context when the modification happened. Both the Current and Original values will be the same updated value.
As a very crude example to see this in action you can use this:
Widget widget = null;
using (var context1 = new AppDbContext())
{
widget = context.Widgets.First();
widget.Name = "Something New";
}
// Or change here...
// widget.Name = "Something Else New";
using (var context2 = new AppDbContext())
{
context.Widgets.Attach(widget);
context.Entry(widget).State = EntityState.Modified;
context.SaveChanges();
}
The widget was read and tracked by context1, however that context scope ends. This is like what happens when you load a Widget and serialize it to a view. As far as the view is concerned the entity is just a POCO class. The change tracking is overseen by the Context, not the entity. When context2 attaches the entity and marks it as modified so it can be saved, that will satisfy your first check to find a modified entity, but then all of the columns original and current values will reflect the state of the entity when it was attached.
If the code relies on passing entities around then probably the best solution is to use Automapper to transfer changes from the passed, untracked entity back into a tracked instance for the context.
public void UpdateWidget(Widget widget)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Widget, Widget>());
var mapper = config.CreateMapper();
using(var context = new AppDbContext())
{
var dbWidget = context.Widgets.Single(x => x.WidgetId == widget.WidgetId);
mapper.Map(widget, dbWidget);
context.SaveChanges();
}
}
Alternatively you could do this manually by copying over any applicable fields from the source Widget to the DB Widget. Note that it is worth adding Ignore
conditions using Automapper's ForMember
configuration to prevent values you don't expect/allow to be changed. Like the first original method that just loaded an entity and updated it's name, the change tracker will only flag and update entities for the fields that actually changed rather than all fields, and it will give you the original and updated values as you expect on SaveChanges
.