Managing parallel contexts

In this recipe, we will be setting up a management class that allows us to create and manage multiple contexts on a per thread basis.

Getting ready

We will be using 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 Parallel Context Management solution in the included source code examples.

How to do it...

  1. We start with a test that defines how the threads and the contexts should interact. This will let us control the scope, and make sure we have accomplished the goal of having a thread-specific context that can be reused with the following code:
    using System.Threading;
    using DataAccess;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace Test
    {
      [TestClass]
      public class MultiThreadedRepoManagementTests
      {
        private IBlogRepository repo1 = null;
        private IBlogRepository repo2 = null;
        private IBlogRepository repo3 = null;
    
        [TestMethod]
        public void ShouldAllowCommitsToMultipleContextsWithoutConcernForThread()
        {
          //Arrange
          RepositoryFactory.Get("Test");
    
          //Act
          Thread thread = new Thread(GetRepositories);
          repo1 = RepositoryFactory.Get("Test");
          thread.Start();
          while (thread.ThreadState == ThreadState.Running)
          {
            Thread.Sleep(1);
          }
    
          //Assert
          Assert.IsNotNull(repo1);
          Assert.IsNotNull(repo2);
          Assert.IsNotNull(repo3);
          Assert.AreSame(repo2, repo3);
          Assert.AreNotSame(repo1, repo2);
          Assert.AreNotSame(repo1, repo3);
        }
    
        private void GetRepositories()
        {
          repo2 = RepositoryFactory.Get("Test");
          repo3 = RepositoryFactory.Get("Test");
        }
      }
    
    }
  2. We then want to add a new C# class named Blog to the BusinessLogic project with the following code:
    using System;
    
    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; }
      }
    }
  3. Next up will be for us to add a new C# class named BlogMapping to the DataAccess project 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()
        {
          this.ToTable("Blogs");
          this.HasKey(x => x.Id);
          this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
    
          this.Property(x => x.Title).IsRequired().HasMaxLength(250);
          this.Property(x => x.Creationdate).HasColumnName("CreationDate").IsRequired();
    
          this.Property(x => x.ShortDescription).HasColumnType("Text").IsMaxLength().IsOptional().HasColumnName("Description");
        }
    
      }
    }
  4. We then want to add a new C# class named BlogContext to the DataAccess project with the following code:
    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. We then want to add a new C# class named RepositoryFactory into the DataAccess project. The class will have another class defined in it that will handle identifying contexts with the following code:
    using System.Collections.Generic;
    using System.Configuration;
    using System.Threading;
    
    namespace DataAccess
    {
      public class RepositoryFactory
      {
        private static Dictionary<string, IBlogRepository> 
          repositories = new Dictionary<string, IBlogRepository>();
    
        public static IBlogRepository Get(string connectionString)
        {
          var id = new RepositoryIdentifier(Thread.CurrentThread, connectionString);
    
          if(!repositories.ContainsKey(id))
          {
            //This would more than likely not new up the blog //repository but supply it from an IoC implementation.
            repositories.Add(id, new BlogRepository(new BlogContext(connectionString)));
          }
          return repositories[id];
        }
    
        public static void Dispose(string connectionString)
        {
          var id = new RepositoryIdentifier(Thread.CurrentThread, connectionString);
          if (!repositories.ContainsKey(id))
          {
            repositories.Remove(id);
         }
        }
    
        private class RepositoryIdentifier
        {
          private readonly Thread _currentThread;
          private readonly string _connectionString;
    
          public RepositoryIdentifier(Thread currentThread, string connectionString)
          {
            _currentThread = currentThread;
            _connectionString = connectionString;
          }
    
          public override string ToString()
          {
            return _currentThread.ManagedThreadId + _connectionString;
          }
          public static implicit operator string(RepositoryIdentifier r)
          {
            return r.ToString();
          }
        }
      }
    }
  6. Run all of our tests, and they should pass.

How it works...

The test defines how the repository factory should be used to get a context per thread. It also ensures that the same thread will get a reusable context if it calls the factory with the same connection string. This definition of the problem that we are trying to solve gives us a clear model to solve.

The addition of the Blog object, the mappings, and the context will allow us to connect and communicate with a database, so that our example code is fully functional to the database. These pieces are essential for the full-testing of our solution.

The repository factory will act as a central creation point for all the repositories. This ensures that all repositories created this way are specific to the thread that they are in. The importance of this is that the DbContext is not thread-safe, and will cause problems if the same object is shared across many threads. We can avoid these problems by separating the context by thread and allowing for reuse without conflicts.

There's more...

When dealing with multi-threaded applications, there are several issues that we need to be aware of so that we can avoid them, and the following sections provide more details on some of those issues:

Race conditions

When we have two threads that have access to the shared data (such as a database), and one of them is somehow dependent on the execution happening in the right sequence, we have created a race condition. For example, when we have one thread adding a post to blog 1, and the second thread is selecting all posts from blog 1, and then modifying them to have a new title. If thread 2 completes its select operation before the save operation of thread 1, then it will not achieve the goal, but if save operation of thread 1 occurs first, then it will succeed. These two threads are now racing.

Locking

When resources are shared between threads, the resource may need to be locked. Locking ensures that only one thread can interact with the object at a time. This will allow shared resources to be used, without sacrificing the integrity of the system. This will enforce a serialization concurrency control.

See also

In this chapter:

  • Improving multiple context performance
..................Content has been hidden....................

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