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();
#>
    }
  }
}

1 comment:

  1. Oh man you just saved my week! Thank you! I have legacy template files from years ago and this is exactly the problem i was having with our audit trail.

    -Sam

    ReplyDelete