Blog

ObjectContext's SavingChanges Event

By Martin Schaeferle | September 28, 2012

ObjectContext's SavingChanges event lets you validate or change data before Entity Framework sends it to the database. Entity Framework raises this event immediately before it creates the SQL INSERT, UPDATE, and DELETE statements that will persist the changes to the database. One of the issues with using Entity Framework with SQL Server is that the range of DateTime values in .NET is different than those of the DateTime type in SQL Server. So in the case where you have a non-nullable DateTime field in a table, you have to assign a value to the corresponding property in an entity.

This is exactly the case with the Modified property, associated with the ModifiedDate field in all of the tables of the AdventureWorksLT database. If you create a new instance of any entity and don't explicitly set a valid value for the Modified property, .NET sets its minimum value, which doesn't work for a SQL Server DateTime type. (It would, however, work with SQL Server 2008 and later's DateTime2 type, but AdventureWorksLT doesn't use this type for its date/time fields.) So you either need to explicitly set the Modified property to DateTime.Now when you create or update any entity, you'll get a rather cryptic exception about some unnamed DateTime type being out of range.

NOTE: It is interesting that while the ModifiedDate fields in the database have a default value of GETDATE()-the T-SQL equivalent of DateTime. Now-there is nothing that updates the value when a record is changed. So you'll need to supply a value for the Modified property when creating a new instance of an entity to avoid the datetime overflow problem, and when modifying an entity so that the field reflects the last time any data in the record changed.

This is a perfect use of the SavingChanges event. The one trick is that the view entities-CategoryList, ProductAndDescription, and ProductModelCatalogDescription-don't have a Modified property. So the code has to be selective about which entities it applies to. But SavingChanges makes this easy through how it requires you to get access to the set of inserted, updated, or deleted entities. You have to access the entities with the ObjectContext's GetObjectStateEntries method.

The method takes one or more EntityState enumerations OR'd together, and returns a collection of entities with the selected state. You can select for Added, Deleted, Modified, and Unchanged states, but in this case you're only interested in the Added and Modified states. There is no reason to update Modified for an entity you are deleting or that remains unchanged.

modify entities method

 

TIP: There is one other EntityState enumeration value, Detached. This state means that ObjectContext isn't managing state for that entity. This value is not relevant in the SavingChanges event because there won't be any work to do for detached entities.

The AWLT project in AWLT.sln has a Context.cs code file with a partial AWLTEntities class to customize the context object. The class implements the following AWLTEntities_SavingChanges method. The code uses the ObjectStateManager property of ObjectContext to get a reference to the ObjectStateManager for the context.

It then uses the GetObjectStateEntries method, with the Added and Modified EntityState enumeration values, to populate the entities object with a collection of modified entities. Then the code loops through the collection, and uses reflection to update the value of the Modified property of each entity. The update code is wrapped in a try block and a do-nothing catch just in case an entity slips through that doesn't have a Modified property.

public void AWLTEntities_SavingChanges (
object sender, EventArgs e)
{
 ObjectStateManager osm = ObjectStateManager;
 IEnumerable<ObjectStateEntry> entries =
 osm.GetObjectStateEntries(
 EntityState.Added | EntityState.Modified); 
 foreach (var entry in entries)
 {
 // Use reflection to set the property
 try
 {
 Type t = entry.Entity.GetType();
 t.GetProperty("Modified")
 .SetValue(entry.Entity, DateTime.Now, null);
  }
 catch
 {}
 }
}

You also have to wire up the SavingChanges event handler, and the OnContextCreated partial method is the perfect place to do that. The following code in the partial AWLTEntities class takes care of this task in the code.

partial void OnContextCreated()
{
 SavingChanges += AWLTEntities_SavingChanges;
}

The ModifyEntries method in Program.cs puts the SavingChanges event handler to use. It executes a query to retrieve all customers named Ann, and updates each one with a Jr. suffix. Then it saves the changes to the database, and turns around and resets the suffixes to null (which is what they all were originally). It then refreshes the collection of customers, just to make sure nothing was cached in memory, and writes out each customer, including the new value of the Modified property. Figure 1 shows the result of running the application.

private static void ModifyEntities()
{
 using (AWLTEntities context = new AWLTEntities())
 {
 var customers = context.Customers
 .Where(c => c.FirstName == "Ann");
 foreach (Customer customer in customers)
 {
 customer.Suffix = "Jr.";
 }
 context.SaveChanges();
 foreach (Customer customer in customers)
 {
 customer.Suffix = null;
 }
 context.SaveChanges();
 customers = null;
  
 customers = context.Customers
 .Where(c => c.FirstName == "Ann");
 foreach (Customer customer in customers)
 {
 Console.WriteLine("{0}: {1} {2} - Modified {3}",
 customer.CompanyName,
 customer.FirstName,
 customer.LastName,
 customer.Modified);
 }
 }
} 

Figure 1. The result of running the ModifyEntities method.

 



Martin Schaeferle

Martin Schaeferle has taught IT professionals nationwide to develop applications using Visual Basic, Microsoft SQL Server, ASP, and XML. He has been a featured speaker at Microsoft Tech-Ed and the Microsoft NCD Channel Summit, and he specializes in developing Visual Basic database applications, COM-based components, and ASP-based Web sites. In addition to writing and presenting technical training content, Martin is also LearnNowOnline's vice president of technology.


This blog entry was originally posted September 28, 2012 by Martin Schaeferle