Entity Framework - a concurrency manager

by Rudi16. December 2008 21:22

Dedicated concurrency properties 

Entity Framework offers support for optimistic locking through the ability to mark the concurrency mode of properties as "fixed".

Within Entity Framework, you can use this feature in two ways:

1. Mark (almost) all properties as having "fixed" concurrency mode. This will ensure that all properties will have their original values included in the WHERE clause of the UPDATE statement.

2. Mark a dedicated property as having "fixed" concurrency mode. This property could for instance be version number, or a datetime of last change.

The second approach requires specific support from the persistence layer. Hereby I present a solution for automating the update of dedicated concurrency properties with Entity Framework.

Sample case

Step 1 : Defining dedicated concurrency properties

Let's take a sample case. In an invoicing application, I define a "LastChanged" property on each of my entities:

 

Within the properties of the LastChanged property, the Concurrency Mode is set to "Fixed": 

Now, on the one hand the Entity Framework API does not provide at runtime information as to which properties have fixed concurrency mode (at least, I couldn't find it). On the other hand, such information would not sufice: to update the value of the dedicated concurrency property we need additional information:

Is it a dedicated concurrency property ?

What is the type of the property ?

What algorithm to use to compute it's next value ?

To provide this additional information, I introduced an OptimisticConcurrencyAttribute. This attribute decorates the entity type, tells which property is the dedicated concurrency property (usually only one, but multiple are supported) and which class to use to compute it's next value. As the EDM Designer generates entity classes that are partial, we can easily create a new part to decorate the entity type without changing the code generated by the EDM designer (and jeopardizing a regeneration by the EDM designer). For instance, I can create the following partial class:

namespace CodeTuning.Sample.Invoicing
{
[OptimisticConcurrency("LastChanged", typeof(LocalDateTimeConcurrencyResolver))]
partial class Customer
{
}
}

The LocalDateTimeConcurrencyResolver here referenced is one of three ConcurrencyResolvers pre-implemented:

LocalDateTimeConcurrencyResolver : for use with local DateTime typed dedicated concurrency properties.

UniversalDateTimeConcurrencyResolver : for use with universal DateTime typed dedicated concurrency properties.

VersionNumberConcurrencyResolver : for use with Int64 typed dedicated concurrency properties.

You can easily provide your own concurrency resolver type by implementing the following interface:

public interface IConcurrencyResolver<T>
{
T NextValue(T actualValue);
}

Step 2 : Extending the ObjectContext

The second step is to extend the ObjectContext with the functionality to update dedicated concurrency properties on modified entities.

This is the task of the OptimisticConcurrencyManager class. Simply create an instance of it, and attach it to the context that will perform the SaveAllChanges() operation. For instance:

using (var context = new InvoicingContext())
{
// Attach an OptimisticConcurrencyManager on the context:
new OptimisticConcurrencyManager(context);
...
// Save changes:
context.SaveAllChanges();
}

However, the backdraw of this is that you need to attach the concurrency manager each time you create a context. I prefer to extend the context itself to have a concurrency manager attached in the following way: create a part of the partial class that the EDM designer generated, and implement the OnContextCreated partial method to create and attach an OptimisticConcurrencyManager:

namespace CodeTuning.Sample.Invoicing
{
partial class InvoicingContext
{
partial void OnContextCreated()
{
// Automatically attach an OptimisticConcurrencyManager on each new context:
new OptimisticConcurrencyManager(this);
}
}
}

That's all there is to do. The OptimisticConcurrencyManager registers to the context's WhenSavingChanges event, then queries the ObjectStateManager of the context and updates the dedicated concurrency properties of all modified entities.

If the dedicated concurrency property was modified on the entity, or by a concurrent transaction on the database, an OptimisticConcurrencyException will be raised. Otherwise, the dedicated concurrency property is updated on the entity, then checked and saved on database.

The code

The framework to support automated concurrency property updating, consists of the following code:

The IConcurrencyResolver interface:

namespace CodeTuning.Data.Entity
{
/// <summary>
/// Concurrency Resolver definition.
/// </summary>
/// <typeparam name="T">Type of the optimistic concurrency property</typeparam>
public interface IConcurrencyResolver<T>
{
/// <summary>
/// Provide the next value of the optimistic locking field, given
/// it's actual value.
/// </summary>
T NextValue(T actualValue);
}
}

Default implementations of the IConcurrencyResolver interface:

using System;
namespace CodeTuning.Data.Entity
{
/// <summary>
/// A ConcurrencyResolver for DateTime? properties containing LocalDateTime
/// of last change.
/// </summary>
public class LocalDateTimeConcurrencyResolver : IConcurrencyResolver<DateTime?>
{
DateTime? IConcurrencyResolver<DateTime?>.NextValue(DateTime? actualValue)
{
return DateTime.Now;
}
}
/// <summary>
/// A ConcurrencyResolver for DateTime? properties containing UniversalDateTime
/// of last change.
/// </summary>
public class UniversalDateTimeConcurrencyResolver : IConcurrencyResolver<DateTime?>
{
DateTime? IConcurrencyResolver<DateTime?>.NextValue(DateTime? actualValue)
{
return DateTime.UtcNow;
}
}
/// <summary>
/// A ConcurrencyResolver for Int64 properties containing a sequence number
/// of last change.
/// </summary>
public class VersionNumberConcurrencyResolver : IConcurrencyResolver<long>
{
long IConcurrencyResolver<long>.NextValue(long actualValue)
{
return actualValue++;
}
}
}

The OptimisticConcurrencyAttribute class:

using System;
using System.Data.Objects;
using System.Linq;
using System.Reflection;
namespace CodeTuning.Data.Entity
{
/// <summary>
/// Declares a property for optimistic concurrency.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class OptimisticConcurrencyAttribute : Attribute
{
private string propertyName;
private Type concurrencyResolverType;
private object concurrencyResolver;
/// <summary>
/// Declares the optimistic concurrency attribute and its resolvertype.
/// </summary>
public OptimisticConcurrencyAttribute(string propertyName, Type concurrencyResolverType)
{
this.propertyName = propertyName;
this.concurrencyResolverType = concurrencyResolverType;
this.concurrencyResolver = Activator.CreateInstance(concurrencyResolverType);
}
/// <summary>
/// Name of the concurrency property.
/// </summary>
public string PropertyName
{
get { return this.propertyName; }
set { this.propertyName = value; }
}
/// <summary>
/// Concurrency property value resolver type.
/// </summary>
public Type ConcurrencyResolverType
{
get { return this.concurrencyResolverType; }
set { this.concurrencyResolverType = value; }
}
/// <summary>
/// Whether the optimistic concurrency property has changed on the given instance.
/// </summary>
public bool HasPropertyChanged(ObjectContext context, object instance)
{
return context.ObjectStateManager.GetObjectStateEntry(instance).GetModifiedProperties().Contains(this.propertyName);
}
/// <summary>
/// Updates the concurrency property on the given instance.
/// </summary>
public void UpdateInstance(object instance)
{
// Retrieve the property instance:
PropertyInfo property = instance.GetType().GetProperty(this.propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (property == null) throw new ArgumentException(String.Format("ConcurrencyAttribute's PropertyName \"{0}\" not found."));
// Invoke the NextValue method on the concurrency resolver, given the actual property value:
object actualValue = property.GetValue(instance, null);
Type iftype = typeof(IConcurrencyResolver<>).MakeGenericType(property.PropertyType);
object nextValue = this.concurrencyResolverType.GetInterfaceMap(iftype).TargetMethods[0].Invoke(this.concurrencyResolver, new object[1] { actualValue });
// Assign NextValue:
property.SetValue(instance, nextValue, null);
}
/// <summary>
/// Retrieves the OptimisticConcurrencyAttributes decorating the given entity type.
/// </summary>
public static OptimisticConcurrencyAttribute[] GetConcurrencyAttributes(Type entityType)
{
return (OptimisticConcurrencyAttribute[])entityType.GetCustomAttributes(typeof(OptimisticConcurrencyAttribute), true);
}
}
}

And finally the OptimisticConcurrencyManager class:

using System;
using System.Data;
using System.Data.Objects;
namespace CodeTuning.Data.Entity
{
/// <summary>
/// An Entity Framework addition to manage optimistic
/// concurrency using OptimisticConcurrencyAttributes.
/// </summary>
public class OptimisticConcurrencyManager : IDisposable
{
private ObjectContext context;
/// <summary>
/// Instantiates a new OptimisticConcurrencyManager for the
/// given ObjectContext.
/// </summary>
public OptimisticConcurrencyManager(ObjectContext context)
{
this.context = context;
this.context.SavingChanges += new EventHandler(WhenSavingChanges);
}
/// <summary>
/// Disposes the OptimisticConcurrencyManager, releasing it
/// from the ObjectContext.
/// </summary>
public void Dispose()
{
if (this.context != null)
{
this.context.SavingChanges -= new EventHandler(WhenSavingChanges);
this.context = null;
}
}
/// <summary>
/// Triggered when the attached ObjectContext saves changes.
/// Changes optimistic concurrency attribute values.
/// </summary>
void WhenSavingChanges(object sender, EventArgs e)
{
// Update the concurrency properties of modified entities:
foreach (var item in this.context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified))
{
object entity = item.Entity;
foreach (var attr in OptimisticConcurrencyAttribute.GetConcurrencyAttributes(entity.GetType()))
{
// Verify the property was not yet updated, which could
// indicate an optimistic concurrency violation:
if (attr.HasPropertyChanged(this.context, entity))
throw new OptimisticConcurrencyException(String.Format("Concurrency property {0}.{1} contains invalid update.", entity.GetType(), attr.PropertyName));
attr.UpdateInstance(entity);
}
}
}
}
}

Tags:

Entity Framework

Pingbacks and trackbacks (2)+

Comments are closed

About me

Widget Month List not found.

The file '/blog/widgets/Month List/widget.ascx' does not exist.X

Widget Page List not found.

The file '/blog/widgets/Page List/widget.ascx' does not exist.X