
It has come to my attention that a lot of people who use WCF RIA Services and the Entity Framework, really don’t know how to deal with optimistic concurrency, both on the client side and the server side. This doesn’t come as a surprise, because dealing with this requires some deep knowledge of WCF RIA Services and the Entity Framework. Especially optimistic concurrency in WCF RIA Services is badly documented. In this article I will show you how to deal with optimistic concurrency in the Entity Framework and WCF RIA Services, in the most elaborate way.
The Entity Data Model
Let’s start with the entity data model. For this example I’ll use a simple model, displayed below:
I’m using the Product class from the Adventureworks2008 database. I’ve added an extra property, namely the “Version” property. This is a timestamp in the database. To enable optimistic concurrency I’ve changed the “ConcurrencyMode” property of the “Version” property to fixed:
The Product class itself is generated by the T4 Poco template, downloadable through the extension manager. This means that this class doesn’t contain any Entity Framework related stuff. I’ve also created a metadata class for it, so that the Product class can be used with WCF RIA Services:
1: [MetadataType(typeof(ProductMetadata))]
2: public partial class Product
3: {
4: private class ProductMetadata
5: {
6: [Key]
7: public int ProductID { get; set; }
8: [Range(0,Double.MaxValue)]
9: public decimal ListPrice { get; set; }
10: [ConcurrencyCheck]
11: public byte[] Version { get; set; }
12: }
13: }
You should notice the [ConcurrencyCheck] attribute above the “Version” property in the metadata class. This makes sure that the original version of the “Version” property get’s roundtripped to the server, each time an update of a Product is required.
The DomainService
Next up is the DomainService, which is of course, the most interesting part of the solution:
1:
2: namespace SilverlightApplication1.Web
3: {
4: using System;
5: using System.Collections.Generic;
6: using System.ComponentModel;
7: using System.ComponentModel.DataAnnotations;
8: using System.Linq;
9: using System.ServiceModel.DomainServices.Hosting;
10: using System.ServiceModel.DomainServices.Server;
11: using System.Data;
12: using System.Data.Objects;
13: using System.Reflection;
14:
15:
16:
17: [EnableClientAccess()]
18: public class AdventureworksDomainService : DomainService
19: {
20: private AdventureWorks2008Entities _ents;
21:
22: public AdventureworksDomainService ()
23: {
24:
25: }
26:
27: protected override void Dispose(bool disposing)
28: {
29: base.Dispose(disposing);
30: if(disposing)
31: {
32: _ents.Dispose();
33: }
34: }
35:
36: public override void Initialize(DomainServiceContext context)
37: {
38: base.Initialize(context);
39: _ents = new AdventureWorks2008Entities();
40: _ents.ContextOptions.LazyLoadingEnabled = false;
41: _ents.ContextOptions.ProxyCreationEnabled = false;
42: _ents.Product.MergeOption = System.Data.Objects.MergeOption.NoTracking;
43: }
44:
45: public Product[] GetProducts()
46: {
47: return _ents.Product.ToArray();
48: }
49:
50: public void UpdateProduct(Product toUpdate)
51: {
52: ChangeSetEntry entry = ChangeSet.ChangeSetEntries.Single((ce) => ce.Entity == toUpdate);
53: Product original = (Product) entry.OriginalEntity;
54: //Set the original version back....
55: toUpdate.Version = original.Version;
56: _ents.Product.Attach(toUpdate);
57: _ents.ObjectStateManager.ChangeObjectState(toUpdate, EntityState.Modified);
58: }
59:
60: protected override bool PersistChangeSet()
61: {
62: try
63: {
64: _ents.SaveChanges();
65: return true;
66: }
67: catch (OptimisticConcurrencyException ex)
68: {
69: ObjectStateEntry entry = ex.StateEntries.Single();
70: string setName = entry.EntitySet.Name;
71: object current = entry.Entity;
72: ChangeSetEntry changeEntry = ChangeSet.ChangeSetEntries.Single((ce) => ce.Entity == current);
73: EntityKey key = entry.EntityKey;
74: _ents.Detach(current);
75: object inDatabase;
76: if (_ents.TryGetObjectByKey(key, out inDatabase))
77: {
78: _ents.ApplyOriginalValues(setName, current);
79: changeEntry.ConflictMembers = _ents.ObjectStateManager.GetObjectStateEntry(inDatabase).GetModifiedProperties().ToArray();
80: changeEntry.StoreEntity = inDatabase;
81: }
82: else
83: {
84: //Deleted in the meantime.....
85: changeEntry.IsDeleteConflict = true;
86: }
87: //return false, since none of the entities were updated....;
88: return false;
89:
90: }
91: }
92:
93: }
94: }
95:
96:
The first thing you should notice is on line 18. You can see that I’m not inheriting from the LinqToEntitiesDomainService class. This is because this class hides way to much implementation details, especially when dealing with optimistic concurrency. Next up I will explain the methods in detail:
- Line 27: An override of the Dispose() method, which makes sure that the ObjectContext is neatly disposed of when all is said and done.
- Line 36: An override of the Initialize() method. My ObjectContext is instantiated here. To improve performance, I’m disabling all things that won’t matter or I don’t need in a stateless / service environment.
- Line 45: GetProducts(), this was just a quick method to get some data to the client. I know, I know, I should have made the return type IQueryable to enable further filtering on the client, again, this method isn’t what matters the most.
- Line 50: The UpdateProduct method. This method get’s called for each Product that need’s to be updated. I’m retrieving the original value for the “Version” property, just to make sure that I don’t get a modified one from the client. After that, I’m attaching the Product and changing it’s state to “Modified”. This is necessary, as the Entity Framework won’t know that it needs to send an update statement if I don’t change it’s state. Notice that I’m not calling SaveChanges() on the ObjectContext in this method. RIA Services has a special method just for persisting the changes.
- Line 60: An override of the PersistChangeSet() method. This method is especially designed in WCF RIA Services for persisting the changes, so this is the ideal place to call SaveChanges() on the ObjectContext. Notice how I catch the OptimisticConcurrencyException, which get’s automatically thrown by the Entity Framework if the “Version” column changed in the database. This is where the real magic happens:
- On line 69 I’m retrieving the ObjectStateEntry of the entity which has caused the conflict. This entry contains vital information that I will need later on.
- On line 70 and 71 I’m retrieving this vital information. I want a generic solution that works for every entity type, so I’m typing the entity as object in the variable “current” and I’m retrieving the entityset name. This name is required by the Entity Framework in a lot of situations when dealing with entities of type “object”.
- On line 72 I’m retrieving the ChangeSetEntry belonging to the entity which has caused the conflict. The ChangeSetEntry is something provided by WCF RIA Services, which you can use to retrieve original values and to indicate server side errors for an entity.
- On line 73 I’m retrieving the EntityKey. I need this to retrieve the database version of the entity in a generic way.
- On line 74 I’m detaching the current entity. This is a very important step; The Entity Framework won’t give me the database version of an entity as long as it has the “same” entity in memory.
- On line 75 and 76 I’m trying to retrieve the database version. I deliberately say “trying”, since an OptimisticConcurrencyException can also mean that the entity was deleted in the mean time.
- On line 78 I’m applying a little trick. I need to indicate the properties that are in conflict to WCF RIA Services. I use the ApplyOriginalValues() to change the original values of the entity just retrieved from the database to the values that are present in the entity that caused the conflict. This may seem a bit strange, but by doing this, I can ask the Entity Framework on line 79 which properties have changed. I’m putting the changed properties (Which are a string[]) in the ConflictMembers property of the ChangeSetEntry. This is the essential step that let’s WCF RIA Services know that an optimistic concurrency error has occurred.
You might have asked yourself: “Could I have also used the ApplyCurrentValues() method instead of the ApplyOriginalValues() method?”. The answer would be: “Yes but……”. I could have used the “ApplyCurrentValues()” method followed by the “GetModifiedProperties()” method to retrieve which properties have changed. But then I wouldn’t be able to perform the code on line 80. By putting the database entity in the StoreEntity property of the ChangeSetEntry, WCF RIA services will provide the database values to the client. If I had used “ApplyCurrentValues()”, these properties would have the same values as the properties in the entity that caused the conflict. By using “ApplyOriginalValues()” I can still let the Entity Framework figure out what has changed and I can send the current database values back to the client…. Let us continue 🙂
- Line 83-86: If the Entity Framework couldn’t retrieve a database version, it means that the record was deleted in the mean time. This is supported by WCF RIA Services and you can just set the “IsDeleteConflict” property to “true” on the ChangeSetEntry.
- On line 88 I’m returning false, this indicates to RIA Services that none of the changes have been persisted. This is the case when saving multiple objects with the Entity Framework and one of them causes an exception.
The Silverlight Client
Next up is the code of the Silverlight client:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Net;
5: using System.Windows;
6: using System.Windows.Controls;
7: using System.Windows.Documents;
8: using System.Windows.Input;
9: using System.Windows.Media;
10: using System.Windows.Media.Animation;
11: using System.Windows.Shapes;
12: using SilverlightApplication1.Web;
13: using System.ServiceModel.DomainServices.Client;
14:
15: namespace SilverlightApplication1
16: {
17: public partial class MainPage : UserControl
18: {
19:
20: private AdventureworksDomainContext _context;
21:
22: public MainPage()
23: {
24: InitializeComponent();
25: _context = new AdventureworksDomainContext();
26: LoadOperation<Product> op = _context.Load(_context.GetProductsQuery());
27: op.Completed += new EventHandler(op_Completed);
28: }
29:
30: void op_Completed(object sender, EventArgs e)
31: {
32: LoadOperation<Product> op = (LoadOperation<Product>)sender;
33: _dtgProducts.ItemsSource = op.Entities;
34: }
35:
36: void submitop_Completed(object sender, EventArgs e)
37: {
38: SubmitOperation op = (SubmitOperation)sender;
39: if (op.HasError)
40: {
41: if (op.Error is DomainOperationException)
42: {
43: DomainOperationException ex = (DomainOperationException)op.Error;
44: if (ex.Status == OperationErrorStatus.Conflicts)
45: {
46: foreach (Entity item in op.EntitiesInError)
47: {
48: //Client wins all.......
49: if (!item.EntityConflict.IsDeleted)
50: {
51: item.EntityConflict.Resolve();
52: }
53: }
54: }
55: }
56: op.MarkErrorAsHandled();
57: MessageBox.Show("Clients has won all concurrency errors.");
58: }
59: else
60: {
61: MessageBox.Show("All updates were saved succesfully");
62: }
63: }
64:
65: private void Button_Click(object sender, RoutedEventArgs e)
66: {
67: SubmitOperation op = _context.SubmitChanges();
68: op.Completed += new EventHandler(submitop_Completed);
69: }
70: }
71: }
The real magic starts on line 36:
- On lines 38-42 I’m checking whether an error has occurred and if the error is a DomainOperationException.
- If it is a DomainOperationException, I’m checking it’s status property on line 44. Possible values could be: “ValidationFailed”, “ServerError” etc. “Conflicts” mean that an optimistic concurrency error has occurred.
- On line 46 I’m iterating over the EntitiesInError property. Technically, a foreach isn’t required. If you use the Entity framework as described above, there is always one Entity that causes an OptimisticConcurrencyException. On line 51 I call the Resolve() method. This method means that all the values that were in the database that I sent back to the client, will become the original values of the entity. This means that when the entities are saved again, The Version property will match the Version in the database and the current values will overwrite the values in the database.
Of course, a simple “Client wins” scenario could have been implemented completely on the server. But the real beauty of the code above comes if you use the “item.EntityConflict.StoreEntity” property. Let’s take a look at it with the debugger:
That’s right!!! All the values that were in the database have been neatly transferred over to the client. After you’ve called “Resolve()”, you can easily use this information to provide a dialog to the user and let the user merge the values. This simply means copying over values from the StoreEntity to the current entity. By calling SubmitChanges() you can persist the manual merge in the database.
Concluding
I hope this article proves to you that WCF RIA Services has excellent support for optimistic concurrency, even the most advanced scenario’s. Why this isn’t better documented beats me……You can find the sample code here.