Entity Framework (EF) – Events

Introduction

In this post we are going to take a look at some of the events that are raised by various parts of the Microsoft ADO.NET Entity Framework (EF). I’m using the recently released Beta 1 of Visual Studio 2010 / .Net Framework 4.0 for this demo but most of the information also applies to the first version of EF released in .Net Framework 3.5 SP1.

I am using a simple model with 2 entities that have a one to many association between them. I’m using the default code generated entities but the same events apply to the use of POCO entities or any variation in between, there is one example at the end where I provide alternate code for people using POCO. here is the model I am using;

I’m also overriding the ToString() method on the two entity classes to make it a little easier to see what’s going on;

public partial class Employee
{
     public override string ToString()
     {
          return string.Format("Employee[{0}]", this.Name);
    }
}

public partial class Office
{
    public override string ToString()
    {
         return string.Format("Office[{0}]", this.Number);
    }
}

ObjectStateManager.ObjectStateManagerChanged

This event is raised whenever an entity leaves or enters the context, this includes query as well as add/attach methods and the following code shows this event in action;

using (EmployeeEntities ctx = new EmployeeEntities())
{ 

    //ObjectStateManager.ObjectStateManagerChanged is raised whenever
    //an entity leaves or enters the context
    ctx.ObjectStateManager.ObjectStateManagerChanged += (sender, e) =>
    {
        Console.WriteLine(string.Format(
            "ObjectStateManager.ObjectStateManagerChanged | Action:{0} Object:{1}"
            , e.Action
            , e.Element));
    };

    //Create an Employee in the database so that we can query for it
    Guid existingEmployeeId = Guid.NewGuid();
    ctx.ExecuteStoreCommand(string.Format("INSERT INTO Employee (EmployeeId, Name) VALUES ('{0}', 'UnchangedEmployee')", existingEmployeeId));

    //When an entity is queried for we get an added event
    Console.WriteLine("Fetching via Query");
    Employee emp_unchanged = ctx.Employees.Where(e => e.EmployeeId.Equals(existingEmployeeId)).Single();

    //When an entity is added we also get an added event
    Console.WriteLine("\nPerforming Add");
    Employee emp_added = new Employee { EmployeeId = Guid.NewGuid(), Name = "AddedEmployee" };
    ctx.AddObject("Employees", emp_added);
} 

When the above code is run we get the following written to the console;

Fetching via Query
ObjectStateManager.ObjectStateManagerChanged | Action:Add Object:Employee[UnchangedEmployee]

Performing Add
ObjectStateManager.ObjectStateManagerChanged | Action:Add Object:Employee[AddedEmployee]

We also get a removed event raised when objects leave the context so if we delete the two employees we will get two removal events. I’m deleting both because the behavior in terms of the context is actually a little different for each object. In the case of the added employee there is nothing to be updated in the database so the object is simply detached from the context, the unchanged employee however remains in the context but is marked as deleted. At this stage there is no difference in behavior as far as events go but when we save back out to the database our unchanged employee will get detached from the context once it has been deleted from the database, this actually raises a second removal event, this behavior is shown in the ObjectContext.SavingChanges section.

So adding the following code;

//Deleting an entity raises a removed event
Console.WriteLine("\nDeleting Added Employee");
ctx.DeleteObject(emp_added);

Console.WriteLine("\nDeleting Unchanged Employee");
ctx.DeleteObject(emp_unchanged);

Results in the following output;

Deleting Added Employee
ObjectStateManager.ObjectStateManagerChanged | Action:Remove Object:Employee[AddedEmployee]

Deleting Unchanged Employee
ObjectStateManager.ObjectStateManagerChanged | Action:Remove Object:Employee[UnchangedEmployee]

ObjectContext.SavingChanges

This one’s pretty self explanatory and is raised prior to writing data out to the database once ObjectContext.SaveChanges() is called. As I mentioned above deleting an object will result in a removal event being raised at the time of deletion but we will also get a second removal event when the object is detached from the context as part of the save process. So adding the following code;

//ObjectContext.SavingChanges is raised prior to writing changes to database
ctx.SavingChanges += (sender, e) =>
{
    Console.WriteLine(string.Format("ObjectContext.SavingChanges"));
};

Console.WriteLine("\nPerforming Save");
ctx.SaveChanges();

Will give us the following output;

Performing Save
ObjectContext.SavingChanges
ObjectStateManager.ObjectStateManagerChanged | Action:Remove Object:Employee[UnchangedEmployee]

RelatedEnd.AssociationChanged

This event gets raised whenever EF becomes aware that an object has been added or removed from a navigation property. If you are using the default code generated entities or change tracking POCO proxies then this occurs at the same time the change is made to the actual navigation property, if you are using pure POCO classes then the change is detected when you call ObjectContext.DetectChanges() or ObjectContext.SaveChanges() (calling SaveChanges() includes an implicit call to DetectChanges()).

So using the code generated entities we can write the following code;

using (EmployeeEntities ctx = new EmployeeEntities())
{
    //Create two unrelated objects
    Employee emp = new Employee { EmployeeId = Guid.NewGuid(), Name = "John Doe" };
    ctx.AddToEmployees(emp);

    Office off = new Office { OfficeId = Guid.NewGuid(), Number = "1234" };
    ctx.AddToOffices(off);

    //This event is raised when a navigation property changes
    emp.OfficeReference.AssociationChanged += (sender, e) =>
    {
        Console.WriteLine(string.Format(
            "RelatedEnd.AssociationChanged | Employee.Office | Action:{0} Object:{1}",
            e.Action,
            e.Element));
    }; 

    off.Employees.AssociationChanged += (sender, e) =>
    {
        Console.WriteLine(string.Format(
            "RelatedEnd.AssociationChanged | Office.Employees | Action:{0} Object:{1}"
            , e.Action
            , e.Element));
    };

    //Relate the two entities
    Console.WriteLine("\nSetting Employee.Office");
    emp.Office = off;
}

And we will get the following output;

Setting Employee.Office
RelatedEnd.AssociationChanged | Office.Employees | Action:Add Object:Employee[John Doe]
RelatedEnd.AssociationChanged | Employee.Office | Action:Add Object:Office[1234]

Now if you are using POCO classes then your navigation properties aren’t going to derive from RelatedEnd as they will be List or some other collection type, EF still maintains the RelatedEnd structures and we can get hold of them from the ObjectStateManager as follows;

ctx.ObjectStateManager
    .GetRelationshipManager(emp)
    .GetRelatedReference("EmployeeModel.FK_Employee_Office", "Office")
   .AssociationChanged += ...

ctx.ObjectStateManager
    .GetRelationshipManager(off)
   .GetRelatedCollection("EmployeeModel.FK_Employee_Office", "Employee")
    .AssociationChanged += ...

If you are after something a bit more generic there is a GetRelatedEnd() on RelationshipManager which will give you an IRelatedEnd, but the AssociationChanged event is not included in the interface so you would have to cast it back to RelatedEnd, it’s definitely not good programming practice but it is safe to do in the current EF implementation (but may not be in future releases). The code would look like this;

// <BadCodingPractice>
((RelatedEnd)ctx.ObjectStateManager
    .GetRelationshipManager(off)
    .GetRelatedEnd("EmployeeModel.FK_Employee_Office", "Employee"))
    .AssociationChanged += ...
//</BadCodingPractice>