How to handle concurrency in Entity Framework
This is going to be the fifth post of a series of posts regarding ASP.Net and the Entity Framework and how we can use Entity Framework to access our datastore. You can find the first one here, the second one here and the third one here. You can read the fourth one here. I have a post regarding ASP.Net and EntityDataSource. You can read it here.I have 3 more posts on Profiling Entity Framework applications. You can have a look at them here, here and here. In this post I will be looking into the issue of concurrency and how Entity Framework manages concurrency I will be using Entity Framework version 4.0 that ships with Visual Studio 2010 and .Net 4.0. I assume that you have access to a version of SQL Server and AdventureWorkLT database. If you do not, you can download and install the free SQL Server Express edition from here. If you need the installation scripts for the sample AdventureWorkLT database, click here 1) Launch Visual Studio 2010 (express edition will work fine). Create a new empty website and choose a suitable name for it. Choose C# as the development language. 2) Add a new item to your site, a web form. Leave the default name, Default.aspx 3) Add a new project to your solution, a class library project.Remove the class1.cs file from the project. 4) Add a new item to your class library project, a ADO.Net Entity Data model. Choose a suitable name for it, e.g AdventureWorkLT.edmx. 5) Then the Wizard pops up. Choose "Generate from Database". Follow the steps (5-9) in this post to complete the generation of the entity data model. 6) When we are talking about concurrency in an application where EF acts as the data access layer, our main focus is how to make sure that the integrity of the data in the database is ensured in a multi user connected environment. Databases are very good in managing concurrent users. All the objects/entities are retrieved from the datastore into the memory. The changes we make are applied in the memory and then we flush the changes to the database. But what happens if those entities have been changed from another user in the time it took us to update them in memory?Well someone will say, "Dude, you just have to use pessimistic concurrency and your problems are solved." Well, I do not want to go down that road. Locks are applied to all related data when I use this type of concurrency management. Remember we are in a multiuser enviroment. That means I want to have data integrity on the one hand but I also want performance to be at an acceptable level. With pessimistic concurrency you limit scalability and performance. So I will use optimistic concurrency which in plain words mean "Hm... I want to check if anyone has modified the data in the database while I did my own modifications in memory". Out of the box , EF does not use any concurrency mechanism. It is off by default. That is exactly the opposite of LINQ to SQL where optimistic concurrency is on by default. There is an attribute for every property of an entity that is called Concurrency Mode. if you go to the EDM designer and select the Customer entity and then e.g the NameStyle property and then hit F4 from the keyboard the Properties window will become active. There you can set the Concurrency Mode to Fixed. Go to the EDM designer and locate the ModifiedDate property for the Customer entity.Set the Concurrency Mode to Fixed. Have a look at the picture below to see what I mean. 7) In order to test that optimistic concurrency works as expected I will write some code in the Page_Load event handling routine of the Default.aspx page. I will create two instances of the ObjectContext class, in our case it is AdventureWorksLTEntities . Then I will try to modify the value for the same property of the object/entity which has CustomerID=3 and see how EF handles concurrency. In the Page_Load event handling routine, type In those lines AdventureWorksLTEntities ctx = new AdventureWorksLTEntities(); I create two instances of the AdventureWorksLTEntities and store them in 2 different objects. Then I create 2 more variables that store the exact same entity object in them. In those lines try I update the value for the first customer. That will work fine. Then when you move on with the code execution and reach the second , context.SaveChanges(); ,your code will jump inside catch statement. When this happens I know that someone else has modified ModifiedDate value for the customer object with CustomerID = 3.So I need to refresh the data from the database for that object/record and then apply the new changes and that is exactly what I do inside the catch block. Run your application (If I were you i would place a breakpoint in the first line of the code and execute it step by step) and see the results. You can have SQL Profiler open and see the SQL statements executed and you can query the table Customer as well to see when actually the data changes. Drop me an email if you want the source code. Hope it helps!!!
AdventureWorksLTEntities context = new AdventureWorksLTEntities();
var mycustomer = ctx.Customers.Where(cust => cust.CustomerID == 3).First();
var othercustomer = context.Customers.Where(cust => cust.CustomerID == 3).First();
try
{
mycustomer.ModifiedDate = DateTime.Now.AddDays(1);
ctx.SaveChanges();
othercustomer.ModifiedDate = DateTime.Now.AddDays(100);
context.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, othercustomer);
othercustomer.ModifiedDate = DateTime.Now.AddDays(100);
context.SaveChanges();
}
AdventureWorksLTEntities context = new AdventureWorksLTEntities();
var mycustomer = ctx.Customers.Where(cust => cust.CustomerID == 3).First();
var othercustomer = context.Customers.Where(cust => cust.CustomerID == 3).First();
{
mycustomer.ModifiedDate = DateTime.Now.AddDays(1);
ctx.SaveChanges();
othercustomer.ModifiedDate = DateTime.Now.AddDays(100);
context.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, othercustomer);
othercustomer.ModifiedDate = DateTime.Now.AddDays(100);
context.SaveChanges();
}