Introduction

Entity Framework is able to track the changes made to entities and their relations, so the correct updates are made on the database when the SaveChanges method of context is called. This is a key feature of the Entity Framework. Change tracking happens through snapshot change tracking for the most POCO entity type. In the snapshot change tracking, a snapshot of the entity is taken when it loads or is attached by context and this snapshot of the entity is used to track changes on an entity by comparing the current value with the original value.

The Change Tracking tracks changes while adding new record(s) to the entity collection, modifying or removing existing entities. Then all the changes are kept by the DbContext level. These track changes are lost if they are not saved before the DbContext object is destroyed.

Automatic change tracking is enabled by default in Entity Framework. We can disable change tracking by setting the AutoDetectChangesEnabled property of DbContext to false. If this property is set to true then the Entity Framework maintains the state of entities.

using (Entities Context = new Entities())
{
    Context.Configuration.AutoDetectChangesEnabled = true;
}

Example

Suppose I have a table called “DepartmentMaster” and I am doing some operations like create, update and delete. Now I want to track all changes on this entity.

Department master

the following is the initial test data of the Department Master table.

Test Data

Code for Getting Changed entity using Change Tracker of Context

The DbContext.ChangeTracker.Entries() method returns all the entities tracked by the DbContext. These entities are in the form of DbEntityEntry. Using the DbEntityEntry.Entity Property, we can determine the tracked entity with its state. The DbEntityEntry instances always contain a non-null Entity and also Stub entities and Relationship entries are not a part of these DbEntityEntry instances, so we do not need to do further filtration for these.

The following code can help us determine the tracked entities. Here the AuditLog class is useful for storing the details of the tracked entities, like table name, column name, action type, original value, and new value.

public class AuditLog
{
    public string State { get; set; }
    public string TableName { get; set; }
    public string RecordID { get; set; }
    public string ColumnName { get; set; }
    public string NewValue { get; set; }
    public string OriginalValue { get; set; }
}

public static List<AuditLog> GetAuditLogData(Entities Context)
{
    List<AuditLog> AuditLogs = new List<AuditLog>();
    var changeTrack = Context.ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified);
    foreach (var entry in changeTrack)
    {
        if (entry.Entity != null)
        {
            string entityName = string.Empty;
            string state = string.Empty;
            switch (entry.State)
            {
                case EntityState.Modified:
                    entityName = ObjectContext.GetObjectType(entry.Entity.GetType()).Name;
                    state = entry.State.ToString();
                    foreach (string prop in entry.OriginalValues.PropertyNames)
                    {
                        object currentValue = entry.CurrentValues[prop];
                        object originalValue = entry.OriginalValues[prop];
                        if (!currentValue.Equals(originalValue))
                        {
                            AuditLogs.Add(new AuditLog
                            {
                                TableName = entityName,
                                State = state,
                                ColumnName = prop,
                                OriginalValue = Convert.ToString(originalValue),
                                NewValue = Convert.ToString(currentValue),
                            });
                        }
                    }
                    break;
                case EntityState.Added:
                    entityName = ObjectContext.GetObjectType(entry.Entity.GetType()).Name;
                    state = entry.State.ToString();
                    foreach (string prop in entry.CurrentValues.PropertyNames)
                    {
                        AuditLogs.Add(new AuditLog
                        {
                            TableName = entityName,
                            State = state,
                            ColumnName = prop,
                            OriginalValue = string.Empty,
                            NewValue = Convert.ToString(entry.CurrentValues[prop]),
                        });
                    }
                    break;
                case EntityState.Deleted:
                    entityName = ObjectContext.GetObjectType(entry.Entity.GetType()).Name;
                    state = entry.State.ToString();
                    foreach (string prop in entry.OriginalValues.PropertyNames)
                    {
                        AuditLogs.Add(new AuditLog
                        {
                            TableName = entityName,
                            State = state,
                            ColumnName = prop,
                            OriginalValue = Convert.ToString(entry.OriginalValues[prop]),
                            NewValue = string.Empty,
                        });
                    }
                    break;
                default:
                    break;
            }
        }
    }
    return AuditLogs;
}

Test application code

In the test application, I have done create, update, and delete operations on the DepartmentMaster entity and am trying to catch changes using the Change tracking proxy.

static void Main(string[] args)
{
    List<AuditLog> auditLogs = new List<AuditLog>();
    using (Entities Context = new Entities())
    {
        // Add new Value
        DepartmentMaster dept = Context.DepartmentMasters.Create();
        dept.Name = "New Added Department";
        dept.Code = "AAA";
        Context.DepartmentMasters.Add(dept);

        // Modify the existing Value
        DepartmentMaster deptUpdate = Context.DepartmentMasters.Find(1);
        deptUpdate.Code = "BBB";

        // Delete the existing value
        DepartmentMaster deptDelete = Context.DepartmentMasters.Find(2);
        Context.Entry(deptDelete).State = EntityState.Deleted;

        auditLogs = GetAuditLogData(Context);

        Console.WriteLine("Table Name \t\t Column Name \t Action \t Old Value \t New Value");
        Console.WriteLine("--------------------------------------------------------------------------------");
        foreach (AuditLog a in auditLogs)
        {
            if (string.IsNullOrEmpty(a.OriginalValue))
            {
                a.OriginalValue = "\t\t";
            }
            if (string.IsNullOrEmpty(a.NewValue))
            {
                a.NewValue = "\t";
            }
            Console.WriteLine(a.TableName + " \t " + a.ColumnName + "\t " + a.State + " \t " + a.OriginalValue + " \t " + a.NewValue);
        }

        Context.SaveChanges();
    }
}

Output

Output

The following is a snapshot of the database after code execution.

Database snap

The following are some rules that must be followed by classes to enable Change-tracking.

The following are the advantages of change tracking proxies.

The following are the disadvantages of change tracking proxies.

Performance of change-tracking proxies

Complex Type and Change Tracking proxies

The same as Lazy loading, Change Tracking is also not supported for complex types. The reason is the complex type may not be real entities and the complex type based property is itself considered for change tracking.