The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.


 

In my last post on POCO, I mentioned the fact that there are two types of change tracking possibilities with POCO: Snapshot based Change Tracking and Notification based Change Tracking with Proxies. In this post, I would like to drill into both options a bit further and cover the advantages and disadvantages of both, along with the implications of using either approach. We will also be posting ideas on the EF Design Blog regarding some of the feedback we have received so far about POCO support in Entity Framework 4.0.

Snapshot based Change Tracking (pure POCO without proxies)

As I mentioned in Part 2 of this series, Snapshot based change tracking is what you get with pure POCO entities that don’t use proxies to handle change tracking. This is a simple change tracking solution that relies on complete snapshots of before and after values being maintained by the Entity Framework. These values are compared during SaveChanges to determine what has truly changed from their original values. In this model, unless you are using Lazy loading, the runtime type of your entities is the same as you define for your POCO entities.

There really isn’t anything problematic with this approach, and if you care about the runtime purity of your entity types without the use of proxies, it is perfectly fine to rely on this approach and take the route of pure POCO entities that don’t rely on proxy types for additional functionality.

The only catch with Snapshot based change tracking is that there are a few things you have to be aware of: because there is no direct notification of changes to the Entity Framework anytime your objects change, Entity Framework’s Object State manager will go out of sync with your own object graph.

 

Let’s take an example to see how this manifests itself:

Customer
 customer = (
from
 c in context.Customers
where c.CustomerID == "ALFKI"
select
 c).Single();

ObjectStateEntry
 ose = context.ObjectStateManager.GetObjectStateEntry(customer);

Console
.WriteLine("Customer object state: {0}", ose.State); // Unchanged

customer.Country = "UK";

Console
.WriteLine("Customer object state: {0}", ose.State); // Still Unchanged

In this example, Customer is a pure POCO type. Unlike with EntityObject or IPOCO based entities, making changes to the entity doesn’t automatically keep the state manager in sync because there is no automatic notification between your pure POCO entities and the Entity Framework. Therefore, upon querying the state manager, it thinks that the customer object state is Unchanged even though we have explicitly made a change to one of the properties on the entity.

If you were to perform SaveChanges without choosing to acceptChangesDuringSave, you will see that the state becomes Modified after the save. This is because the snapshot based change tracking kicked in during save and detected the change. Of course, the default SaveChanges call will leave the state back to Unchanged since the default Save behavior is to accept changes during save.

We’ll discuss more about what to do when you would actually need to keep things in sync between your objects, the object graph and the state manager in a little bit. But first let’s look at the other type of change tracking that’s available to you with POCO.

Notification based Change Tracking with Proxies

This is a different solution if you care about very efficient and up to date change tracking as you make changes to your entity values, relationships and object graph: Proxy based Change Tracking. You can leverage proxy based change tracking for a particular entity type if you declare all mapped properties on that entity type as virtual.

Entities that are tracked using change tracking proxies will always be in sync with the Entity Framework’s Object state manager because the proxies will notify the Entity Framework of the changes to values and relationships as each change happens. On the whole, this makes it more efficient to track changes because the object state manager can skip comparing original and current values of properties that it knows haven’t changed.

For all intents and purposes, the change tracking behavior you get with proxies is identical to the change tracking behavior you get with EntityObject based non-POCO entities or IPOCO entities.

Let’s take the same example:

Customer
 customer = (
from
 c in context.Customers
where c.CustomerID == "ALFKI"
select
 c).Single();

ObjectStateEntry
 ose = context.ObjectStateManager.GetObjectStateEntry(customer);

Console
.WriteLine("Customer object state: {0}", ose.State); // Unchanged

customer.Country = "UK";

Console
.WriteLine("Customer object state: {0}", ose.State); // Modified

The example is self explanatory – Object State Manager was notified of the changes as you made changes to your entity. There is no additional overhead you have to incur during SaveChanges.

Proxy based change tracking does however mean that the runtime type of your entities is not exactly the same type you defined – rather, it is a subclass of your own type. This can be undesirable in many cases (such as serialization) and you have to choose the approach that best works within the constraints and the requirements of your application and domain.

Keeping the State Manager in Sync without using Proxies

There are pros and cons to both approaches and it certainly won’t be surprising that the simplicity and elegance of non proxy based pure POCO will mean that it becomes the default choice for many of you. So let’s look at how you can keep the state manager in sync with your object graph when you work in this mode.

ObjectContext.DetectChanges()

There is one method that matters the most when you work with snapshot based pure POCO entities: ObjectContext.DetectChanges().

This API should be used explicitly whenever you have made changes to your object graph, and it will inform the state manager that it needs to sync up with your graph.

ObjectContext.SaveChanges will implicitly invoke DetectChanges by default, so you don’t need to explicitly call DetectChanges if all you are doing is making a series of changes to your objects and immediately performing a Save. However, keep in mind that depending on the size of your graph, DetectChanges may turn out to be expensive (also DetectChanges might be unnecessary depending on what you are doing) and therefore it is possible for you to skip DetectChanges that happens implicitly during SaveChanges - more on this when we discuss SaveChanges additions that are new to Entity Framework 4.0.

Let’s take a look at how DetectChanges impacts our example we looked at earlier:

Customer
 customer = (
from
 c in context.Customers
where c.CustomerID == "ALFKI"
select
 c).Single();

ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer);

Console
.WriteLine("Customer object state: {0}", ose.State); // Unchanged

customer.Country = "UK";
Console
.WriteLine("Customer object state: {0}", ose.State); // Still Unchanged

context.DetectChanges();
Console
.WriteLine("Customer object state: {0}", ose.State); // Modified

 

Calling DetectChanges explicitly brings the state manager in sync with the state of your objects.

Because DetectChanges is potentially expensive, it isn’t called explicitly during the other APIs on ObjectContext that rely on the state manager being in sync with the object graph. As a result, you have to call DetectChanges whenever you are performing state dependent operations on the context.

The behavior of the following APIs in Object Servces depend on the current state in the ObjectStateManager and therefore could be affected if knowledge aobut the part of the graph on which they operate is out of sync with the actual state of the graph:

ObjectContext API:

  • AddObject
  • Attach
  • AttachTo
  • DeleteObject
  • Detach
  • GetObjectByKey
  • TryGetObjectByKey
  • ApplyCurrentValues
  • ApplyPropertyChanges
  • ApplyOriginalValues
  • Refresh
  • ExecuteQuery

ObjectStateManager API:

  • ChangeObjectState
  • ChangeRelationshipState
  • GetObjectStateEntry
  • TryGetObjectStateEntry
  • GetObjectStateEntries

ObjectStateEntry API:

  • Any

EntityCollection/EntityReference API:

  • Any

New Overloads for SaveChanges

In Entity Framework 3.5, there were two possibilities when invoking ObjectContext.SaveChanges():

  • SaveChanges() – The parameterless option allowed you to save changes, and also accept changes implicitly
  • SaveChanges(bool acceptChangesDuringSave) – This option allowed you to opt out of the default behavior of “accept changes during save”. You will have to explicitly use AcceptAllChanges to accept changes after the save.

With DetectChanges behavior added to the mix, it was obvious that adding additional overloads would clutter the API. In order to address this problem, the SaveOptions flags enum was introduced:

[
Flags
]
public enum
SaveOptions

{
None = 0,
DetectChangesBeforeSave = 1,
AcceptChangesAfterSave = 2,
}

 

 

 

 

 

SaveChanges now includes an overload that looks like this:

public virtual int SaveChanges(
SaveOptions
 options);

SaveOptions will allow us (the Entity Framework team) to extend the API in a much more manageable way should we need to add additional options in the future.

But this overload is also interesting for another reason: Note that this overload of SaveChanges is virtual. In Entity Framework 4.0, you can override the SaveChanges behavior with your own behavior customization, as a part of the ObjectContext derived implementations that you might choose to write. Whether or not you are using POCO support in EF, this will allow you add customizations in a way that was not possible with Entity Framework 3.5.

That pretty much wraps up most of the things I wanted to highlight about POCO in Entity Framework 4.0. Stay tuned for a write-up on using Patterns with Entity Framework next.

Faisal Mohamood
Program Manager, Entity Framework Team