Chapter 18. Handling Entity Framework Exceptions

Things can go awry in many ways when you’re querying or updating entities. The Entity Data Model (EDM), for example, could have a problem that the validator fails to detect but that makes it difficult for a query to be compiled. You might attempt to save entities to the database that are missing related data—a reservation without a trip, perhaps, or a reservation without a customer. The database might have a constraint that is not reflected in the model; if a user saves data that breaks the rule of the constraint, the database will throw an error. Or someone may have modified or even deleted a record while another user was editing it.

The Entity Framework includes a specialized set of exceptions for capturing problems like these that arise during query and command execution.

In your application, you should embed each query execution or call you make to SaveChanges in some sort of mechanism for catching these exceptions, most likely a Try/Catch block. When one of these exceptions is raised, it is up to your code to handle it.

In this chapter, we’ll look at exception handling that is unique to the Entity Framework. Some exceptions are Entity Framework-specific exceptions, and others are .NET exceptions caused by faulty operations in the Entity Framework. You’ll also spend a good deal of time focusing on concurrency exceptions, which are raised by a database conflict is caused when multiple users edit the same data concurrently.

Note

You should strongly consider exception handling for any application you write. If this is a new topic for you, plenty of resources are available on the Web, in the MSDN documentation, and in a variety of books to teach you accepted patterns and practices for implementing exception handling in .NET.

Preparing for Exceptions in Entity Framework Code

In Object Services, you can get exceptions from the moment you try to instantiate an ObjectContext to the time you call SaveChanges. Use Try/Catch blocks around this functionality to capture exceptions. For instance, you can dispose the context in the Finally clause in case it was instantiated before the error occurred, as shown in Example 18-1.

Example 18-1. Catching an exception and disposing the ObjectContext in Finally

VB
Dim context As BAEntities
Try
  context = New BAEntities
  Dim res = context.Reservations.First
  Return res
Catch ex As Exception
   Throw   'Throw or handle exception
Finally
  context = Nothing
End Try
C#
BAEntities context = null;
try
{
  context = new BAEntities();
  var res = context.Reservations.First();
  return res;
}
catch (Exception ex)
{
  throw;  //throw or handle exception}
finally
{
  context = null;
}

When you employ the using block in the context’s instantiation, as shown in Example 18-2, the context and any resources that it controls will be disposed at the end of the block.

Example 18-2. Catching an exception when ObjectContext is automatically disposed

VB
Try
  Using context As New BAEntities
    Dim res = context.Reservations.First
  End Using
Catch ex As Exception
   Throw   'Throw or handle exception
End Try
C#
try
{
  using (BAEntities context = new BAEntities())
  {
    var res = context.Reservations.First;
  }
}
catch (Exception ex)
{
   throw; //Throw or handle exception
}

Exceptions can occur when you’re creating connections and executing commands with EntityClient. It’s equally important to capture those exceptions and be sure the connections and DataReaders are properly disposed. Example 18-3 shows the code for catching an exception when using EntityClient.

Example 18-3. Catching an exception when using EntityClient

VB
Dim esql = "SELECT VALUE r FROM BAEntities.Reservations AS r"
Try
  Using econn As New EntityConnection("Name = BAEntities")
    Dim eComm = econn.CreateCommand
    Dim eReader As EntityClient.EntityDataReader
    With eComm
      .CommandText = esql
      econn.Open()
      eReader = .ExecuteReader(CommandBehavior.SequentialAccess)
      econn.Close()
      Do While eReader.Read
        'process results
      Loop
    End With
  End Using
Catch ex As Exception
   Throw   'Throw or handle exception
End Try
C#
var esql = "SELECT VALUE r FROM BAEntities.Reservations AS r";
try
{
  using (EntityConnection econn =  new EntityConnection("Name = BAEntities"))
  {
    var eComm = econn.CreateCommand();
    EntityClient.EntityDataReader eReader = null;
    eComm.CommandText = esql;
    econn.Open();
    eReader = eComm.ExecuteReader(CommandBehavior.SequentialAccess);
    econn.Close();
    while (eReader.Read())
    {
      //process results
    }
  }
}
catch (Exception ex)
{
   throw; //Throw or handle exception
}

EntityConnectionString Exceptions

A number of problems are the result of a missing, misinformed, or even malformed EntityConnectionString.

Connection String Can’t Be Found or Is Improperly Configured: System.ArgumentException

The first step in most activities involving entities is to create an ObjectContext. As you’ve learned, this requires an EntityConnection, which in turn depends on the EntityConnectionString.

If the default or specified connection string cannot be found either in the application’s .config file or in other locations that you’ve designated, a System.Argument Exception will be thrown that reads as follows:

The specified named connection is either not found in the configuration,
 not intended to be used with the EntityClient provider, or not valid.

You will get this error if you are creating the EntityConnection directly and are passing in a connection string name with the same problem.

This is easier to deal with during debug mode than at runtime. In debug mode, check your entity connection string. If you are relying on the default in the .config file, make sure its name matches the EntityContainer name of your model. In our examples, the EntityContainer is BAEntities and the connection string should have the same name:

<connectionStrings>
<add name="BAEntities"  .... />

If that’s not the problem, check that the string doesn’t contain some type of invalid formatting.

Metadata Files Cannot Be Found: System.Data.MetadataException

The metadata attribute of the connection string has the names and paths of the model files (e.g., BAModel.csdl, BAModel.msl, BAModel.ssdl) hardcoded into it. The path could be a file path:

res:C:/BAModel.csdl|C:/BAModel.ssdl|C:/BAModel.msl

or a notation that indicates the files are embedded in an assembly:

res://*/BAModel.csdl|res://*/BAModel.ssdl|res://*/BAModel.msl

Many times in the ObjectContext life cycle these files need to be read so that when you instantiate the context, it looks for the files. If the files in the metadata tag cannot be found in the designated file path or in one of the referenced assemblies, a System.Data.MetadataException will be thrown.

If you have changed the name of the EDMX file in your solution, this can cause problems with the metadata attribute.

Note

In many of the following examples, the exact code for dealing with a problem may not be specified. If you put no code in a Catch statement, the exception will be caught but will not be thrown to the calling code, thereby going completely undetected. Minimally, you should call Throw to cause the exception to be raised to the calling code.

Handling Connection String Exceptions

Example 18-4 shows how to prepare for possible exceptions the aforementioned problems can throw. It looks for the message related to a missing or invalid ConnectionString as well as the MetadataException. You can handle the errors right in the code or throw them to the calling code.

Example 18-4. Catching a connection string problem in an ArgumentException

VB
Catch ex As ArgumentException
  If ex.Message.Contains("specified named connection is either not found") Then
    Throw     'replace with handling code
  Else
    Throw     'handle or Throw the exception
  End If
Catch ex As MetadataException
    Throw     'replace with handling code
Catch ex As Exception
  Throw
End Try
C#
catch (ArgumentException ex)
{
  if (ex.Message.Contains("specified named connection is either not found"))
    throw; //replace with handling code
  else
    throw; //handle or Throw the exception
}
catch (MetadataException ex)
{
    throw; // replace with handling code
}
catch (Exception ex)
{
  throw;
}

It’s not easy to resolve these types of problems in your code, and your best resolution in your exception handler is to exit out of the method elegantly, provide the end user with some information, and log the error in such a way that an administrator or support person can assist with the issue. The System.Data.MetadataException or System.ArgumentException contains no special information other than the message itself. You will benefit by using standard exception-handling methods, such as reporting the message along with the connection string and where the message came from.

Query Compilation Exceptions

If the connection succeeds, the next thing you will probably do that involves an Entity Framework-related exception is to create and execute a query.

Invalid LINQ to Entities Query Expressions: System.NotSupportedException

LINQ’s syntax has the benefit of IntelliSense and compile-time checking, so it is less prone to runtime errors. However, certain syntax will be valid to the compiler, but not when it comes time to process the query.

A good example of this is the use of the .NET method ToShortDateString(). The following LINQ query passes through the compiler’s checks because ToShortDateString is a valid method for a Date type:

From r In context.Reservations Select r.ReservationDate.ToShortDateString()

But at runtime, when it attempts to compile the query into a native store command, it will discover that ToShortDateString has no direct mapping to any function in the store. Thus, the store command cannot be created, which results in a System.NotSupportedException with the following message:

LINQ to Entities does not recognize the method 'System.String ToShortDateString()'
method, and this method cannot be translated into a store expression.

You should be able to catch these during debug mode.

Invalid Entity SQL Query Expressions: EntitySQLException

EntitySQLException will probably be the most common exception you will encounter as you are learning Entity SQL and debugging your applications. So, you might as well make friends with it straight away. Also, remember that the query builder methods can help you with a good portion of your Entity SQL queries, even though they provide only a subset of the operators and functions you can use when you write Entity SQL directly.

EntitySQLException is thrown when your Entity SQL expression cannot be parsed or processed. You should be testing every one of the Entity SQL expressions you write so that you don’t have any runtime surprises. However, if you are building dynamic queries, chances are greater that bad syntax will sneak in. And there are always the “what if” scenarios that you can’t even imagine until they occur, but that you might lie awake worrying about at night. So, rather than lose sleep, you can hedge your bets by making sure you catch any of the exceptions properly.

Note

Don’t forget about eSqlBlast, the tool for testing Entity SQL queries that I mentioned in Chapter 3. It’s a great help for testing Entity SQL expressions without having to constantly debug your code to do so.

Here’s an example of a malformed expression where the AS operator is missing; a common mistake. The expression should be using AS con after contacts; but with that missing, the variable con used elsewhere in the expression has no meaning.

SELECT VALUE con FROM BAEntities.contacts WHERE left(con.Lastname,1)='S'

The exception passes back some very helpful information contained in its properties.

The exception details are as follows:

Column= 61

In the preceding code, column 61 is where con.Lastname begins.

ErrorContext= multipart identifier

multipart identifier refers to the fact that multiple items (con and Lastname) exist and the parser has an issue with one (or more) of them. If the expression had selected a single value (SELECT VALUE con), the ErrorContext would be a singlepart identifier.

ErrorDescription = "'con.Lastname' could not be resolved in the current scope or context. Make sure that all referenced variables are in scope, that required schemas are loaded, and that namespaces are referenced correctly."

Again, this is saying that the parser just can’t figure out what con.Lastname is, and is listing all of the possible causes.

Another example occurs when you use incorrect functions or operators. Even the provider-specific functions and operators will be checked here. For instance, the following expression will throw an error because it incorrectly uses SqlServer.AVERAGE instead of the correct function, SqlServer.AVG:

SELECT VALUE SQLServer.AVERAGE(p.amount)
FROM BAEntities.Payments AS p

The exception’s message will read as follows:

'SqlServer.AVERAGE' cannot be resolved into a valid type constructor or function,
near function, method or type constructor, line 1, column 30."

The line break is not accounted for in the message. In addition, the parsing occurs long before any attempts to touch the database are made.

Example 18-5 shows the Catch block for a method that creates an EntityConnection and attempts to execute it.

Example 18-5. Checking for a connection string problem in an ArgumentException

VB
Catch ex As EntitySqlException
  Throw 'TODO: Replace with handling code
Catch ex As ArgumentException
  If ex.Message.Contains("specified named connection is either not found") Then
    Throw 'TODO: Replace with handling code
  Else
    Throw  'handle or Throw the exception
  End If
Catch ex As MetadataException
  Throw  'handle or Throw the exception
Catch ex As Exception
  Throw  'handle or Throw the exception
End Try
C#
catch (EntitySqlException ex)
{
    throw; //TODO: Replace with handling code
}
catch (ArgumentException ex)
{
  if (ex.Message.Contains("specified named connection is either not found"))
  {
    throw; //TODO: Replace with handling code
  }
  else
  {
    throw; //handle or Throw the exception
  }
}
catch (MetadataException ex)
{
  throw; //handle or Throw the exception
}
catch (Exception ex)
{
  throw; //handle or Throw the exception
}

Store Provider Issues: EntityCommandCompilationException

Command compilation occurs when the Entity Framework creates the command tree to represent a store query. It’s possible that the provider compiling the query is causing a problem. In this case, an EntityCommandCompilationException will be thrown with the following message, and no additional details:

An error occurred while preparing the command definition.

This is another tricky one to solve, although you won’t be able to solve it in your code. The best you can do is to log the exception, inform the user if necessary, and gracefully exit the method.

Creating a Common Wrapper to Handle Query Execution Exceptions

If you have a defined system for handling or perhaps logging errors, you wouldn’t want to rewrite that handling code for every query. Instead, you could build a set of wrappers to execute queries and handle particular exceptions. Each wrapper method would take either an ObjectQuery or a LINQ to Entities query as an argument and return either a single object or some type of enumerable collection of objects. If the query execution fails, the method could provide code to handle the different types of exceptions.

Here are the signatures of two methods, each with the two overloads for ObjectQuery or LINQ to Entities queries. The first method returns a single entity and the second returns a List of entities:

VB
Public Function ExecuteFirstorDefault(Of TEntity) _
 (ByVal objectQuery As ObjectQuery(Of TEntity)) As TEntity
Public Function ExecuteFirstorDefault(Of TEntity) _
 (ByVal L2EQuery As IQueryable(Of TEntity)) As TEntity

Public Function ExecuteList(Of TEntity) _
 (ByVal objectQuery As ObjectQuery(Of TEntity)) As List(Of TEntity)
Public Function ExecuteList (Of TEntity) _
 (ByVal objectQuery As ObjectQuery(Of TEntity)) As List(Of TEntity)
C#
public TEntity ExecuteFirstorDefault<TEntity>(ObjectQuery<TEntity> objectQuery)
public TEntity ExecuteFirstorDefault<TEntity>(IQueryable<TEntity> L2EQuery)

public List<TEntity> ExecuteList<TEntity>(ObjectQuery<TEntity> objectQuery)
public List<TEntity> ExecuteList<TEntity>( IQueryable <TEntity> L2EQuery)

Each method executes the given query and returns the requested result. For example, the first method would call return objectQuery.FirstOrDefault() or return L2EQuery.FirstOrDefault() to perform the execution. The ObjectQuery methods can take an ObjectQuery whether it was created using context.CreateQuery, new ObjectQuery, or a QueryBuilder method. Just be sure not to do anything to execute that query in the calling code. The methods that accept LINQ to Entities queries can take straight LINQ to Entities queries or those that were created by invoking a CompiledQuery.

Example 18-6 shows one of the methods with all of its Exceptions stubbed out. Remember that the ObjectQuery queries can throw EntitySQLExceptions, while the LINQ to Entities queries can throw InvalidOperationExceptions.

Example 18-6. The ExecuteFirstorDefault wrapper method for executing ObjectQuery queries

VB
Public Function ExecuteFirstorDefault(Of TEntity) _
(ByVal objectQuery As ObjectQuery(Of TEntity)) As TEntity
  Try
    Return objectQuery.FirstOrDefault()
  Catch ex As EntitySqlException
    Throw ex 'TODO: Replace with handling code
  Catch ex As ArgumentException
    If ex.Message.Contains("specified named connection is either not found") Then
	  Throw 'TODO: Replace with handling code
    Else
      Throw 'handle or Throw the exception
    End If
  Catch ex As MetadataException
    Throw 'handle or Throw the exception
  Catch ex As Exception
    Throw 'handle or Throw the exception
  End Try
End Function
C#
public TEntity ExecuteFirstorDefault<TEntity>(ObjectQuery<TEntity> objectQuery)
{
  try
  {
    return objectQuery.FirstOrDefault(); 
  }
  catch (EntitySqlException ex)
  {
    throw ex; //TODO: Replace with handling code
  }
  catch (ArgumentException ex)
  {
    if (ex.Message.Contains("specified named connection is either not found"))
      throw; //TODO: Replace with handling code
    else
      throw; //handle or Throw the exception
  }
   catch (MetadataException ex)
  {
    throw ; //handle or Throw the exception
  }
  catch (Exception ex)
  {
    throw; //handle or Throw the exception
  }
}

Example 18-7 shows code that calls the ExecuteList method overload to execute a LINQQuery.

Example 18-7. Executing a LINQ to Entities query with the ExecuteList method

VB
Dim linqquery = context.Contacts.OfType(Of Customer) _
                       .Include("Reservations") _
                       .Where(Function(c) c.Reservations.Any())
Dim custlist = dal.ExecuteList(Of Customer)(linqquery)
C#
var linqquery = context.Contacts.OfType<Customer>()
                      .Include("Reservations")
                      .Where(c => c.Reservations.Any());
var custlist = dal.ExecuteList<Customer>(linqquery );

You will still need some error handling in the calling code to handle particular scenarios, but the Execution query helper methods allow you to avoid repeating exception handling code that you may want to repeat for every query. Note that you don’t necessarily need to return EntityObjects from the queries. You could return a single or set of DbDataRecords or any other predefined class. The only thing that you can’t return would be an anonymous type that results from a LINQ to Entities projection. You’ll see these query execution methods used in later chapters.

SaveChanges Command Execution Exceptions

When it’s time to save changes back to the database you have another set of problems to be aware of. The connection issues raised earlier in this chapter will come into play if you are establishing a new ObjectContext or EntityConnection to perform the update. But the data itself causes other problems. The Entity Framework will catch some of the problems and prevent the data from going to the database. The database will detect others and will return an error to the application.

Model and Mapping Constraints Are Broken: UpdateException

If you have done something such as violated a constraint built into the model, an UpdateException will be thrown.

Relationships are constraints. If an association defines a 1:* (One to Many) relationship between two entities, any child in that relationship that is being saved needs to have some evidence of a parent. Even if the parent entity is not attached to the child, the EntityReference must have an EntityKey. Example 18-8 shows a new reservation being created in memory and added to the context, which then calls SaveChanges. But no Customer is associated with the context, not even an EntityKey for the CustomerReference. As a result, this call to SaveChanges will fail.

Example 18-8. A SaveChanges call that will fail because the new reservation has no Customer identified

VB
Dim res = New Reservation
res.TripReference.EntityKey = trip.EntityKey
res.ReservationDate = Today
Using context As New BAEntities
  context.AddToReservations(res)
  context.SaveChanges()
End Using
C#
var res = new Reservation();
res.TripReference.EntityKey = trip.EntityKey;
res.ReservationDate = DateTime.Today;
using (BAEntities context = new BAEntities())
{
  context.AddToReservations(res);
  context.SaveChanges();
}

Note

This code assigns the trip’s EntityKey to TripReference, rather than just stating res.Trip=trip. Remember that when the reservation is added to the context, the context will also try to add anything attached to that reservation. If the trip, which came from the database, is attached to the reservation, you’ll get an error as the context attempts to assign a new temporary EntityKey to that trip.

SaveChanges throws an UpdateException with the following message:

Entities in 'BAEntities.Reservations' participate in the FK_Reservations_Customers'
relationship.
0 related 'Customers' were found. 1 'Customers' is expected.

The ObjectContext doesn’t do this type of validation when you add the reservation to the context, because you might attach a Customer or its EntityKey later. Therefore, the only time it’s confident that you have no intention of adding a Customer is when you are calling SavingChanges, and that’s when it does its check.

Note

You should detect this type of problem before your code goes into production. You can also employ your own business rules to perform these types of checks before it’s time to call SaveChanges.

For example, you could have specific rules in the Reservation class that test to see whether a Customer is defined by checking for the presence of the CustomerReference.EntityKey or by checking that the Customer property is not null. The code behind SaveChanges uses the MetadataWorkspace to read the model, identify the constraints, and then check the entities in the cache to see whether they pass or fail the constraints. You could write similar generic code to perform this type of function as well, if it makes sense for you to do so.

Exceptions Thrown by Broken Constraints in the Database

Other constraints may not be defined in the model or handled by any business logic. Such is the case with the dependency that a Payment must contain a valid ReservationID. The database does not define a cascading delete to delete any payments related to a reservation if that reservation is being deleted. Therefore, if an attempt is made to delete a reservation that would leave orphaned Payment records, the database will throw an error and will not execute the delete command. That error is passed back to the client. If the client is the Entity Framework and the error was a result of a SaveChanges call, an UpdateException will be thrown with the following message:

The DELETE statement conflicted with the REFERENCE constraint
"FK_Payments_Reservations".

The conflict occurred in database "BreakAway", table "dbo.Payments",
column 'ReservationID'.

Automatically Rolling Back SaveChanges When an UpdateException Occurs

In Chapter 16, you learned that SaveChanges is wrapped in an implicit transaction. If an UpdateException is thrown during the call to SaveChanges, this halts the entire SaveChanges method and causes any previously executed commands to be rolled back.

ObjectStateEntries Returned by Object Services Exceptions

UpdateException is part of the System.Data.Entity API and is an Object Services exception. It inherits from .NET’s DataException class and adds to it a valuable piece of information: the ObjectStateEntry of the entity being processed when the error occurred.

Figure 18-1 shows the UpdateException thrown by SaveChanges in Example 18-2, where the Reservation has no Customer.

Exceptions from Object Services containing an ObjectStateEntry

Figure 18-1. Exceptions from Object Services containing an ObjectStateEntry

In the exception, you can see the StateEntries property. Multiple entries can appear in this property; for example, if you have relationships to other entities being managed by the context, the RelationshipEntry objects will be in this collection. But only the primary entry will be displayed in the debug window. And in this window you can see the ObjectStateEntry that is related to the Reservation, and that the entry has a pointer back to the entity.

If you want to you can log this information or present it to the user.

Remember that the context might be tracking a number of entities, so it’s not always going to be obvious which entity caused the problem. By having this information returned in the exception, you can handle the exception intelligently.

General Entity Exceptions That May Occur When Executing Queries or Commands

A number of other exceptions derive from the generic System.Data.EntityException. Although some of these exceptions are internal, some may be raised simply as an EntityException.

For example, if there is a problem with the database server during command execution, an EntityException with the following message could be thrown:

"An error occurred while starting a transaction on the provider connection.
See the inner exception for details."

The InnerException will contain the actual error from the database, such as the following error in which, for dramatic effect, the SQL Server service was paused on the server:

"SQL Server service has been paused. No new connections will be allowed.
To resume the service, use SQL Computer Manager or the Services application
in Control Panel. Login failed for user 'domainjulie'. A severe error occurred
on the current command.  The results, if any, should be discarded."

Note

Although these are the exceptions you will most likely encounter, check the documentation of the EntityException base class to learn about some of the other exceptions that can occur during query and command execution.

InvalidOperationExceptions

Not all entity-related exceptions are query and execution related. One example you may encounter is the System.InvalidOperationException. This will be thrown when, for instance, you try to add an object to a context that already has an EntityKey. ObjectContext.Add is used to add new entities, and part of its job is to create an EntityKey. This is the type of exception you can avoid through testing and debugging. But you may certainly encounter it during testing and debugging.

In Example 18-9, the code that calls AddToContacts will throw an InvalidOperationException. The error results from the fact that the entity already has an EntityKey. The appropriate method to use is Attach.

Example 18-9. Trying to add an entity that already has an EntityKey

VB
Dim con = context.Contacts.First
context.Detach(con)
context.AddToContacts(con)
C#
var con=context.Contacts.First();
context.Detach(con);
context.AddToContacts(con);

InvalidOperationException is another exception you may want to plan for when working with entities.

Exceptions When Multiple Parties Edit Data Concurrently

Concurrency issues are the bane of data access developers. In any sizable organization, it is not uncommon for multiple users or services to be processing the same sets of information. Not infrequently, different users may be updating the same piece of data concurrently, and a conflict occurs. For instance, a salesperson could be modifying a payment at the same time an accounting system is processing it. Or a scheduler might delete a calendar item while another person in a different department was in the middle of editing the same item.

These are two very different types of concurrency problems. In the first problem, you need to consider whose changes are saved. Does the accounting system rule over the salesperson, or vice versa? Do you just take the last changes that were sent to the database, overriding the changes that were just saved? In some organizations the focus is on a single record, whereas other organizations might get as granular as worrying about which fields in the record were updated by whom.

In the second example, the problem occurs when the second user tries to save her changes but the record no longer exists in the database. What do you do then? You can’t assume that her changes are unimportant just because the record was deleted. You might at least want to inform the user about the problem and give her an opportunity to take further action, if she has the proper permissions.

It is a tangled web of conundrums and decision making on the part of the application designer. Once your organization has devised the rules, it is up to the developer to implement them. But how?

Handling Concurrency Conflicts

In the database world, there are two ways to deal with concurrency conflicts. One involves pessimistic concurrency, where you expect the worst; the other is optimistic concurrency, where you hope for the best.

With pessimistic concurrency the database row is locked when a user retrieves that data and is then released when the user is finished working with that row. Nobody else can touch that data until the row is unlocked. It greatly reduces the potential of conflicts, but it comes at a price. Pessimistic concurrency is not scalable, because it maintains open connections and it can cause deadlocks.

A number of data access technologies do not support pessimistic concurrency because of the overhead involved. ADO.NET does not support it, nor does the ADO.NET Entity Framework. Therefore, this chapter will not cover pessimistic concurrency, but will focus instead on optimistic concurrency.

Optimistic concurrency does not lock the database rows, and relies on you, the developer, to provide logic in your application to handle potential update conflicts.

Concurrency is an age-old problem for anybody who designs line-of-business applications, and there is no silver bullet solution. You need to understand your business rules, be aware that these scenarios will need to be considered, and build your business logic to follow the rules you desire.

The Entity Framework does not magically solve the problem for you, either; however, it does provide tools for you to implement your business logic.

Understanding Optimistic Concurrency Options in the Entity Framework

Given that pessimistic concurrency is not an option for the Entity Framework, what are your options when multiple people (or processes) are concurrently editing data? First we’ll survey the lay of the land, and then we’ll dig into implementation.

A few solutions are commonly used; however, a narrower field of applications will process concurrency conflicts in a very granular way, which is not as common.

We’ll look at these options as they are generally used in software and then focus on how the Entity Framework addresses them.

Ignoring Concurrency Conflicts

Many small systems don’t even worry about these conflicts. When a user saves her changes, they are written to the database regardless of what anybody else is doing or has done.

The Entity Framework’s default commands play an interesting role here. Because an Entity Framework update will update only the fields the user edited, it’s possible that concurrent editing won’t even cause a problem. Imagine that User A retrieves a Customer record, and while she is editing that record User B edits the same Customer, changing the BirthDate property. User B saves his changes. User A modifies the Customer’s Notes field and saves. The Entity Framework will write a command to update the Notes field for that Customer. It won’t touch the BirthDate, so all of the edits by both users are safe.

When using stored procedures to update, however, this scenario won’t be so rosy. The procedure will most likely update every field regardless of its status. So, in that case, the original BirthDate value will be saved back to the database, and User B’s changes will disappear.

Forcing the User’s Data to the Server (ClientWins)

In a system designed to alert you of conflicts, the system would alert you when User A attempts to save her data, indicating that the record has been modified since the time she initially retrieved it. In the Entity Framework, one option in this case is to force the current user’s updates to the database. This is also referred to as ClientWins. It’s different from ignoring the conflict, however, because it will update all of the values in the entity, even those that have not been edited. The impact in the scenario described in the preceding section is that the BirthDate field and every other field in the database record will be changed to reflect the user’s version of the data. It would have the same effect as the stored procedure.

Refreshing the User’s Data with Server Data (StoreWins)

In this resolution, when the conflict is detected the user’s data is refreshed from the server. The entity that she has just modified in her application will be updated to reflect the server’s current version of the data. Any edits she made will be lost. That may sound malicious, but if this is the expected behavior of the application, it shouldn’t be a problem. The application can alert the user and she can apply her edits to the Notes field again (or the application can do that for her if, for example, the changes still exist in memory, or even in the Text value of a user control), and then she can save again. If a process that doesn’t involve a user is making the updates, you should apply logic that doesn’t require user interface.

Determining the Scope of Changes

You can discover whether data has changed on the server while a user is in the process of editing the same data in a number of ways:

Check for any change at all to the record

To do this, you would need to compare every field in the property to see whether that row was edited. The Entity Framework supports this using its ConcurrencyMode property. Another mechanism that developers use is a database function called checksum that computes a hash value from all of the data in a particular row. The Entity Framework doesn’t have direct support for checksum, but you can access it using Entity SQL, because of the way Entity SQL allows you to use database functions.

Check for particular field changes

Here you need to focus on only one or more specific fields that would indicate a change has been made. Database rowversion fields are great for this. But you may really be interested in a few specific properties. For example, with an employee record, you may determine that the only piece of data in which a conflict would create a problem is the Social Security number. Rather than using rowversion, which indicates that something changed with no regard to which field that may have been, you could specifically watch only the Social Security number field. If it was updated during a concurrent operation, it’s time to raise a flag.

Check to see whether the fields you are updating have changed

The Entity Framework does not support this directly. You would have to do additional queries to check in this way.

Note

SQL Server’s timestamp type is the same as the ISO standard rowversion type and its name is a bit confusing. Microsoft will be changing the name of this type to rowversion in a future version of SQL Server.

“The timestamp syntax is deprecated. This feature will be removed in a future version of Microsoft SQL Server. Avoid using this feature in new development work, and plan to modify applications that currently use this feature.”

Using rowversion for Concurrency Checks

The simplest mechanism for detecting that anything in a database row has changed is to use a rowversion field. A rowversion is a binary field that is automatically updated whenever changes are made to the row. Many databases have a specific field type that is used for this. As noted earlier, SQL Server’s timestamp is a rowversion field and will eventually be renamed to rowversion in a future version. With databases that do not have an explicit rowversion type, patterns are available for creating triggers to update fields in your database.

If you use rowversion fields in your database and they are surfaced as properties in your entities, the Entity Framework will need to check only that single field to detect whether a change was made to the data in the data store.

Note

Because the BreakAway database is a SQL Server database and its field type is timestamp, I will use that term frequently in this discussion.

A number of entities in the BreakAway model have TimeStamp properties, which map to timestamp fields in the database.

In the EDM, the TimeStamp property is a non-nullable binary field. In the store schema in the EDMX, the field is also non-nullable (Nullable=false) and its StoreGeneratedPattern is Computed, so the Entity Framework does not need to worry about managing this field:

CSDL
<Property Name="TimeStamp" Type="Binary" Nullable="false" MaxLength="8"
          FixedLength="true" />
SSDL
<Property Name="TimeStamp" Type="timestamp" Nullable="false"
          StoreGeneratedPattern="Computed" />

When working with disconnected data, a rowversion field—whether your database inherently supports it or you have to use a binary field with triggers—is one of the most important tools you have in your arsenal for dealing with concurrent systems. Otherwise, if you want to identify that a change has been made to a table row, you may have to consider a less efficient method of concurrency checking, such as checking every single field in the row.

Implementing Optimistic Concurrency with the Entity Framework

In the Entity Framework, enabling optimistic concurrency checks requires two steps:

  1. Define which property or properties will be used to perform the concurrency check.

  2. Handle the OptimisticConcurrencyException that is thrown when the check fails.

First we’ll look at the various methods and effects of identifying the properties for the concurrency checks, and after that we’ll dig into the exception handling.

Flagging a Property for Concurrency Checking

Because concurrency is defined on a property-by-property basis in the Entity Framework, the first step is to identify the property or properties that you will use for concurrency checking. We’ll use the Contact entity’s TimeStamp field.

The ConcurrencyMode property of an entity is used to flag a property for concurrency checking and can be found right in the Properties window. Its options are None, which is the default, and Fixed, as shown in Figure 18-2.

Setting a property’s concurrency mode

Figure 18-2. Setting a property’s concurrency mode

By setting Concurrency Mode to Fixed, you ensure that the property is validated when an entity that was retrieved from the database is updated during a call to SaveChanges.

How the Entity Framework Uses the ConcurrencyMode Property

When Object Services prepares an Update or Delete command, it uses any properties marked for concurrency checking as a filter in the command along with the identity key.

With the ConcurrencyMode of Contact.TimeStamp set to Fixed, anytime a Contact is updated the Update command will look for the Contact using its EntityKey and its TimeStamp property.

For example, if Charles Petzold is knighted, his Title property will change from Mr. to Sir, as shown in Example 18-10.

Example 18-10. Changing a property of an entity with a concurrency checking property

VB
Using context As New BAEntities
  Dim con = context.Contacts _
           .Where(Function(c) c.LastName = "Petzold" And c.FirstName = "Charles") _
           .FirstOrDefault
  con.Title = "Sir"
  context.SaveChanges()
End Using
C#
using (BAEntities context = new BAEntities())
{
  var con = context.Contacts
            .Where(c => c.LastName == "Petzold" && c.FirstName == "Charles")
            .FirstOrDefault();
  con.Title = "Sir";
  context.SaveChanges();
}

When SaveChanges is called, the command shown in Example 18-11 will be sent to the database.

Example 18-11. The T-SQL Update command when using a TimeStamp field for optimistic concurrency checking

exec sp_executesql N'update [dbo].[Contact]
set [Title] = @0
where (([ContactID] = @1) and ([TimeStamp] = @2))
select [TimeStamp]
from [dbo].[Contact]
where @@ROWCOUNT > 0 and [ContactID] = @1',N'@0 nchar(3),@1 int,@2
binary(8)',@0=N'Sir',@1=850,@2=0x000000000000791A

That last value in Example 18-11 is the binary TimeStamp field. The command is attempting to update a Contact record where ContactID=1 and TimeStamp=0x000000000000791A, the original value of TimeStamp when the contact was first retrieved.

If that TimeStamp value had changed since the first query, due to someone else editing that record, the Update command will not find a matching record and will throw an error back to the client. The Entity Framework will report this as an OptimisticConcurrencyException.

This same type of concurrency check will also happen if the user is attempting to delete the contact. It will add the TimeStamp to the WHERE clause of the command. Every property that is marked as ConcurrencyMode=Fixed in your entity will be incorporated into the WHERE clause of Update and Delete commands in this way.

You’ll read more about OptimisticConcurrencyException after reviewing some other options.

Concurrency Checking Without a rowversion Field

Although the rowversion concurrency checks are the most common methods developers use, you may not have that field available as an option.

In that case, you can use the ModifiedDate DateTime fields, but you need to be sure they are being updated. Although the SavingChanges example in Chapter 10 ensures that a particular model’s ObjectContext always updates the ModifiedDate fields, this does not give you full coverage. You need to be sure that any application, process, or even user accessing the database directly updates that field every time, or the concurrency check will not detect a change.

Another method is to mark every property in the entity as FIXED. Although this does the trick, it makes your commands less efficient because all of the properties’ original values will become part of every WHERE clause.

Concurrency Checking on a Checksum in the Data Store

With the assumption that you are thinking about CheckSum because you are unable to modify the database to use timestamp fields, a last resort is to use a QueryView or a Store Schema Definition Layer (SSDL) function, and to write store function queries (store queries written directly in the SSDL, as you saw in Chapter 13) directly into your model.

If you have checksum functions in the data store or you are implementing them in the SSDL, you still need to consider the actual act of performing the update so that you can get a concurrency check. If the CheckSum value is directly in the data table, it is represented in your entity as a binary property. It can be marked as a Fixed field and used for concurrency checks in the same way you use the TimeStamp or any other property in an entity.

If you have used QueryView or a store function to query data that includes a checksum value, you will need to use stored procedures for the update and delete operations, and these stored procedures will need to perform the concurrency checking.

Concurrency Checks for EntityReference Navigation Properties

If an entity has navigation properties that point to a parent or “One” side of a relationship, the concurrency check will still take place if that relationship changes. In the database, a foreign key value represents the relationship. So, as long as something changes that value and causes the TimeStamp field to change, when your application attempts to update the same row the change will be detected.

Concurrency Checks and Inherited Types

Version 1 of the Entity Framework does not support concurrency checks in derived types, period. You will see this quickly if you attempt to change the ConcurrencyMode property of any property in a derived type.

With inherited types, however, you may only use properties from the base type for concurrency checks. If you set the ConcurrencyMode of any property in a derived type to Fixed, you will get a validation error on the model that says that new concurrency requirements are not allowed for subtypes of base EntitySet types.

Given that you can’t perform concurrency checks on derived types, it’s important to see what behavior you can expect if the base type has any concurrency properties.

When you edit an inherited type that has a concurrency check in its base type, concurrency checking will happen, but only on the base type itself. Let’s take a closer look at this.

In the BreakAway model, Customer inherits from Contact and Contact has a TimeStamp field that is now being used for concurrency checks. What happens when a Customer entity is being edited and a field that is specific to Customer has been modified? (See Example 18-12.)

Example 18-12. Modifying a derived entity whose base entity has a concurrency checking property

VB
Dim cust = context.Contacts.OfType(Of Customer).First
cust.InitialDate = cust.InitialDate.Value.AddDays(1)
context.SaveChanges()
C#
var cust = context.Contacts.OfType<Customer>.First();
cust.InitialDate = cust.InitialDate.Value.AddDays(1);
context.SaveChanges();

In this case, two commands will be sent to the database. The first will test to see whether anything in the Contact has changed. It is an update command that first declares a new variable (@p) and then attempts to update it using the ContactID and TimeStamp filter, as shown in Example 18-13.

Example 18-13. T-SQL checking for a change in the table related to the base entity before updating the derived entity

exec sp_executesql N'declare @p int
update [dbo].[Contact]
set @p = 0
where (([ContactID] = @0) and ([TimeStamp] = @1))
select [TimeStamp]
from [dbo].[Contact]
where @@ROWCOUNT > 0 and [ContactID] = @0',N'@0 int,@1
binary(8)',@0=1,@1=0x00000000000016B9

If a contact with a matching ContactID and TimeStamp is found, the context will send the next command, which is the one to update the InitialDate field in the Customer table, to be executed in the database. If the contact is not found, the command will throw an error back to the ObjectContext, which will in turn throw an OptimisticConcurrencyException.

There is a problem that you need to keep an eye on here. This mechanism assumes that all updates are being made through this model and that anytime something in the Customer table is changed, the TimeStamp field of the Contact table will be modified.

However, other applications may be using the same data, or even other EDMs that map to this data where the Customer is not a derived type. If one of the Customer table fields in the database is modified, the concurrency check will not detect it.

In this case, if you do need the check to be performed, you may want to rely on stored procedures for your DML commands.

Concurrency Checks and Stored Procedures

If you have mapped stored procedures to the insert, update, and delete functions of an entity, any properties marked as Fixed in that entity will not automatically be used in concurrency checks. However, you can define concurrency checking in the function mappings; the stored procedure that the functions are based on needs to be designed correctly.

Defining a stored procedure to perform concurrency checking

If your stored procedure has one or more parameters that take in values to be used for concurrency checking, when mapping to these parameters you can force the original value to be sent to that parameter with the Use Original Value checkbox.

There is one other important requirement for the stored procedure and the mapping. The database needs to return the new timestamp value to the entity. That way, if you need to use the original value again, it will be the correct version.

Adding an additional SELECT statement after the UPDATE command will impact the procedure’s ability to return an error, because the SELECT statement will most likely succeed. Therefore, between the UPDATE command and the SELECT command, you will need to test to see whether the update was successful. If it was, continue with the SELECT; otherwise, the procedure will be finished and the error will be returned to your application.

The UpdatePayment stored procedure you already mapped to the Payment entity is written for you to use in this way. Example 18-14 displays the stored procedure.

Example 18-14. The UpdatePayment stored procedure

ALTER PROCEDURE [dbo].[UpdatePayment]
@PaymentID INT,
@date DATETIME,
@reservationID INT,
@amount MONEY,
@modifiedDate DATETIME,
@timestamp timestamp

AS

UPDATE payments
SET paymentdate=@date,reservationID=@reservationID,amount=@amount
WHERE
paymentid=@paymentid AND TIMESTAMP=@timestamp

IF @@ROWCOUNT>0
     SELECT timestamp AS newTimeStamp
     FROM payments WHERE paymentid=@paymentid

In Chapter 7, when you mapped the UpdatePayment function, you selected Use Original Value next to the TimeStamp property and you defined the function to capture the timestamp value that the stored procedure returned.

If you want to see this in action, now you can test the concurrency checking with Example 18-15, a short routine you can use to test the UpdatePayment procedure.

Example 18-15. Testing for update conflicts with function mappings

VB
Using context As New BAEntities
  Dim pmt = context.Payments.First
  pmt.PaymentDate = pmt.PaymentDate.Value.AddDays(1)
  Dim origTS = pmt.TimeStamp
  Try
    context.SaveChanges()
    Dim newTS = pmt.TimeStamp
    If newTS Is origTS Then
      Console.WriteLine("timestamp not updated")
    Else
      Console.WriteLine("timestamp updated")
    End If
  Catch ex As OptimisticConcurrencyException
    Console.WriteLine("Concurrency Exception was thrown")
  End Try
End Using
C#
using (BAEntities context = new BAEntities())
{
  var pmt = context.Payments.First;
  pmt.PaymentDate = pmt.PaymentDate.Value.AddDays(1);
  var origTS = pmt.TimeStamp;
  try
  {
    context.SaveChanges();
    var newTS = pmt.TimeStamp;
    if (newTS == origTS)
      Console.WriteLine("timestamp not updated");
    else
      Console.WriteLine("timestamp updated");
  }
  catch (OptimisticConcurrencyException ex)
  {
    Console.WriteLine("Concurrency Exception was thrown");
  }
}

You’ll see that the Payment entity’s TimeStamp property is updated when the update is successful. If you test the collision by editing the database manually at the suggested breakpoint, an OptimisticConcurrencyException will be thrown.

Handling OptimisticConcurrencyExceptions

Now it’s time to look at the other piece of the concurrency puzzle: handling the exception that is thrown when a concurrency check fails.

When a check fails and a System.Data.OptimisticConcurrencyException is thrown, this is where you can inject your business logic to determine how to deal with the issue.

The most common resolutions, as described earlier, are to force the client-side data to the server, or to pull the server-side data to the client and lose the client’s edits. You can perform either of these actions using ObjectContext.Refresh. While many applications handle concurrency conflicts, it is more common to have a sweeping rule rather than to have logic handle very narrow cases. We’ll look at both scenarios, but we’ll spend more time on the more commonly used patterns.

Using ObjectContext.Refresh

ObjectContext.Refresh allows you to refresh entities in the context from the database. You can also use it in other places in your application. Here we’ll focus on using it to handle OptimisticConcurrencyExceptions.

You can use Refresh to force either a ClientWins scenario or a StoreWins scenario with your updates.

Refresh takes two parameters. The first is RefreshMode, which has the options RefreshMode.ClientWins and RefreshMode.StoreWins. The second parameter is either a single entity or an IEnumerable of entities. The IEnumerable can be something such as a List or an Array, or even an IQueryable (LINQ to Entities query) or ObjectQuery:

context.Refresh(RefreshMode.ClientWins, aTrip)

If the RefreshMode is ClientWins, a query will be executed against the database to get the current server values for the entity. Then it will push those values into the original values of the entity. That will make the entity think it started out with those server values and it will build the update commands accordingly when SaveChanges is called again.

StoreWins will replace all of the current and original values of the entity with the data from the server. The user will lose her edits, and instead the cached data will be in sync with the database. Entities that are refreshed with StoreWins will be ignored by SaveChanges until they are edited again.

To get your first look at this, let’s focus on a single entity and see what Refresh looks like in a basic scenario.

Using ClientWins Refresh

In Example 18-16, a simple query returns a single entity. If a conflict arises during a call to SaveChanges, that same entity is passed into the Refresh method.

Example 18-16. A ClientWins refresh on a single entity

VB
Using context As New BAEntities
  Dim con = context.Contacts _
           .Where(Function(c) c.LastName = "Petzold" And c.FirstName = "Charles") _
           .FirstOrDefault
  con.Title = "Sir"
Try
  context.SaveChanges()
Catch ex As OptimisticConcurrencyException
 'Refresh the contact entity,using ClientWins
  context.Refresh(RefreshMode.ClientWins, aTrip)
 'SaveChanges again   
  context.SaveChanges()
End Using
C#
using (BAEntities context = new BAEntities())
{
  var con = context.Contacts
                   .Where(c => c.LastName == "Petzold" && c.FirstName == "Charles")
                   .FirstOrDefault();
  con.Title = "Sir";
  try
  {
    context.SaveChanges();
  }
  catch (OptimisticConcurrencyException)
  {
    //Refresh the contact entity,using ClientWins;
    context.Refresh(RefreshMode.ClientWins, con);
    //SaveChanges again;
    context.SaveChanges();
  }
}

Before SaveChanges is called, if another user has edited the same contact, causing the timestamp field to be updated, an OptimisticConcurrencyException will be thrown when SaveChanges is called.

Note

To test the OptimisticConcurrencyException, you’ll need to emulate an edit being made by another user or process. To do this, place a breakpoint on context.SaveChanges. When the breakpoint is hit, open the Contact table in the Solution Explorer and edit the matching record.

Figure 18-3 shows the state of that contact using the ObjectStateEntry visualizer (which you built in Chapter 17) before the Refresh is executed.

The state of the Contact entity before calling ObjectContext.Refresh

Figure 18-3. The state of the Contact entity before calling ObjectContext.Refresh

The only changed field is Title, with the current value of Sir and original value of Mr..

Next, Refresh is called, which executes a query to retrieve the current values of this entity in the database, including the new timestamp. Figure 18-4 shows the contact after Refresh has been called.

ObjectContext.Refresh with ClientWins refreshing all of the original values of the designated entities, even those that have not changed, leaving every property modified

Figure 18-4. ObjectContext.Refresh with ClientWins refreshing all of the original values of the designated entities, even those that have not changed, leaving every property modified

All of the original fields have been updated to reflect the latest server values, and you can see that on the server side some naughty person changed Mr. Petzold’s first name to Chuck, causing the TimeStamp field to be updated.

Because every property was modified in this entity, each property’s EntityState was changed to Modified. That means when SaveChanges is called again, every value will be sent to the server for updating. This time the record will be found because you have the new value of the TimeStamp for the WHERE clause. The update succeeds and we now have Sir Charles Petzold, which has quite a nice ring to it.

Using StoreWins Refresh

Let’s take the same scenario and see what happens when you choose the StoreWins option.

Figure 18-5 shows the state of the Contact entity after Refresh(StoreWins, con) has been called. The entity’s state is Unchanged and the Current and Original values have been replaced with the server-side values. The local entity has lost its nice title of Sir and has acquired the nickname Chuck. When SaveChanges is called again, it will do nothing because this entity is now Unchanged.

ObjectContext.Refresh with StoreWins refreshing the entities by completely synchronizing them with the database

Figure 18-5. ObjectContext.Refresh with StoreWins refreshing the entities by completely synchronizing them with the database

In this case there is no need to call SaveChanges again, because you have done a StoreWins refresh on the only entity in the ObjectContext. However, if you are building a generic routine, it’s safer to call SaveChanges anyway, as you may have other entities in the ObjectContext that you need to deal with. It doesn’t waste any resources if there is nothing to change.

Refreshing Collections of Entities

You also can use Refresh with a collection of entities. The easiest scenario with which to use this overload is when you already have a set of entities encapsulated in a collection. For example, if you are working with a list of Contact entities, you can refresh the entire list at once. This makes an assumption that your business rules don’t require any granular decision making to determine whether this type of update is appropriate for every entity in that collection.

Example 18-17 shows a simple query that changes any contact with a FirstName of Chuck to Charles. Then, if there is a concurrency exception, it uses the brute force of a ClientWins refresh to ensure that this change is made to the database.

Example 18-17. Doing a ClientWins Refresh on a set of entities

VB
Using context As New BAEntities
  Dim cons = context.Contacts.Where("(it.FirstName)='Chuck'").ToList()
  For Each con In cons
    con.FirstName = "Charles"
  Next
  Try
    context.SaveChanges()'breakpoint here to modify server data
  Catch ex As OptimisticConcurrencyException
    context.Refresh(RefreshMode.ClientWins, cons)
    context.SaveChanges()
  End Try
End Using
C#
using (BAEntities context = new BAEntities())
{
  var cons = context.Contacts.Where("(it.FirstName)='Chuck'").ToList();
  foreach (var con in cons)
    con.FirstName = "Charles";
  try
  {
    context.SaveChanges();
  }
  catch (OptimisticConcurrencyException ex)
  {
    context.Refresh(RefreshMode.ClientWins, cons);
    context.SaveChanges();
  }
}

In this case, we are passing the entire list of Contact entities to the Refresh method.

Note

As I explained earlier in the book, I recommend that you not work directly with the query unless you want to execute it again and instead that you create a set of results, such as a List. This is to avoid accidental query execution. However, I did test to see what would happen if I passed an ObjectQuery and a LINQ IQueryable directly into a Refresh command. Did it wreak havoc? No. It made no attempt to execute the query again. The behavior was no different from passing in the List, as in Example 18-17.

Refresh builds a query to retrieve the current store contact data by placing the EntityKeys into one big WHERE clause so that it is a single query:

SELECT
1 AS [C1],
CASE WHEN (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL))
THEN '0X0X' ELSE '0X1X' END AS [C2],
[Extent1].[ContactID] AS [ContactID],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName], etc........
WHERE ([Extent1].[ContactID] = 468) OR ([Extent1].[ContactID] = 471) OR
([Extent1].[ContactID] = 474) OR ([Extent1].[ContactID] = 480) OR
([Extent1].[ContactID] = 489) OR ([Extent1].[ContactID] = 492) OR
([Extent1].[ContactID] = 498) OR ([Extent1].[ContactID] = 506) OR
([Extent1].[ContactID] = 578) OR ([Extent1].[ContactID] = 581) OR
([Extent1].[ContactID] = 587) OR ([Extent1].[ContactID] = 596) OR
([Extent1].[ContactID] = 805)

This way, it is able to refresh all of the items in the collection at once.

Note

Remember that this means any other fields of any of these records that were modified in the meantime will be overwritten by the values in the entities in the context. However, only the originally modified entities will be updated when SaveChanges is called again. Any Unchanged entities that were refreshed will still be Unchanged after the refresh and therefore overlooked by SaveChanges.

Refreshing Related Entities in a Graph

If a modified entity is within a graph and it causes a concurrency exception, be cautious about which entities you pass into the Refresh method. Refresh will only refresh the parent node of a graph and will not impact any related entities in the graph.

Warning

Beware! Refresh does not impact graphs.

For instance, in Example 18-18, addressGraph is a graph whose main entity is an address that contains a contact. If the contact’s update throws a concurrency exception when SaveChanges is called, you might want to solve that by calling Refresh on the addressGraph.

Example 18-18. Refreshing a graph—not the results you might expect

VB
Dim addressGraph = context.Addresses.Include("Contact").First
add.Contact.FirstName =  _
   New String(add.Contact.FirstName.Trim.Reverse.ToArray)
Try
  context.SaveChanges()
Catch ex As OptimisticConcurrencyException
  context.Refresh(RefreshMode.StoreWins, add)
  context.SaveChanges()
End Try
C#
var addressGraph = context.Addresses.Include("Contact").First();
addressGraph.Contact.Title = "Dr.";
try
{ context.SaveAllChanges(); }
catch (OptimisticConcurrencyException)
{
  context.Refresh(RefreshMode.StoreWins, addressGraph);
  context.SaveAllChanges();
}

But only the parent entity of the graph, the Address entity, will be refreshed. The contact will continue to cause the exception every time you save changes.

But there is a way to attack this problem. Remember that the exception returns ObjectStateEntry objects for the entity that was causing the problem.

This means that in the exception, you will have the ObjectStateEntry for the contact, which contains a reference to the entity. You can extract that entry’s entity and call Refresh on the contact, and then call SaveChanges again if necessary. Example 18-19 shows the code for this.

Example 18-19. Getting a graph child to refresh

VB
Catch ex As OptimisticConcurrencyException
  Dim contact=ex.StateEntries(0).Entity
  context.Refresh(RefreshMode.ClientWins, contact)
  context.SaveChanges()
End Try
C#
catch (OptimisticConcurrencyException ex)
{
  var contact = ex.StateEntries[0].Entity;
  context.Refresh(RefreshMode.ClientWins, contact);
  context.SaveAllChanges();
}

Rewinding and Starting Again, and Maybe Again After That

It’s important to realize that when handling these exceptions, SaveChanges won’t just continue on its merry way, updating the next entity in the context. If you hit the exception, the SaveChanges method rolls back whatever it has already done and then halts.

In the exception handler you can call SaveChanges again. However, if that call fails, you need to catch it again. If you are pushing a lot of changes in one SaveChanges call and a number of exceptions are in there, each time you call SaveChanges you may have fixed the last problem but you will then hit the next one.

So again, you need to trap that error, handle it, and call SaveChanges again. You will end up with code that looks like the VB code in Example 18-20 (the C# version would require quite a lot of braces).

Example 18-20. Catching a number of concurrency exceptions

VB
Try
Context.SaveChanges
Catch ex As OptimisticConcurrencyException
 'do some work, then try again
  Try
    context.SaveChanges()
  Catch ex As OptimisticConcurrencyException
   'do some work, then try again
    Try
      context.SaveChanges()
      Catch ex As OptimisticConcurrencyException
       'do some work, then try again
        Try
          context.SaveChanges()
        Catch ex As OptimisticConcurrencyException
          'and so on and so forth....   
        End Try
    End Try
  End Try
End Try

You will be better off having your own method that calls SaveChanges and contains the exception handler. Then you can call the method recursively as needed. Example 18-21 shows a custom method, SaveMyChanges. Further on, you’ll see a twist on this, which is a method added to the partial class for BAEntities, allowing you to call context.SaveMyChanges without having to pass the context in as a parameter.

Example 18-21. Handling concurrency exceptions recursively

VB
Private Sub SaveMyChanges(ByVal context As Objects.ObjectContext)
  Try
    context.SaveChanges()
  Catch ex As OptimisticConcurrencyException
    'handle concurrency exception here
    'then try again
    SaveMyChanges(context)
  Catch ex As Exception
    'handle other exceptions
  End Try
End Sub
C#
private void SaveMyChanges(Objects.ObjectContext context)
{
  try
  {
    context.SaveChanges();
  }
  catch (OptimisticConcurrencyException ex)
  {
    //handle concurrency exception here
    //then try again
    SaveMyChanges(context);
  }
  catch (Exception ex)
  {
    //handle other exceptions
  }
}

Note

Although I prefer the separate and reusable method, SaveMyChanges, you can find other ways to recursively call SaveChanges in the MSDN documentation. One example, which is combined with a System.Transaction.TransactionScope (more on transactions and exceptions later in this chapter), is in the topic titled “How to: Manage Object Services Transactions (Entity Framework).”

What If a Relationship, But No Scalar Properties, Changes?

The code in Example 18-22 reassigns a payment to a different reservation.

Example 18-22. Reassigning a payment’s ReservationReference

VB
Dim pmt = context.Payments.FirstOrDefault
pmt.ReservationReference.EntityKey = _
  New EntityKey("BAEntities.Reservations", "ReservationID", 13)
SaveMyChanges(context)
C#
var pmt = context.Payments.FirstOrDefault();
pmt.ReservationReference.EntityKey =
  new EntityKey("BAEntities.Reservations", "ReservationID", 13);
SaveMyChanges(context);

Figure 18-6 shows the ObjectStateEntries before calling SaveChanges. The Payment itself is Unchanged. There is a RelationshipEntry for the original relationship to Reservation 10. That relationship was deleted when the new relationship was added.

State entries when moving a payment from one reservation to another

Figure 18-6. State entries when moving a payment from one reservation to another

The Entity Framework uses the added relationship and the entity that it belongs to (Payment) to build an update command, changing the RelationshipID property in the Payment record.

If a conflict occurs, the exception that results contains these same two entries that were used to build the command, as shown in Figure 18-7.

StateEntries returned by an OptimisticConcurrencyException when editing a relationship

Figure 18-7. StateEntries returned by an OptimisticConcurrencyException when editing a relationship

In a simple Refresh, you only need to refresh the payment; therefore, the existing code that refreshes StateEntries(0) works perfectly.

However, if you need more granular logic, there’s great information to be found in the entries returned by the exception.

Reporting an Exception

Using the details from the exception, you can create a log error or even a message to a user that describes the conflict in detail. The client-side data is readily available to create this report. If you need even more details from the server, you’ll have to hit the server to get details about what actually changed there, although this is not a common scenario.

Reporting the exception could be as simple as alerting the user that there was a conflict when updating this payment.

The EntryDetails extension method, which you can find in the downloads on the book’s website, creates a string of property names and values from a given ObjectStateEntry. The particular method is designed to supply information to an end user, so it doesn’t include EntityKey values or any binary data.

For example, calling this extension method on the Payment entry from the exception shown in Figure 18-7 would return the following:

PaymentDate: 4/6/2008 12:00:00 AM
Amount: 800.0000
ModifiedDate: 5/1/2008 08:23:16 AM

The method is generalized, but you can enhance it even further to fine-tune the details.

If a user modified a variety of data, knowing which specific piece of data was causing the problem could be useful in letting the user decide whether her edits should be sent to the server or whether she would rather have the latest data from the server.

The level of information to access is up to you. Do you want the exception handler to retrieve the current store values as well? Should the user know who made that last change and when? These are common decisions that have to be made for handling concurrent data access, and again, they are not new to the Entity Framework.

Handling Concurrency Exceptions at a Lower Level

Although the generic ClientWins and StoreWins will suffice for many applications, in some applications more granular exception handling is defined. It’s definitely difficult to come up with rules for automating intricate exception handling, but because of the information in the exception, if you do need to go to this level, a lot of possibilities are open to you. The rest of this chapter will explore some more heavy-duty exception handling.

Handling Granular Exceptions Without User Intervention

You may have your own concurrency rules that don’t require a user to get involved. Perhaps for Payment entities, your rule is that if the client is editing the amount, the client’s data should win; otherwise, refresh the payment information from the server. You may decide that the client should update all contact data. You may not even place a concurrency check on the contact for this reason, but you may have a best practice that requires concurrency checks on every entity.

Because the rule in this case is that all contacts should get a ClientWins, it doesn’t make sense to hit them one at a time. So, on the first occurrence of a conflict with a contact, the code will refresh all contacts in the ObjectContext. This will require a little trickery that involves digging through the ObjectStateManager to get a collection of the contacts.

Let’s see what the exception code looks like for these scenarios. First, you can separate the logic for the various types into their own methods, as shown in Example 18-23.

Example 18-23. Subroutines for handling exceptions differently for payments than for contacts

VB
Private Sub PaymentRefresh _
   (ByRef entry As ObjectStateEntry, ByRef context As ObjectContext)
 'rule - if amount was changed locally,
 ' then clientwins, otherwise, storewins
  If entry.GetModifiedProperties.Contains("Amount") Then
    context.Refresh(RefreshMode.ClientWins, entry.Entity)
  Else
    context.Refresh(RefreshMode.StoreWins, entry.Entity)
  End If
End Sub

Private Sub ContactRefresh _
   (ByRef entry As ObjectStateEntry, ByRef context As ObjectContext)
 'Contacts will always have a ClientWins refresh
 'Refresh all of the contacts when the first Contact conflict occurs
  Dim contactsinContext = context.EntitiesFromContext(Of Contact)()
  context.Refresh(RefreshMode.ClientWins, contactsinContext)
End Sub
C#
private void PaymentRefresh(ref ObjectStateEntry entry, ref ObjectContext context)
{
 //rule - if amount was changed locally,
 //then clientwins, otherwise, storewins;
  if (entry.GetModifiedProperties().Contains("Amount"))
    context.Refresh(RefreshMode.ClientWins, entry.Entity);
  else
    context.Refresh(RefreshMode.StoreWins, entry.Entity);
}

private void ContactRefresh(ref ObjectStateEntry entry, ref ObjectContext context)
{
 //Contacts will always have a ClientWins refresh
 //Refresh all of the contacts when the first Contact conflict occurs
  var contactsinContext = context.EntitiesFromContext<Contact>();
  context.Refresh(RefreshMode.ClientWins, contactsinContext);
}

EntitiesFromContext is another custom extension method that creates a list of entities of a particular type by pulling the entries for that type out of the ObjectStateManager and then placing the entity object for each ObjectStateEntry into a List. This way, you’ll have the whole set of contacts that exist in the context. Example 18-24 shows this method, which extends ObjectContext. If during the course of the call to SaveChanges another concurrency conflict arises with a contact, all of the contacts will be refreshed again.

Example 18-24. Extracting entities from a context using an extension method

VB
<Extension()> _
Public Function EntitiesFromContext(Of TEntity)(ByVal context As ObjectContext) _
 As List(Of TEntity)
  Dim entries = context.ObjectStateManager.GetObjectStateEntries(Of TEntity)()
  Dim list As New List(Of TEntity)
  For Each e In entries
    list.Add(CType(e.Entity, TEntity))
  Next
  Return list
End Function
C#
public List<TEntity> EntitiesFromContext<TEntity>(ObjectContext context)
{
  var entries = context.ObjectStateManager.GetObjectStateEntries<TEntity>();
  List<TEntity> list = new List<TEntity>();
  foreach (var e in entries)
  {
    list.Add((TEntity)e.Entity);
  }
  return list;
}

The updated exception code inside SaveMyChanges now farms out the Refresh call to the appropriate method after it tests to be sure the entry is not a relationship, as shown in Example 18-25.

Example 18-25. Updated exception handling for calling subroutines

VB
Dim conflictEntry = ex.StateEntries(0)
If Not conflictEntry.IsRelationship Then
  Dim entryEntityType = conflictEntry.Entity.GetType.Name
  Select Case entryEntityType
    Case GetType(Contact).Name 'this should refresh customers, too
      ContactRefresh(conflictEntry, context)
    Case GetType(Payment).Name
      PaymentRefresh(conflictEntry, context)
    Case Else 'business rule is to ClientWins refresh anything else
       context.Refresh(RefreshMode.ClientWins, conflictEntry)
  End Select
End If
SaveMyChanges(context)
C#
var conflictEntry = ex.StateEntries[0];
if (! conflictEntry.IsRelationship)
{
  var entryEntityType = conflictEntry.Entity.GetType().Name;
  if (entryEntityType == typeof(Contact).Name) //this should refresh customers, too
      ContactRefresh(conflictEntry, context);
  else if (entryEntityType == typeof(Payment).Name)
      PaymentRefresh(conflictEntry, context);
  else
     context.Refresh(RefreshMode.ClientWins, conflictEntry);
}

You can use a lot of variations of this once you’ve gotten into the exception and you know how to drill into the details and make some decisions based on what you’ve found.

Handling Multiple Conflicts

The default method of conflict resolution in the Entity Framework has a few downsides. The first is that none of the data in the context will be saved until every conflict has been resolved. If you are updating a lot of records in a highly concurrent system, your SaveChanges operation may go through many loops before all of the commands execute successfully. The user may or may not notice the delay, but the delay could cause other conflicts.

Another downside is that you can’t easily gather a list of all of the conflicts to present to the user at a later time for resolution. You might want to give the user a list of the conflicts, rather than giving the user one conflict at a time, with no indication of how many more there might be. This is because you have to resolve the first conflict encountered before you can get a report of the next conflict; otherwise, that first conflict will keep coming back.

Separating the good from the bad

One way to set the conflicting entities aside is to remove them from the context and save all of the entities that don’t pose any conflicts. Then, as soon as the save is complete, pull them back into the context in such a way that you can reconstruct their state. This is not a simple task, but you have already learned the necessary steps to pull it off.

When all of the conflicting entries have been removed from the context, SaveChanges will succeed and the other data changes will be applied to the data store. On the book’s website, you can find a PersistedStateEntry class in both VB and C# that achieves this pattern. This class takes advantage of many of the things you learned regarding MetadataWorkspace and ObjectStateManager. It also uses reflection because the ObjectContext (and therefore the ObjectStateManager) are not available for setting properties.

There are two principal functions. The first is to store the state entry information. This is done by storing the main ObjectStateEntry’s EntityKey, original values, and entity in the constructor and then adding the information for each RelationshipEntry that also came back in the StateEntries. The second main function is performed by the NewEntityfromOrig method which reconstructs the object with its state and the EntityReferences that were defined by the RelationshipEntries in the exception’s StateEntries.

Along with the code for the PersistedEntry class on the book’s website, you will find an example of a Save routine that uses the class. Essentially, the routine instantiates a list of PersistedEntry objects, and any time an OptimisticConcurrencyException is encountered, a new PersistedEntry is created from the ObjectStateEntry that caused the problem. Its Entity is detached from the context and added into the list. The method repeats this process until all of the exceptions are encountered, and then on a final call to SaveChanges, the valid updates are persisted to the database. The entities that were persisted are reconstructed and reattached to the context along with their relationship information. All of the information about the entity along with the exception’s message and any inner exception information are available from the PersistedEntry class for building an informational UI for the end user, for logging the problems, or any other task you may want to perform as part of your exception handling.

This is one pattern for separating conflicting data from good data that not only allows you to get the good data into the database more quickly, but also provides you an opportunity to present all of the conflicts to a user at once.

Handling Exceptions When Transactions Are Your Own

When you allow the Entity Framework to provide its default transactions, rollbacks and commits will occur automatically. In addition, ObjectContext.AcceptChanges will be called at the end of a successful SaveChanges so that the state of the entities becomes Unchanged.

If, however, you are using your own transactions as described in Chapter 16, you will need to roll back and commit the transactions yourself depending on the success or failure of the call to SaveChanges.

Depending on your application architecture and business rules, you may even choose to commit changes that have already been sent to the database, rather than rolling them back. You will also need to call AcceptAllChanges manually when the commands are completed successfully. Example 18-26 shows a basic pattern for using your own transaction with an OptimisticConcurrencyException.

Example 18-26. Handling an exception in a manual transaction

VB
Using tran As New System.Transactions.TransactionScope
  Try
    context.SaveChanges()
    tran.Complete()
    context.AcceptAllChanges()
  Catch ex As OptimisticConcurrencyException
    'TODO: add code for handling exception
    context.SaveChanges()
  End Try
End Using
C#
using (var tran = new System.Transactions.TransactionScope())
{
  try
  {
    context.SaveChanges();
    tran.Complete();
    context.AcceptAllChanges();
  }
  catch (OptimisticConcurrencyException ex)
  {
    //TODO: add code for handling exception
    context.SaveChanges();
  }
}

Although Complete and AcceptAllChanges won’t be executed anytime an exception is thrown, you still may want to separate those calls from the SaveChanges loop.

Example 18-27 shows a pattern that allows you to shift the location of some of the logic.

Example 18-27. Moving the transaction completion into a Finally clause

VB
Dim ChangesSaved as Boolean
Using tran As New System.Transactions.TransactionScope
  Try
    context.SaveChanges()
    ChangesSaved=True
  Catch ex As OptimisticConcurrencyException
    'add code for handling exception
    context.SaveChanges()
  Finally
    If ChangesSaved Then
      tran.Complete()
      context.AcceptAllChanges()
    End If
  End Try
 End Using
C#
bool ChangesSaved = false;
using (var tran = new System.Transactions.TransactionScope())
{
  try
  {
    context.SaveChanges();
    ChangesSaved = true;
  }
  catch (OptimisticConcurrencyException ex)
  {
    //TODO: add code for handling exception
    context.SaveChanges();
  }
  finally
  {
    if (ChangesSaved)
    {
      tran.Complete();
      context.AcceptAllChanges();
    }
  }
}

Summary

In this chapter, you saw that there are many opportunities for exceptions to be thrown when querying or updating entities. You’ll need to catch these exceptions and do something about them, or you will have some very unhappy end users.

The patterns in the chapter concentrated on handling the exceptions within the code where they occurred. Another common pattern is to raise exceptions to a common ExceptionHandler that you can use throughout your application. This is not specific to the Entity Framework, and you can find plenty of guidance on .NET exception handling in the documentation, articles, and other books that focus on handling exceptions.

Rules for handling concurrency problems vary among enterprises and applications. Because it is difficult to even come up with rules for resolving these issues at a granular level, you’ll find that most commonly, the three sweeping solutions—client always wins with no concurrency checks, client wins with a complete replacement of the server data, and server wins with a complete replacement of the client data—are the ones chosen.

But you do have some options for handling exceptions in a more detailed way, and hopefully you’ll find the patterns that I laid out in the final pages of the chapter both interesting and useful.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.94.190