Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
214 views
in Technique[技术] by (71.8m points)

c# - original and current values the same - change tracking is not working

After following this awesome tutorial, my change tracker is not showing that the values have changed.

Here's my method:

    public override int SaveChanges()
    {
        var modifiedEntities = ChangeTracker.Entries()
            .Where(p => p.State == EntityState.Modified).ToList();
        var now = DateTime.UtcNow;

        foreach (var change in modifiedEntities)
        {
            var entityName = change.Entity.GetType().Name;
            var primaryKey = GetPrimaryKeyValue(change);

            foreach (var prop in change.OriginalValues.PropertyNames)
            {
                var originalValue = change.OriginalValues[prop].ToString();
                var currentValue = change.CurrentValues[prop].ToString();
                if (originalValue != currentValue)
                {
                    ChangeLog log = new ChangeLog()
                    {
                        EntityName = entityName,
                        PrimaryKeyValue = primaryKey.ToString(),
                        PropertyName = prop,
                        OldValue = originalValue,
                        NewValue = currentValue,
                        DateChanged = now
                    };
                    ChangeLogs.Add(log);
                }
            }
        }
        return base.SaveChanges();
    }

Why is the originalValue is the same as the currentValue?

enter image description here

When making a change, for some reason the original and current value remain the same. What am I doing wrong?

The ChangeTracker is showing that 1 record has been changed!

enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...