Practical .NET

Handling Record Contention in Code-First Entity Framework

Peter returns to the topic of managing multiple users accessing the same row in a table using Entity Framework, but this time using code-first development. There are some unexpected issues to deal with.

This column is really a follow-up to one that I did in 2009. In that column, I discussed strategies for handling two users updating the same row in a table when using Entity Framework (one strategy is "Don't worry about it"). I recommended setting the ConcurrencyMode property on the entity property that corresponds to a table's timestamp column to Fixed. I still think that's a great answer, but it only applies in database-first or model-first development.

In code-first development, you achieve the same effect by adding the ConcurrencyCheck attribute to one or more properties in your business entity class. In the following code, for instance, I've got a Customer class with a column called TimeStamp that, presumably, maps to a timestamp or rowversion column on the Customer table in the database. In order to keep Entity Framework (EF) from trying to update the TimeStamp column, I also decorate it with the TimeStamp attribute:

Public Class Customer
  Id As String
  FirstName As String
  LastName As String
  <ConcurrencyCheck>
  <TimeStamp>
  TimeStamp As Byte()
End Class

Now, when I save changes to the class, if the TimeStamp column in the table has been changed since I retrieved the row, I'll get a DbUpdateConcurrencyException. When that exception is raised, I update the Customer object with the current data from the database so that I can show it to the user and they can retry their changes:

Dim saved As Boolean
Dim cust As Customer
Dim dbc As New CustomerContext
custExisting = dbc.Customers.Single(Function(c) c.Id = "A123")

...update the entity object...

Try
  dbc.SaveChanges();
catch dbuEx as DbUpdateConcurrencyException
  dbc.Entry(custExisting).Reload()
End Try
...redisplay entity...

Managing the Concurrency Token
Technically, this timestamp is acting as what EF calls a "concurrency token." Under the hood, when it comes time to do the updates, EF uses the original value retrieved with the row to see if the row has been changed since the row was retrieved. For this to work, however, the entity that I update before calling SaveChanges must hold the timestamp value that I got when I first retrieved the Customer object. My previous code -- really, only appropriate for a desktop application -- ignores some issues by keeping the retrieved entity object in memory from retrieval to update. In an ASP.NET application (Web Forms, MVC or Web API) or a Windows Communication Foundation (WCF) service, the problem is more complicated.

If you're retrieving a Customer object and moving its data to a Data Transfer Object before sending that DTO to the presentation layer, you must move the TimeStamp column into the DTO also. In an ASP.NET MVC project, you'll need to store the value in a hidden field. In an ASP.NET Web Forms project, you can put the value in a hidden control or the ViewState. In a service, you'll need to send the TimeStamp column along with the rest of the data and tell the client not to modify the value.

When the DTO comes back from the presentation layer, you'll need to re-fetch the entity object from the database and update it with the values in the DTO before calling SaveChanges. As part of that, you must move the timestamp value into the entity. This ensures that the ConcurrencyCheck column has the value from when the entity object was first retrieved.

There's a wrinkle here, though: Updating the TimeStamp column, even if you update it with the same value that it currently has in the database, causes EF to flag the entity as having been changed. That can cause SaveChanges to generate unnecessary trips to the database. So, when you update the entity object, you'll want to skip updating the TimeStamp column until you've determined that there's been some change to the entity.

You can build in your own mechanism for checking for changes or, if you're using EF 5.0, you can use EF's ChangeTracker object. When writing your own assignment statements, that just means that you check the HasChanges property on the ChangeTracker object on the DbContext object before updating the TimeStamp column on the entity, using code like this:

custExisting.FirstName = custDTO.FirstName
custExisting.LastName = custDTO.LastName
If dbc.ChangeTracker.HasChanges Then
   custExisting.TimeStamp = custDTO.TimeStamp

If, as I do, you're using AutoMapper to move your data around, then you'll use code like this to have AutoMapper skip moving the TimeStamp column and do it yourself after checking to see if there were any changes:

Mapper.CreateMap(Of custDTO, Customer).
          ForMember("TimeStamp", Sub(b) b.Ignore())
custExisting = Mapper.Map(Of custDTO, Customer)(custIn, custExisting)
If dbc.ChangeTracker.HasChanges Then
  custExisting.TimeStamp = custIn.Timestamp

I should point out that using ChangeTracker's HasChanges property isn't free -- the property does trigger some trips to the database. If you're confident that you can live with the odd "unnecessary" update, you may want to consider skipping checking the HasChanges property on the basis that the odd extra update will be less expensive than constantly reading the HasChanges property.

That's a lot of words when all you need are two attributes on one of your entity properties (TimeStamp and ConcurrencyCheck) and a little code in your data access layer to manage that property. And if even that sounds like too much work, there's always the "Don't worry about it" option.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Microsoft's Tools to Fight Solorigate Attack Are Now Open Source

    Microsoft open sourced homegrown tools it used to check its systems for code related to the recent massive breach of supply chains that the company has named Solorigate.

  • Microsoft's Lander on Blazor Desktop: 'I Don't See a Grand Unified App Model in the Future'

    For all of the talk of unifying the disparate ecosystem of Microsoft-centric developer tooling -- using one framework for apps of all types on all platforms -- Blazor Desktop is not the answer. There isn't one.

  • Firm Automates Legacy Web Forms-to-ASP.NET Core Conversions

    Migration technology uses the Angular web framework and Progress Kendo UI user interface elements to convert ASP.NET Web Forms client code to HTML and CSS, with application business logic converted automatically to ASP.NET Core.

  • New TypeScript 4.2 Tweaks Include Project Explainer

    Microsoft shipped TypeScript 4.2 -- the regular quarterly update to the open source programming language that improves JavaScript with static types -- with a host of tweaks including a way to explain why files are included in a project.

Upcoming Events