Published
Monday, August 27, 2007 4:36 PM
by
mtaulty
When you look at an ObjectContext (or something derived from it) in the Entity Framework you'll spot that it has a property on it called ObjectStateManager which is of type ObjectStateManager.
This type is vital to making object services work beyond just querying the database. With the ObjectContext, you can grab objects from queries and you can then modify them, delete them and, additionally, you can add objects to the context.
Nothing happens in the store until you call ObjectContext.SaveChanges and, at that point, something somewhere has to figure it all out and generate the right commands to interact with the store.
That something is the ObjectStateManager (or, at least, it looks like it plays a big part in that process).
Because you can have more than one ObjectContext instance so you can also have more than one ObjectStateManager and, hence, it's not "global" within your application. The context scopes the state manager and its up to you how you deal with multiple contexts.
These are some of the things that I think the ObjectStateManager does for me, having played with it a little;
- When I run a query, the results are cached by the ObjectStateManager such that if do;
- Query record id 1 from the store
- Modify some value of that record 1 in memory
- Requery some resultset from the store that includes record id 1
- Access the modified value.
- Then at (4) above I'd expect to see my modified value rather than the store's value. That is, I expect the ObjectStateManager to notice that it already has record id 1 in its "cache" and to discard the incoming record id 1 from the store. Note though that this is actually controllable with the Entity Framework so it's not quite as simple as I'm painting it here - there's an enum called MergeOption which I'll drop into another post.
- When I add an object to the object context, the ObjectStateManager will be aware of this, will generate a temporary key value for that object and will maintain a set of "added" objects.
- Same as (2) but for delete.
- When I modify an object, the object will notify the ObjectStateManager that a property has changed and the ObjectStateManager will maintain a set of "modified" objects.
- At any time, I can walk up to the ObjectStateManager and ask it for all objects that are Unchanged, Added, Deleted, Modified and also ask for original values versus current values on those objects.
- I can also walk up to the ObjectStateManager, grab modified entries from it and ask them to behave as though they were not modified.
Quite a lot of this is familiar from working with DataTables in that DataTables have that capability to store various sets of values (Original, Modified, Added, Deleted, etc) and to report back to you rows that exist in the various states.
The ObjectContextManager isn't too complicated an object to deal with from a public interface point of view but it dishes out instances of ObjectStateEntry which have quite a lot of functionality. Here's a picture;
In playing with my own code, I've been using this function in order to dump out an ObjectStateManager to my console window - it probably needs extending to deal with relationships and also to deal with more complex result-sets but it's a start;
static void DumpObjectStateManager(ObjectStateManager manager)
{
EntityState[] values = (EntityState[])Enum.GetValues(typeof(EntityState));
Console.WriteLine("Dumping Object State Manager");
foreach (EntityState state in values)
{
Console.WriteLine("\tDumping objects in state [{0}]", state);
try
{
foreach (ObjectStateEntry entry in manager.GetObjectStateEntries(state))
{
if (!entry.IsRelationship)
{
Console.WriteLine("\t\tEntity from entity set [{0}], key [{1}]",
entry.EntitySet.Name, KeyValuesToString(entry.EntityKey));
Console.WriteLine("\t\tCurrent Values");
DumpDataRecord(entry.CurrentValues);
Console.WriteLine("\t\tModified Properties [{0}]",
ModifiedPropertiesToString(entry.GetModifiedProperties()));
Console.WriteLine("\t\tOriginal Values");
DumpDataRecord(entry.OriginalValues);
}
}
}
catch // gulp
{
}
}
}
static void DumpDataRecord(DbDataRecord record)
{
for (int i = 0; i < record.FieldCount; i++)
{
Console.WriteLine("\t\t\t<Field [{0}], Value [{1}]>", record.GetName(i),
record[i]);
}
}
static string ModifiedPropertiesToString(IEnumerable<string> strings)
{
List<string> list = new List<string>(strings);
return (string.Concat(list.ToArray()));
}
static string KeyValuesToString(EntityKey key)
{
StringBuilder builder = new StringBuilder();
if (key.IsTemporary)
{
builder.Append("<Key=Temporary>");
}
else
{
foreach (KeyValuePair<string, object> pair in key.EntityKeyValues)
{
builder.AppendFormat("<Key={0},Value={1}>", pair.Key, pair.Value);
}
}
return (builder.ToString());
}
}
I'm not so sure on the IsRelationship test at the moment. This would be much better packaged as a Debugger Visualizer for the ObjectStateManager :-)