Working With Change Tracking Proxy in Entity Framework 6.0

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 class must be public and not sealed.
  • All properties must be public or protected with virtual getters and setters.
  • Collection type navigation must be a type ICollection<T>. They cannot be IList<T>, List<T>, HashSet<T>, and so on.

The following are the advantages of change tracking proxies.

  • Changes are detected immediately for the tracked entities instead of when DetectChanges is called.
  • In some scenarios when may get performance gains

The following are the disadvantages of change tracking proxies.

  • The rules are so restrictive.
  • Complex properties still require snapshot change-tracking.
  • The Behavior of entities may be different when being tracked by the context because proxies cannot use the context object when the entity is not tracked.
  • It must use the DbSet.Create a method to create instances of the entity classes. If we create an entity instance using the "NEW" keyword then it will not be change-tracked.

Performance of change-tracking proxies

  • The Performance of change-tracking proxies may be both advantageous and disadvantageous. Performance depends on what we do with the entities.
  • Change-tracking proxies are more useful and provide good performance when many entities are being tracked and changes are made to some of them. In snapshot change tracking, the system needs to scan and compare all entities to detect changes. With change tracking proxies, every change is detected immediately without the scanning of entities.
  • Now consider just the reverse scenario. There are several changes being made on tracked entities. There is an extra cost of recording changes made on the entities in this case, so performance will be worse.
  • Now consider the scenario in which the same values are being assigned in the properties of entities. In the snapshot change tracking changes are detected at the time of the DetectChanges() method called but with a Change Tracking proxy, every change is detected immediately, so the performance of a Change Tracking proxy is again worth in this case.

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.