Wednesday, January 21, 2015

Using ObjectStateManager for Object Tracking in Entity Framework

ObjectStateManager in Entity Framework is responsible for tracking states and changes of an object. In this post let’s see how we can use ObjectStateManager to track object changes in following scenarios.
  • Adding a new item
  • Updating simple property of an existing item
  • Updating complex property of an existing item
  • Adding item to a collection type property of an existing item
  • Removing item to a collection type property of an existing item
  • Deleting an existing item
Let’s jump into action by creating a console application and installing Entity Framework nuget package (I am installing installing the latest as of today which is 6.1.2) the project. In this application, I am going create a code-first sample database with three POCO entity types which are  “Department”, “Hobby” and “Person”. I am modeling the entities and their relationships as follows. I have two helper methods in "Department" and "Hobby" class to return some sample data.

Department
public class Department
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual List<Person> People { get; set; }

    public static List<Department> GetDepartments()
    {
        return new List<Department>()
        {
            new Department()
            {
                Id = 1,
                Name = "Visual C#"
            },
            new Department()
            {
                Id = 2,
                Name = "Microsoft Visual Studio"
            },
        };
    }
}

Hobby
public class Hobby
{
   public int Id { get; set; }
   public string Name { get; set; }
   public virtual List<Person> People { get; set; }

   public static List<Hobby> GetHobbies()
   {
       return new List<Hobby>()
       {
           new Hobby() { Id = 1, Name = "Programming." },
           new Hobby() { Id = 2, Name = "Reading books." },
           new Hobby() { Id = 3, Name = "Listening to music." },
           new Hobby() { Id = 4, Name = "Watching movies." },
       };
   }
}

Person
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual List<Hobby> Hobbies { get; set; }
    public virtual Department Department { get; set; }
}

Following is the class MyContext which is deriving from DbContext.
public class MyContext : DbContext
{
    public DbSet<Department> Departments { get; set; }
    public DbSet<Hobby> Hobbies { get; set; }
    public DbSet<Person> People { get; set; }
}
Now to create the database and to insert some data, let’s run the following.
static void Main(string[] args)
{
    using (MyContext context = new MyContext())
    {
        foreach (var item in Hobby.GetHobbies())
        {
            context.Hobbies.Add(item);
        }
 
        foreach (var item in Department.GetDepartments())
        {
            context.Departments.Add(item);
        }
 
        context.SaveChanges();
 
        context.People.Add(new Person()
            {
                Id = 1,
                Name = "Jaliya Udagedara",
                Department = context.Departments.First(d => d.Id == 1),
                Hobbies = new List<Hobby>()
                {
                    context.Hobbies.First( h => h.Id == 1),
                    context.Hobbies.First( h => h.Id == 2),
                }
            });
 
        context.SaveChanges();
    }
}
Now when I run this, I can see my database created with all the data inserted.

image
Database
Basically the things I have explained so far is basic fundamentals of Entity Framework code-first, and I am sure you all are familiar with those.

Now let’s dive in to deep writing some code using the ObjectStateManager to detect changes once the changes has been made. For that I am creating a method which I am naming as DetectChanges and it will be accepting a parameter of type MyContext.
private static void DetectChanges(MyContext context)
{
 
}
In this method, I will be writing the code to track changes. Now let’s start adding content to above method, first by making sure changes are synchronized with changes in all objects that are tracked by the ObjectStateManager.
((IObjectContextAdapter)context).ObjectContext.DetectChanges();
ObjectStateManager objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
After getting reference to current ObjectContexts’ ObjectStateManager, let’s proceed with getting different ObjectStateEntry’s which has different EntityStates’.

EntityState : Added


First see how we can get all the ObjectStateEntry’s in which the EntityState is Added.
IEnumerable<ObjectStateEntry> addedEntries = objectStateManager.GetObjectStateEntries(EntityState.Added);
Now for each of this ObjectStateEntrys’, let’s see what are the changes. I am creating another method which I am naming as LogAddedEntries and passing the ObjectStateEntry as a parameter.
foreach (ObjectStateEntry entry in addedEntries)
{
    LogAddedEntries(entry);
}
Here the passing ObjectStateEntry can be of two types.
  1. RelationshipEntry
  2. EntityEntry
So inside my LogAddedEntries method I will have check if the added item is a relationship entity or a just a new entity.
private static void LogAddedEntries(ObjectStateEntry entry)
{
    if (entry.IsRelationship) //relationship added
    {
        StringBuilder sb = new StringBuilder();

        sb.AppendLine(string.Format("Adding relationship to : {0}", entry.EntitySet.Name));
        sb.AppendLine();
 
        var currentValues = entry.CurrentValues;

        for (var i = 0; i < currentValues.FieldCount; i++)
        {
            string fName = currentValues.DataRecordInfo.FieldMetadata[i].FieldType.Name;
            EntityKey fCurrVal = (EntityKey)currentValues[i];
 
            sb.AppendLine(string.Format("Table : {0}", fName));
            sb.AppendLine(string.Format("Property Name: {0}", fCurrVal.EntitySetName));
            sb.AppendLine(string.Format("Id : {0}", fCurrVal.EntityKeyValues[0].Value))
        }

        Console.WriteLine(sb.ToString());
        Console.WriteLine("--------------------------------------------");
    }
    else //item added
    {
        StringBuilder sb = new StringBuilder();
 
        sb.AppendLine(string.Format("Adding new item to : {0}", entry.EntitySet.Name));
        sb.AppendLine();

        var currentValues = entry.CurrentValues;

        for (var i = 0; i < currentValues.FieldCount; i++)
        {
            string fName = currentValues.DataRecordInfo.FieldMetadata[i].FieldType.Name;
            var fCurrVal = currentValues[i];

            sb.AppendLine(string.Format("Property Name : {0}", fName));
            sb.AppendLine(string.Format("Property Value : {0}", fCurrVal));
        }

        Console.WriteLine(sb.ToString());
        Console.WriteLine("--------------------------------------------");
    }
}
If the added item is a RelationshipEntry, I am logging the names of two entities which gets into a relationship and by which items. If the added item is a EntityEntry, I am logging the name of the entity in which to the item is getting added and it’s details.


EntityState : Modified


Now same as Added changes, I am getting the ObjectStateEntrys’ in which the EntityState is Modified.
IEnumerable<ObjectStateEntry> modifiedEntries = objectStateManager.GetObjectStateEntries(EntityState.Modified);

foreach (ObjectStateEntry entry in modifiedEntries)
{
    LogModifiedEntries(entry);
}
Now here is my method for logging modified entry details method which I am naming as LogModifiedEntries.
private static void LogModifiedEntries(ObjectStateEntry entry)
{
    StringBuilder sb = new StringBuilder();

    sb.AppendLine(string.Format("Modifying properties in : {0}", entry.EntitySet.Name));
    sb.AppendLine();
 
    var properties = entry.GetModifiedProperties();
 
    for (int i = 0; i < properties.Count(); i++)
    {
        string propertyName = properties.ToArray()[i];
        string OriginalValue = entry.OriginalValues.GetValue(entry.OriginalValues.GetOrdinal(propertyName)).ToString();
        string CurrentValue = entry.CurrentValues.GetValue(entry.CurrentValues.GetOrdinal(propertyName)).ToString();

        sb.AppendLine(string.Format("Property Name : {0}", propertyName));
        sb.AppendLine(string.Format("Original Value : {0}", OriginalValue));
        sb.AppendLine(string.Format("Current Value : {0}", CurrentValue));
    }

    Console.WriteLine(sb.ToString());
    Console.WriteLine("--------------------------------------------");
}
Here I am getting the modified properties and logging the original and current values of them.

EntityState : Deleted


Now let’s get the ObjectStateEntrys’ in which the EntityState is Deleted.
IEnumerable<ObjectStateEntry> deletedEntries = objectStateManager.GetObjectStateEntries(EntityState.Deleted);

foreach (ObjectStateEntry entry in deletedEntries)
{
    LogDeletedEntries(entry);
}
Here is my method for logging deleted entry details method which I am naming as LogDeletedEntries.
private static void LogDeletedEntries(ObjectStateEntry entry)
{
    if (entry.IsRelationship) //relationship deleted
    {
        StringBuilder sb = new StringBuilder();

        sb.AppendLine(string.Format("Deleting relationship from : {0}", entry.EntitySet.Name));
        sb.AppendLine();
 
        var originalValues = entry.OriginalValues;
 
        for (var i = 0; i < originalValues.FieldCount; i++)
        {
            EntityKey fCurrVal = (EntityKey)entry.OriginalValues.GetValue(i);

            sb.AppendLine(string.Format("Property Name : {0}", fCurrVal.EntitySetName));
            sb.AppendLine(string.Format("Property Value : {0}", fCurrVal.EntityKeyValues[0]));
        }

        Console.WriteLine(sb.ToString());
        Console.WriteLine("--------------------------------------------");
    }
    else //entry deleted
    {
        StringBuilder sb = new StringBuilder();

        sb.AppendLine(string.Format("Deleting item from : {0}", entry.EntitySet.Name));
        sb.AppendLine();
 
        var originalValues = entry.OriginalValues;
 
        for (var i = 0; i < originalValues.FieldCount; i++)
        {
            var fCurrVal = entry.OriginalValues.GetValue(i);
            sb.AppendLine(string.Format("Data : {0}", fCurrVal));
        }
 
        Console.WriteLine(sb.ToString());
        Console.WriteLine("--------------------------------------------");
    }
}
Here again I need to check whether the deleted item is a relationship entity or a just a new entity. If the deleted item is a RelationshipEntry, I am logging the names of two entities which get unlinked and by which items. If the deleted item is a EntityEntry, I am logging the name of entity in which the item is getting deleted and it’s identifier.

Now I am almost done. For the demonstration purposes, let’s comment the above lines of codes and write some code to make some changes to existing data in the database. Before calling the context.SaveChanges(), let’s call the DetectChanges method to see what are the changes.

Adding a new item

context.Departments.Add(new Department() { Id = 3, Name = "Microsoft SQL Server" });
DetectChanges(context);
image
Result

Updating simple property of an existing item

context.People.First(p => p.Id == 1).Name = "Jaliya Bandara Udagedara";
DetectChanges(context);

image
Result

Updating complex property of an existing item

context.People.First(p => p.Id == 1).Department = context.Departments.First(d => d.Id == 2);
DetectChanges(context);

image
Result

Adding item to a collection type property of an existing item

context.People.First(p => p.Id == 1).Hobbies.Add(context.Hobbies.First(h => h.Id == 3));
DetectChanges(context);

image
Result

Removing item to a collection type property of an existing item

context.People.First(p => p.Id == 1).Hobbies.Remove(context.Hobbies.First(h => h.Id == 2));
DetectChanges(context);

image
Result

Deleting an existing item

context.Hobbies.Remove(context.Hobbies.First(h => h.Id == 4));
DetectChanges(context);

image
Result

So that’s it. Finally following are list of some things, I think you will find important.
  • Updating of complex property will does not falls to EntityState.Modified category. It will be removing of an existing relationship and adding of an new relationship.
  • Added objects has no OriginalValues.
  • Deleted objects has no CurrentValues.
Hope you all got a good understanding about ObjectStateManager and now you should be able to what you have learnt so far to match your requirements.

I am uploading the full sample to my OneDrive.


Happy Coding.

Regards,
Jaliya