How to: Execute Business Logic During Scalar Property Changes
The Entity Framework enables you to execute your own custom business logic to perform custom actions when changes are made to generated properties. Entity Data Model Tools generate data classes that represent entities in a conceptual model. Although these generated classes should not be modified directly, for each generated property, the tools also generate a pair of partial methods named OnPropertyChanging and OnPropertyChanged, where Property is the property name. These methods are called by the Entity Framework before and after a property is changed, and you can extend these methods in partial data classes to implement custom code.
Note
If the object-layer code was generated with a text template other than a default text template, the partial methods might not have been generated.
The example in this topic is based on the Adventure Works Sales Model. To run the code in this example, you must have already added the AdventureWorks Sales Model to your project and configured your project to use the Entity Framework. To do this, complete the procedures in How to: Manually Configure an Entity Framework Project and How to: Manually Define the Model and Mapping Files.
To implement custom validation for property changes
In your project, define a custom partial class for each data class to validate.
In this partial class, define one or both of the following methods, where Property is the name of the property to validate:
OnPropertyChanging - include code to execute before the change occurs, such as property validation. The value parameter is the value to which the property is changing. Implement this method to validate a property change before it occurs. To prevent the change from being made, you must throw an exception.
OnPropertyChanged - include code to execute after the change occurs, such as logging the change.
Example
This example checks the value of SalesOrderHeader.Status to ensure that the order can be changed before a change is made to SalesOrderDetail.OrderQty and the pending change is logged to a file. This action is performed in the OnOrderQtyChanging partial method. If the change cannot be made, an exception is raised. Then, after the change is made successfully, the SalesOrderHeader.Status value is reset to 1 and the completed change is logged. These actions are performed in the OnOrderQtyChanged partial method.
Partial Public Class SalesOrderDetail
Inherits EntityObject
Private Sub OnOrderQtyChanging(ByVal value As Short)
' Only handle this change for existing SalesOrderHeader
' objects that are attached to an object context. If the item
' is detached then we cannot access or load the related order.
If EntityState <> EntityState.Detached Then
Try
' Ensure that the referenced SalesOrderHeader is loaded.
If Not Me.SalesOrderHeaderReference.IsLoaded Then
Me.SalesOrderHeaderReference.Load()
End If
' Cancel the change if the order cannot be modified.
If Me.SalesOrderHeader.Status > 3 Then
Throw New InvalidOperationException("The quantity cannot be changed " & _
"or the item cannot be added because the order has either " & _
"already been shipped or has been cancelled.")
End If
' Log the pending order change.
File.AppendAllText(LogFile, "Quantity of item '" & _
Me.SalesOrderDetailID.ToString() & "' in order '" & _
Me.SalesOrderHeader.SalesOrderID.ToString() & _
"' changing from '" + Me.OrderQty.ToString() & _
"' to '" & value.ToString() + "'." & Environment.NewLine & _
"Change made by user: " & Environment.UserName & _
Environment.NewLine)
Catch ex As InvalidOperationException
Throw New InvalidOperationException(("The quantity could not be changed " & " because the order information could not be retrieved. " & "The following error occurred:") + ex.Message)
End Try
End If
End Sub
Private Sub OnOrderQtyChanged()
' Only handle this change for existing SalesOrderHeader
' objects that are attached to an object context.
If EntityState <> EntityState.Detached Then
Try
' Ensure that the SalesOrderDetail is loaded.
If Not SalesOrderHeaderReference.IsLoaded Then
SalesOrderHeaderReference.Load()
End If
' Reset the status for the order related to this item.
Me.SalesOrderHeader.Status = 1
' Log the completed order change.
File.AppendAllText(LogFile, "Quantity of item '" & _
SalesOrderDetailID.ToString() + "' in order '" & _
SalesOrderHeader.SalesOrderID.ToString() & _
"' successfully changed to '" + OrderQty.ToString() & _
"'." + Environment.NewLine & _
"Change made by user: " + Environment.UserName & _
Environment.NewLine)
Catch ex As InvalidOperationException
Throw New InvalidOperationException(("An error occurred; " & _
"the data could be in an inconsistent state. ") & _
Environment.NewLine + ex.Message)
End Try
End If
End Sub
End Class
public partial class SalesOrderDetail : EntityObject
{
partial void OnOrderQtyChanging(short value)
{
// Only handle this change for existing SalesOrderHeader
// objects that are attached to an object context. If the item
// is detached then we cannot access or load the related order.
if (EntityState != EntityState.Detached)
{
try
{
// Ensure that the referenced SalesOrderHeader is loaded.
if (!this.SalesOrderHeaderReference.IsLoaded)
{
this.SalesOrderHeaderReference.Load();
}
// Cancel the change if the order cannot be modified.
if (this.SalesOrderHeader.Status > 3)
{
throw new InvalidOperationException("The quantity cannot be changed "
+ "or the item cannot be added because the order has either "
+ "already been shipped or has been cancelled.");
}
// Log the pending order change.
File.AppendAllText(LogFile, "Quantity of item '"
+ this.SalesOrderDetailID.ToString() + "' in order '"
+ this.SalesOrderHeader.SalesOrderID.ToString()
+ "' changing from '" + this.OrderQty.ToString()
+ "' to '" + value.ToString() + "'." + Environment.NewLine
+ "Change made by user: " + Environment.UserName
+ Environment.NewLine);
}
catch (InvalidOperationException ex)
{
throw new InvalidOperationException("The quantity could not be changed "
+ " because the order information could not be retrieved. "
+ "The following error occurred:" + ex.Message);
}
}
}
partial void OnOrderQtyChanged()
{
// Only handle this change for existing SalesOrderHeader
// objects that are attached to an object context.
if (EntityState != EntityState.Detached)
{
try
{
// Ensure that the SalesOrderDetail is loaded.
if (!SalesOrderHeaderReference.IsLoaded)
{
SalesOrderHeaderReference.Load();
}
// Reset the status for the order related to this item.
this.SalesOrderHeader.Status = 1;
// Log the completed order change.
File.AppendAllText(LogFile, "Quantity of item '"
+ SalesOrderDetailID.ToString() + "' in order '"
+ SalesOrderHeader.SalesOrderID.ToString()
+ "' successfully changed to '" + OrderQty.ToString()
+ "'." + Environment.NewLine
+ "Change made by user: " + Environment.UserName
+ Environment.NewLine);
}
catch (InvalidOperationException ex)
{
throw new InvalidOperationException("An error occurred; "
+ "the data could be in an inconsistent state. "
+ Environment.NewLine + ex.Message);
}
}
}
}
See Also
Tasks
How to: Execute Business Logic When the Object State Changes
How to: Execute Business Logic During Association Changes
How to: Execute Business Logic When Saving Changes