Tuesday, February 14, 2012

How to get changed properties for an entity in Entity Framework

Sometimes it can be useful to find exactly which entities that have been changed before saving to the database. The most basic for check for changes it to look at the EntityState property, but this does not indicate what properties that have been changed.

To get these changes we have to use the ObjectStateManager on ObjectContext. The ObjectStateManager handles the state of all entities, and can supplied a lot of other different state information about the entities.

First we always need to get the ObjectStateEntry for the entity, this a done by calling ObjectStateManager.GetObjectStateEntry.

TestEntities entities = new TestEntities();

// Query for entity and make some changes

// To get the object state entry
var stateEntry = entities.ObjectStateManager.GetObjectStateEntry(changedEntity);

To check which properties that have changed we just do

IEnumerable modifiedProperties = stateEntry.GetModifiedProperties();

We can also get all the original and current values for the entity using

CurrentValueRecord currentValues = stateEntry.CurrentValues;
DbDataRecord originalValues = stateEntry.OriginalValues;

These two objects can are accessed by column ordinal, which you may or may not know before, but luckily there is a method to get the ordinal from a column name. To get the current and/or original value for an entity, do the following

var currentValue = currentValues.GetValue(currentValues.GetOrdinal(propertyName));
var originalValue = originalValues.GetValue(originalValues.GetOrdinal(propertyName));

Not all properties are correctly marked as changed

When using this method to generate a custom audit trail, I discovered a weird behavior in Entity Framework. Everything but the primary key, are marked as modified even though it only was "changed" to the original value. And this does not work in an audit trail.

There are two ways to solve this either manual check if a property actually has been changed, or change to generate classes for the entities. The last solution are described in my previous post, Entity Framework should only mark really updated properties as modified

The other solution is to check if the current value equals the original value using the methods described above. An example could be

var stateEntry = objectStateManager.GetObjectStateEntry(entity);
var currentValues = stateEntry.CurrentValues;
var originalValues = stateEntry.OriginalValues;
var modifiedProperties = stateEntry.GetModifiedProperties();
foreach (string modifiedProperty in modifiedProperties)
{
  var currentValue = currentValues.GetValue(currentValues.GetOrdinal(modifiedProperty));
  var originalValue = originalValues.GetValue(originalValues.GetOrdinal(modifiedProperty));
  if (originalValue.Equals(currentValue))
  {
    // Value not changed
  }
  else
  {
    // Value changed
  }
}

Monday, February 13, 2012

Entity Framework should only mark really updated properties as modified

Today I discovered a weird behavior in Entity Framerwork. When I set the value of an entity to the same value as in the database, it would still say the value had been changed. This is the case of all values except the primary key value.
This means that we would unnecessary update the value on SaveChanges(). How could we change this behavior? I went to Google for an answer and found one on StackOverflow, but unfortunately it can't find it again.

But basically the fix is to edit the T4 template used to generate to entities. The easiest way to do this is to change the Code Generation item by right clicking on the EF design surface, click "Add Code Generation Item..." and choose "ADO.NET EntityObject Generator". This adds a T4 template to the project, which we can edit to check if the value are the same as the original.
Somewhere in the template in my case at line 552, in the .tt file, the following part needs to be changed.
/// <summary>
/// <#=SummaryComment(primitiveProperty)#>
/// </summary><#=LongDescriptionCommentElement(primitiveProperty, 1)#>
[EdmScalarPropertyAttribute(EntityKeyProperty=<#=code.CreateLiteral(ef.IsKey(primitiveProperty))#>, IsNullable=<#=code.CreateLiteral(ef.IsNullable(primitiveProperty))#>)]
[DataMemberAttribute()]
<#=code.SpaceAfter(NewModifier(primitiveProperty))#><#=Accessibility.ForProperty(primitiveProperty)#> <#=code.Escape(primitiveProperty.TypeUsage)#> <#=code.Escape(primitiveProperty)#>
{
  <#=code.SpaceAfter(Accessibility.ForGetter(primitiveProperty))#>get
  {
            if (ef.ClrType(primitiveProperty.TypeUsage) == typeof(byte[]))
      {

    return StructuralObject.GetValidValue(<#=code.FieldName(primitiveProperty)#>);

      }
      else
      {
#>
    return <#=code.FieldName(primitiveProperty)#>;
<#+
      }
#>
  }
  <#=code.SpaceAfter(Accessibility.ForSetter((primitiveProperty)))#>set
  {
<#+
  if (ef.IsKey(primitiveProperty))
    {
      if (ef.ClrType(primitiveProperty.TypeUsage) == typeof(byte[]))
      {
#>
    if (!StructuralObject.BinaryEquals(<#=code.FieldName(primitiveProperty)#>, value))
<#+
      }
      else
      {
#>
    if (<#=code.FieldName(primitiveProperty)#> != value)
<#+
      }
#>
    {
<#+
  PushIndent(CodeRegion.GetIndent(1));
    }
#>
    <#=ChangingMethodName(primitiveProperty)#>(value);
    ReportPropertyChanging("<#=primitiveProperty.Name#>");
    <#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
    ReportPropertyChanged("<#=primitiveProperty.Name#>");
    <#=ChangedMethodName(primitiveProperty)#>();
<#+
  if (ef.IsKey(primitiveProperty))
    {
  PopIndent();
#>
    }
<#+
    }
#>
  }
}


The fix lies in removing the
if (ef.IsKey(primitiveProperty))
statement to always check if the value are different. The solution is to replaces to code block above with this code block:

/// <summary>
/// <#=SummaryComment(primitiveProperty)#>
/// </summary><#=LongDescriptionCommentElement(primitiveProperty, 1)#>
[EdmScalarPropertyAttribute(EntityKeyProperty=<#=code.CreateLiteral(ef.IsKey(primitiveProperty))#>, IsNullable=<#=code.CreateLiteral(ef.IsNullable(primitiveProperty))#>)]
[DataMemberAttribute()]
<#=code.SpaceAfter(NewModifier(primitiveProperty))#><#=Accessibility.ForProperty(primitiveProperty)#> <#=code.Escape(primitiveProperty.TypeUsage)#> <#=code.Escape(primitiveProperty)#>
{
  <#=code.SpaceAfter(Accessibility.ForGetter(primitiveProperty))#>get
  {
<#+             if (ef.ClrType(primitiveProperty.TypeUsage) == typeof(byte[]))
      {
#>
    return StructuralObject.GetValidValue(<#=code.FieldName(primitiveProperty)#>);
<#+
      }
      else
      {
#>
    return <#=code.FieldName(primitiveProperty)#>;
<#+
      }
#>
  }
  <#=code.SpaceAfter(Accessibility.ForSetter((primitiveProperty)))#>set
  {
<#+
      if (ef.ClrType(primitiveProperty.TypeUsage) == typeof(byte[]))
      {
#>
    if (!StructuralObject.BinaryEquals(<#=code.FieldName(primitiveProperty)#>, value))
<#+
      }
      else
      {
#>
    if (<#=code.FieldName(primitiveProperty)#> != value)
    {
<#+
  PushIndent(CodeRegion.GetIndent(1));
    }
#>
    <#=ChangingMethodName(primitiveProperty)#>(value);
    ReportPropertyChanging("<#=primitiveProperty.Name#>");
    <#=code.FieldName(primitiveProperty)#> = StructuralObject.SetValidValue(value<#=OptionalNullableParameterForSetValidValue(primitiveProperty, code)#>);
    ReportPropertyChanged("<#=primitiveProperty.Name#>");
    <#=ChangedMethodName(primitiveProperty)#>();
<#+
  PopIndent();
#>
    }
  }
}