Wednesday, 22 May 2013

Custom Change Tracking Using The Entity Framework

Change tracking is a common feature I find I need to implement in my applications. I like to warn users about the potential loss of data if they cancel without saving changes, and when saving those changes I like to update only those fields in the database that have changed as it's inefficient to update every field.

However, I also find change tracking a rather laborious feature to implement so I spent some time trying to come up with an elegant, simple way of adding this feature to my applications. Then I discovered the Entity Framework, which amongst other things, takes care of change tracking for you. Great, but what if you can't use the Entity Framework, perhaps your data store is not compatible.

It got me thinking, is there an easy way to utilise the Entity Framework's change tracking functionality in any application you want. It turns out you can and it is relatively simple. First you will need to download and install the Entity Framework if you have not already done so, you can find it here.

I've created a simple Console Application to show how this works. You will need to add three references to the following DLL's, EntityFramework, System.Data.Entity and System.ComponentModel.DataAnnotations. In this example we will create an Employee class from which we will create the objects to track. We will also create a derived type, EmployeeContext, from System.Data.Entity.DbContext. This is where the change tracking will take place for us. Here is the Employee class.

  1. class Employee
  2. {
  3.     [Key]
  4.     public long Id { get; set; }
  5.     public string FirstName { get; set; }
  6.     public string LastName { get; set; }
  7.     public DateTime DateOfBirth { get; set; }
  8. }

There is one thing of note here. The EntityFramework requires all tracked objects to have a unique key. In this example the Id property will be our unique key, and we inform our EmployeeContext object of this by applying the System.ComponentModel.DataAnnotations.KeyAttribute to the property. Here is how we create our EmployeeContext type derived from DbContext.

  1. class EmployeeContext : DbContext
  2. {
  3.     public DbSet<Employee> Employees { get; set; }
  4. }

In this derived type we need to add properties of type DbSet<TEntity> (in this example DbSet<Employee>) for any of the objects we would like to track. Here is how we use our EmployeeContext.

  1. class Program
  2. {
  3.     static void Main(string[] args)
  4.     {
  5.         Employee joeBloggs = new Employee
  6.         {
  7.             Id = 1,
  8.             FirstName = "Joe",
  9.             LastName = "Bloggs",
  10.             DateOfBirth = new DateTime(1970, 11, 2)
  11.         };
  13.         Employee joanSmith = new Employee
  14.         {
  15.             Id = 2,
  16.             FirstName = "Joan",
  17.             LastName = "Smith",
  18.             DateOfBirth = new DateTime(1978, 4, 15)
  19.         };
  21.         Database.SetInitializer<EmployeeContext>(null);
  22.         EmployeeContext context = new EmployeeContext();
  23.         context.Employees.Attach(joanSmith);
  24.         context.Employees.Attach(joeBloggs);
  26.         joanSmith.LastName = "Jones";
  28.         var employeeChanges = context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged);
  29.     }
  30. }

In this bit of code we create two employee objects for Joe Bloggs and Joan Smith. We then make a call to Database.SetInitializer<TContext> for our EmployeeContext passing through null as a parameter. This tells the EntityFramework to supress any database initialisation logic as we are not actually mapping these objects to a database via the EntityFramework.

Next we attach our two employees to the EmployeeContext's Employees DbSet, this method will add our employee objects to the DbSet in an Unchanged state. There is also an Add method available on DbSet, if we use this instead of the Attach method our objects will be added in the New state.

Next Joan Smith gets married and changes her name to Joan Jones, lucky lady! We can now query our EmployeeContext for any changes via the ChangeTracker property. We call the Entries method and filter where the state is no longer Unchanged.

If you run this code you should find we have one object returned in the Modified state. You may notice the object returned is actually of type DbEntityEntry which exposes OriginalValues and CurrentValues properties. These can be used to find out exactly what properties have been changed.

No comments:

Post a Comment