Reporting Changes in Custom Data Classes (Entity Framework)

Object Services provides the IEntityChangeTracker interface, which is used by data classes to report changes made to data properties. EntityObject implements the SetChangeTracker method of IEntityWithChangeTracker. This method is called by Object Services to specify the instance of IEntityChangeTracker that an object uses to report changes. The change reporting model supported by IEntityChangeTracker involves reporting a pending change to a property, setting the property, and then reporting that the change is complete.

To report changes in custom data classes that do not inherit from EntityObject, these classes must implement IEntityWithChangeTracker. For more information, see Implementing Custom Data Class Interfaces (Entity Framework).

The following considerations apply when reporting changes:

  • You should report a property as changing before you set the property value, and then report the property as changed after you set the property value.

  • You must report changes to the EntityKey property. After the EntityKey property is set, application code that reports this property as changing causes an InvalidOperationException. However, in some cases, Object Services must be able to change the EntityKey property after it is set. By reporting changes to this property, Object Services is able to determine when to set this property.

  • You can report a property as changing without subsequently reporting it as changed. However, in this case the change will not be tracked.

  • An InvalidOperationException is raised when you report a property as changed before you report the same property as changing, or when an invalid property name is passed. This can occur when multiple properties are reported as changing without subsequently being reported as changed. This is because only the last property is recognized when the changed property is validated against the property that was first reported as changing.

Reporting Property Changes When Inheriting from EntityObject and ComplexObject

When a custom data class inherits from EntityObject or ComplexObject, you must call the ReportPropertyChanging and ReportPropertyChanged methods to report property changes.

To report changes to a property when inheriting from EntityObject

  1. Call the System.Data.Objects.DataClasses.EntityObject.ReportPropertyChanging(System.String) method on EntityObject, passing the name of the changing property.

    This caches the current value of the property, which is used as the original value for the property.

  2. Set the property as appropriate.

  3. Call the System.Data.Objects.DataClasses.EntityObject.ReportPropertyChanged(System.String) method on EntityObject, passing the name of the changed property.

    This notifies Object Services that the pending change on the property is now complete. Object Services then marks the property as modified.

To report changes to a property when inheriting from ComplexObject

  1. Call the System.Data.Objects.DataClasses.ComplexObject.ReportPropertyChanging(System.String) method on ComplexObject, passing the name of the changing property. This caches the current value of the property, which is used as the original value for the property.

  2. Set the property as appropriate.

  3. Call the System.Data.Objects.DataClasses.ComplexObject.ReportPropertyChanged(System.String) method on ComplexObject, passing the name of the changed property. This notifies Object Services that the pending change on the property is now complete. Object Services then marks the property as modified.

The following example shows how to report changes when setting the scalar Status property on the Order object:

<EdmScalarPropertyAttribute(IsNullable:=False)> _
Public Property Status() As Byte
    Get
        Return _status
    End Get
    Set(ByVal value As Byte)
        If _status <> value Then
            ReportPropertyChanging("Status")
            _status = value
            ReportPropertyChanged("Status")
        End If
    End Set
End Property
[EdmScalarPropertyAttribute(IsNullable = false)]
public byte Status
{
    get 
    {
        return _status;
    }
    set
    {
        if (_status != value)
        {
            ReportPropertyChanging("Status");
            _status = value;
            ReportPropertyChanged("Status");
        }
    }
}

Reporting Property Changes When Implementing IEntityWithChangeTracker

When a custom data class implements IEntityWithChangeTracker, you must call change reporting methods on IEntityChangeTracker before and after the property is changed to correctly report the change.

To report changes to a property when implementing IEntityWithChangeTracker

  1. Call the EntityMemberChanging method, passing the name of the changing property. This caches the current value of the property, which is used as the original value for the property.

  2. Set the property as appropriate.

  3. Call the EntityMemberChanged method, passing the name of the changed property.

  4. This notifies Object Services that the pending change on the property is now complete. Object Services then marks the property as modified.

To report changes to a property when implementing IEntityWithChangeTracker on a complex type

  1. Call the EntityComplexMemberChanging method, passing the name of the top-level entity property that has changed, the complex object instance that contains the property that changed, and the name of the property that changed on complex type. This caches the current value of the property, which is used as the original value for the property.

  2. Set the property as appropriate.

  3. Call the EntityComplexMemberChanged method, passing the name of the top-level entity property that has changed, the complex object instance that contains the property that changed, and the name of the property that changed on complex type. This notifies Object Services that the pending change on the property is now complete. Object Services then marks the property as modified.

In some situations, an instance of IEntityChangeTracker might not be available. This can happen when an object is detached from the object context or when a query is executed using the NoTracking option. You must check for an instance of IEntityChangeTracker before you call the change reporting methods.

The following example shows an abstract class ComplexTypeChangeTracker that is the base class for all derived complex types. This class implements change tracking for complex types.

' Base class for complex types that implements change tracking.
Public MustInherit Class ComplexTypeChangeTracker
    Protected _complexChangeTracker As IEntityChangeTracker = Nothing
    Private _rootComplexPropertyName As String

    ' Gets an IEntityChangeTracker to call for properties change. 
    ' You must do this in order to track changes.
    Public Overridable Sub SetComplexChangeTracker( _
        ByVal rootComplexPropertyName As String, _
        ByVal complexChangeTracker As IEntityChangeTracker)
        _rootComplexPropertyName = rootComplexPropertyName
        _complexChangeTracker = complexChangeTracker
    End Sub

    ' Protected method that is called before the change for change tracking 
    ' each of the scalar properties in the complex type.
    Protected Sub ReportMemberChanging(ByVal scalarPropertyName As String)
        If Not _complexChangeTracker Is Nothing Then
            _complexChangeTracker.EntityComplexMemberChanging( _
                _rootComplexPropertyName, Me, scalarPropertyName)
        End If
    End Sub

    ' Protected method that is called after the change for change tracking 
    ' each of the scalar properties in the complex type.
    Protected Sub ReportMemberChanged(ByVal scalarPropertyName As String)
        If Not _complexChangeTracker Is Nothing Then
            _complexChangeTracker.EntityComplexMemberChanged( _
                _rootComplexPropertyName, Me, scalarPropertyName)
        End If
    End Sub
End Class
// Base class for complex types that implements change tracking.
public abstract class ComplexTypeChangeTracker
{
    protected IEntityChangeTracker _complexChangeTracker = null;
    private string _rootComplexPropertyName;

    // Gets an IEntityChangeTracker to call for properties change. 
    // You must do this in order to track changes.
    virtual public void SetComplexChangeTracker(string rootComplexPropertyName, IEntityChangeTracker complexChangeTracker)
    {
        _rootComplexPropertyName = rootComplexPropertyName;
        _complexChangeTracker = complexChangeTracker;
    }

    // Protected method that is called before the change for change tracking 
    // each of the scalar properties in the complex type.
    protected void ReportMemberChanging(string scalarPropertyName)
    {
        if (null != _complexChangeTracker)
        {
            _complexChangeTracker.EntityComplexMemberChanging(_rootComplexPropertyName,
                                                       this, scalarPropertyName);
        }
    }

    // Protected method that is called after the change for change tracking 
    // each of the scalar properties in the complex type.
    protected void ReportMemberChanged(string scalarPropertyName)
    {
        if (null != _complexChangeTracker)
        {
            _complexChangeTracker.EntityComplexMemberChanged(_rootComplexPropertyName,
                                                      this, scalarPropertyName);
        }
    }
}

The following example shows how to use the methods in the previous example to report changes when setting the scalar Status property on the Order object:

<EdmScalarPropertyAttribute()> _
Public Property Comment() As String
    Get
        Return _comment
    End Get
    Set(ByVal value As String)
        ' Validate the value before setting it.
        If (value <> Nothing) AndAlso value.Length > 128 Then
            Throw New ApplicationException(String.Format( _
                      Errors.propertyNotValidString, _
                      "Comment", "128"))
        End If
        If _comment <> value Then
            ' Report the change if the change tracker exists.
            If Not _complexChangeTracker Is Nothing Then
                ReportMemberChanging("Comment")
                _comment = value
                ReportMemberChanged("Comment")
            Else
                _comment = value
            End If
        End If
    End Set
End Property
[EdmScalarPropertyAttribute()]
public string Comment
{
    get
    {
        return _comment;
    }
    set
    {
        // Validate the value before setting it.
        if ((value != null) && value.Length > 128)
        {
            throw new ApplicationException(string.Format(
                      Errors.propertyNotValidString,
                      new string[3] { value, "Comment", "128" }));
        }
        if (_comment != value)
        {
            // Report the change if the change tracker exists.
            if (_complexChangeTracker != null)
            {
                ReportMemberChanging("Comment");
                _comment = value;
                ReportMemberChanged("Comment");
            }
            else
            {
                _comment = value;
            }
        }
    }
}

See Also

Concepts

Customizing Objects (Entity Framework)