Wednesday, February 24, 2010

How to call SaveChanges for a single entity in Entity Framework 4.0

Last week I installed VS2010 RC and I still wonder why I can't save a single entity in Entity Framework 4.0. I use POCO entities with Proxies, so the change tracking is done automatically. By using automatic change tracking, saving changes means 'commit all' or 'commit nothing'. Saving a single entity is not possible out of the box.
I tried to dive into the Entity Framework source code to figure out how Microsoft does the trick of detecting changes, but source stepping is not possible for .NET 4 assemblies in Visual Studio 2010 RC.

So I decided to install .NET Reflector 6.0 Pro. With this tool you can decompile the .NET 4 assemblies and start stepping through the code.

With some voodoo reflection tricks I managed to save a single entity to the database, without committing the changes that were made on the other dirty entities in the ObjectContext. Most of the types and methods you need are internal in System.Data.Entity, so the only way to do the trick is with help of reflection.

To commit a single entity, we have to disable the DetectChangesBeforeSave option when we call ObjectContext.SaveChanges(). By making the ObjectStateManager detect only the changes of the single entity, SaveChanges will only commit the changes that are detected, so only the single entity will be persisted to the database.



private void button2_Click(object sender, EventArgs e)
{
using (DBTestOrdersContext ctx = new DBTestOrdersContext())
{
ctx.ContextOptions.LazyLoadingEnabled = true;
ctx.ContextOptions.ProxyCreationEnabled = true;

Order order = ctx.GetObjectById<Order>(1);
order.OrderNumber += 1;

Order order2 = ctx.GetObjectById<Order>(2);
order2.OrderNumber += 1;

this.DetectChanges(ctx, order, false);

ctx.SaveChanges(SaveOptions.None);
}
}

private void DetectChanges(ObjectContext ctx, object entity)
{
ObjectStateEntry entry;
if (ctx.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
{
// We have to call the next PRIVATE method on ObjectStateManager:
// DetectChangesInScalarAndComplexProperties(IList<EntityEntry> entries)

// Because EntityEntry is an internal type, we have
// to instantiate List<EntityEntry> with reflection!
// Covariance of IList<ObjectStateEntry> to IList<EntityEntry> does
// not work at runtime !!!
System.Type listType = typeof(System.Collections.Generic.List<>).MakeGenericType(entry.GetType());
var list = Activator.CreateInstance(listType);
MethodInfo mi = list.GetType().GetMethod("Add", BindingFlags.Public BindingFlags.Instance);
mi.Invoke(list, new object[] { entry });

// Invoke DetectChangesInScalarAndComplexProperties
mi = ctx.ObjectStateManager.GetType().GetMethod("DetectChangesInScalarAndComplexProperties", BindingFlags.NonPublic BindingFlags.Instance);
mi.Invoke(ctx.ObjectStateManager, new object[] { list });
}
}


In the first piece of code I get 2 Order entities from the database. On both entities the OrderNumber property is changed. Calling DetectChanges only on the first order ensures that only the changes of the first order are persisted to the database.

Best regards,
Harm Neervens

How to find a dirty property in Entity Framework 4.0

In the new Entity Framwork release, Microsoft finally added support for POCO or Persistance Ignorent (PI) objects. It's now possible to generate Proxies for my POCO's. This has the advantage that the change tracking is automatically done for me.

In my daily work, I often feel the need to find out if a property is dirty, before I let the ObjectContext save my changes. For example, when a customers address property is dirty, I want to create an AddressChange record. In EF4, there's a method ObjectStateEntry.GetModifiedProperties(). This method returns an IEnumerable of strings that contain the propertynames which are changed.

Assume we have the following code:

using (DBTestOrdersContext ctx = new DBTestOrdersContext())
{
ctx.ContextOptions.LazyLoadingEnabled = true;
ctx.ContextOptions.ProxyCreationEnabled = true;

Order order = ctx.GetObjectById<Order>(1);
order.OrderNumber += 1;
}

I think we can assume that the OrderNumber property is dirty now. So let's write a method that finds a dirty property:


private bool IsDirtyPropertyNotWorking(ObjectContext ctx, object entity, string propertyName)
{
ObjectStateEntry entry;
if (ctx.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
{
IEnumerable changedNames = entry.GetModifiedProperties();

return changedNames.Any(x => x == propertyName);
}
return false;
}


I expected this method to return true for my OrderNumber property, but unfortunately, nothing is what it seems in EF4. It seems that we first have to call ObjectContext.DetectChanges() before it shows up in the ObjectStateEntry.GetModifiedProperties() list.


private bool IsDirtyPropertyWorking(ObjectContext ctx, object entity, string propertyName)
{
ObjectStateEntry entry;
if (ctx.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
{
ctx.DetectChanges();

IEnumerable<string> changedNames = entry.GetModifiedProperties();

return changedNames.Any(x => x == propertyName);
}
return false;
}


Microsoft admits that ObjectContext.DetectChanges can be a very expensive operation, because the complete ObjectContext has to be 'scanned' for modifications. Another disadvantage is that changes on all other entities in my Context are also detected, and there are scenarios that I don't want that yet. So I decided to write an IsDirtyProperty method that does not have to call ObjectContext.DetectChanges():


private bool IsDirtyProperty(ObjectContext ctx, object entity, string propertyName)
{
ObjectStateEntry entry;
if (ctx.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
{
int propIndex = this.GetPropertyIndex(entry, propertyName);

if (propIndex != -1)
{
var oldValue = entry.OriginalValues[propIndex];
var newValue = entry.CurrentValues[propIndex];

return !Equals(oldValue, newValue);
}
else
{
throw new ArgumentException(String.Format("Cannot find original value
for property '{0}' in entity entry '{1}'",
propertyName,
entry.EntitySet.ElementType.FullName));
}
}

return false;
}


private int GetPropertyIndex(ObjectStateEntry entry, string propertyName)
{
OriginalValueRecord record = entry.GetUpdatableOriginalValues();

for (int i = 0; i <> record.FieldCount; i++)
{
FieldMetadata metaData = record.DataRecordInfo.FieldMetadata[i];
if (metaData.FieldType.Name == propertyName)
{
return metaData.Ordinal;
}
}

return -1;
}


Of course you can decide to make these Extension Methods on ObjectContext or ObjectStateEntry

Best regards
Harm Neervens