Implementing optimistic concurrency

In this recipe, we are going to set up the Entity Framework to use a column for optimistic concurrency, so that multiple contexts do not over run each other without us knowing about it.

Getting ready

We will be using the NuGet Package Manager to install the Entity Framework 4.1 assemblies.

The package installer can be found at http://nuget.org.

We will also be using a database for connecting to the data and updating it.

Open the Improving Optimistic Concurrency solution in the included source code examples.

How to do it...

  1. We start by defining a new C# Test class named ConcurrencyTests in the test project by which we will set up our scope of changes, and make sure that we get the expected results. We are going to set up one instance to which we want to throw a concurrency exception, and one to which we don't want to throw a concurrency exception, with the following code:
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using DataAccess;
    using DataAccess.Database;
    using DataAccess.Queries;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Test.Properties;
    using System.Data.Entity.Infrastructure;
    namespace Test
    {
      [TestClass]
      public class ConcurrencyTests
      {
        [TestMethod]
        [ExpectedException(typeof(DbUpdateConcurrencyException))]
        public void ShouldGiveConcurrencyErrorOnSecondUpdate()
        {
          //Arrange
          BlogContext blogContext = new BlogContext(Settings.Default.BlogConnection);
          Database.SetInitializer(new Initializer());
          blogContext.Database.Initialize(true);
    
          var repository = new BlogRepository(blogContext);
          var item = repository.Set<Blog>().First(b => b.Id ==1);
          item.Title = "Test Change";
    
          var repository2 = new BlogRepository(new BlogContext(Settings.Default.BlogConnection));
          varitem2 = repository2.Set<Blog>().First(b => b.Id == 1);
          item2.Title = "Testing Change";
    
          //Act
          repository2.SaveChanges();
          repository.SaveChanges();
        }
    
        [TestMethod]
        public void ShouldNotGiveConcurrencyErrorOnSecondUpdate()
        {
          //Arrange
          BlogContext blogContext = new BlogContext(Settings.Default.BlogConnection);
          Database.SetInitializer(new Initializer());
          blogContext.Database.Initialize(true);
          var repository = new BlogRepository(blogContext);
          var repository2 = new BlogRepository(new BlogContext(Settings.Default.BlogConnection));
    
          //Act Sequential Saves don't throw a concurency exception
          var item = repository.Set<Blog>().First();
          item.Title = "Test Change";
          repository.SaveChanges();
          var item2 = repository2.Set<Blog>().First();
          item2.Title = "Testing Change";
          repository2.SaveChanges();
    
        }
      }
    
    }
  2. We then want to set up our blog object that will be used in this code base, by creating a new C# class in the BusinessLogic project, and name it Blog, using the following code:
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Text.RegularExpressions;
     
    namespace BusinessLogic
    {
      public class Blog
      {
        public int Id { get; set; }
    
        public DateTime Creationdate { get; set; }
     
        public string ShortDescription { get; set; }
    
        public string Title { get; set; }
    
        public double Rating { get; set; }
    
        public DateTime ModifiedDate { get; set; }
     
        [Timestamp]
        public Byte[] Timestamp { get; set; }
      }
    }
  3. The next step is to set up our mappings, by adding a new C# class named BlogMapping to the Mappings folder in the DataAccess project, paying particular attention to the modified date field with the following code:
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.ModelConfiguration;
    using BusinessLogic;
    namespace DataAccess.Mappings
    {
      public class BlogMapping : EntityTypeConfiguration<Blog>
      {
        public BlogMapping()
        {
          ToTable("Blogs");
          HasKey(x => x.Id);
          Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
    
          Property(x => x.Title).IsRequired().HasMaxLength(250);
    
          Property(x => x.Creationdate).HasColumnName("CreationDate").IsRequired();
    
          Property(x => x.ShortDescription).HasColumnType("Text").IsMaxLength().IsOptional().HasColumnName("Description");
    
          //Property(x => x.ModifiedDate).HasColumnType("DateTime").IsConcurrencyToken(true).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
    
          Property(x => x.Timestamp).IsConcurrencyToken(true).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
        }
     
      }
    }
  4. We now need to set up our BlogContext in the DataAccess project with the following code, to load the mappings for the objects:
    using System;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using DataAccess.Mappings;
    
    namespace DataAccess
    {
      public class BlogContext : DbContext, IUnitOfWork
      {
        public BlogContext(string connectionString) : base(connectionString)
        {
    
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
          modelBuilder.Configurations.Add(new BlogMapping());
          base.OnModelCreating(modelBuilder);
        }
    
        public IQueryable<T> Find<T>() where T : class
        {
          return this.Set<T>();
        }
    
        public void Refresh()
        {
          this.ChangeTracker.Entries().ToList().ForEach(x=>x.Reload());
        }
    
        public void Commit()
        {
          this.SaveChanges();
        }
      }
    }
  5. Then, we run our tests and everything passes.

How it works...

The test is set up as a way of exercising our concurrent model as it would be in our application. The catch here gives us the ability of testing against the exception that was thrown. This would be where we would handle the concurrency issue, and probably notify the user that another person or system has updated the same record. The second test ensures that we can do a series of updates without causing concurrency issues. This will ensure that our concurrency is only truly applied when it is supposed to be, and not anywhere else.

The set up of our Blog object includes a few data points and a date field called ModifiedDate . This gives us a timestamp for changes that can then be used as our concurrency token. This column is updated when the save changes operation is called, and any stale data in other contexts will then throw a concurrency error if they try to update without refreshing from the database. We configure this by calling the IsConcurrencyToken(true) method on the property configuration on Blog.

There's more...

When we are dealing with concurrency in database communication, there are many things to keep in mind, and more details on several of those can be found in the following section. The Entity Framework handles these in a default way, but if we need to modify that default, then more depth of knowledge is needed.

Atomic execution

When dealing with concurrency in a database, we have to be even more aware of our executions. We need them to be atomic in all the applications, but if they are not, in non-concurrent applications, it is less likely to cause a data loss or corruption. In a concurrent system, the likelihood that this would cause trouble is much higher. We have to focus on each execution completely perfectly, or leaving no trace of the failure. This should appear to the rest of the users that either the changes are part of the data and do not violate any of the business rules, or that the changes never existed.

Leaving no mess behind

Every transaction to the database should leave the database in a stable state. No transaction should violate the integrity of the data in a way that is perceivable to other transactions. If we have to turn off identity insert, insert some data, and then turn it back on this should be done within one transaction so between those steps the data cannot be accessed in a non-complete state. It is our responsibility to make sure that the state in which we are trying to get the data into, with an update or transaction, is a valid and stable state. In a concurrently accessed database, this is massively important, because if we violate this, it is likely that our other threads of access will fail due to the state caused by our updates.

Isolation

Every transaction should not be visible to other transactions. This is to keep our dependencies in check, and make sure that if one failure occurs, then it doesn't cause a chain reaction. If we have two dependent executions, then we need to be sure to apply both of them within one transaction. This will ensure that if the first fails, then the second is not even attempted.

See also

In this chapter:

  • Improving data retrieval in highly-threaded environments
  • Attaching objects with unit of work
..................Content has been hidden....................

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