Improving multiple context performance

In this recipe, we will look at performance timings for multi-threaded environments so as to keep from making the mistake of thinking that multi-threading is always faster, without having the proof of the performance increases.

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 Multiple Context Performance solution in the included source code examples.

How to do it...

  1. This integration test will insert 100 entries into the database twice, one using a single thread, the second using four threads. It then asserts that the multiple thread option should have taken less time.
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading;
    using BusinessLogic;
    using DataAccess;
    using DataAccess.Database;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Test.Properties;
    using ThreadState = System.Threading.ThreadState;
    
    namespace Test
    {
      [TestClass]
      public class MultiThreadedOjectsQueriesTests
      {
    
        public TimeSpan ElapsedTime = new TimeSpan(0);
    
        [TestMethod]
        public void ShouldGetMostRecentDataFromDatabaseAfterAnUpdateFromAnotherThread()
        {
          //Arrange
          Database.SetInitializer(new Initializer());
          var repo = RepositoryFactory.Get(Settings.Default.Connection);
    
          //Act
          Stopwatch sw = Stopwatch.StartNew();
          for (int i = 0; i < 100; i++)
          {
            repo.Add(new Blog()
            {
              Creationdate = DateTime.Now,
              Title = string.Format("Test {0}", i),
              Rating = 4,
              ShortDescription = "Test"
            });
          }
          repo.SaveChanges();
          sw.Stop();
    
          List<Thread> threads = new List<Thread>
          {
            new Thread(InsertTwentyFiveBlogs), 
            new Thread(InsertTwentyFiveBlogs),
            new Thread(InsertTwentyFiveBlogs),
            new Thread(InsertTwentyFiveBlogs)
          };
    
          Stopwatch sw2 = Stopwatch.StartNew();
          threads.ForEach(x=>x.Start());
    
          while (threads.Any(x=>x.ThreadState == ThreadState.Running))
          {
            Thread.Sleep(1);
          }
    
          sw2.Stop();
    
          //Assert
          Console.WriteLine("Singled Threaded took {0} ms", sw.ElapsedMilliseconds);
          Console.WriteLine("Multithreaded took {0} ms", sw2.ElapsedMilliseconds);
          Assert.IsTrue(sw2.Elapsed < sw.Elapsed);
    
        }
        private void InsertTwentyFiveBlogs()
        {
          var repo = RepositoryFactory.Get(Settings.Default.Connection);
          for (int i = 0; i < 25; i++)
          {
            repo.Add(new Blog()
            {
              Creationdate = DateTime.Now,
              Title = string.Format("Test {0}", i),
              Rating = 4,
              ShortDescription = "Test"
            });
          }
    
          repo.SaveChanges();
        }
      }
    
    }
  2. We then want to add our Blog object 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 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 now add the IUnitOfWork methods for adding objects with the following code:
    using System.Linq;
    
    namespace DataAccess
    {
      public interface IUnitOfWork
      {
        IQueryable<T> Find<T>() where T : class;
        void Refresh();
        void Commit();
        IQueryable<T> CurrentFind<T>() where T : class;
        void Add<T>(T item) where T : class;
      }
    }
  5. We then want to update the Blogcontext with the following code:
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Data.Objects;
    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 IQueryable<T> CurrentFind<T>() where T : class
        {
          var context = (IObjectContextAdapter) this;
          var query = context.ObjectContext.CreateObjectSet<T>();
          query.MergeOption = MergeOption.OverwriteChanges;
          return query;
        }
    
        public void Add<T>(T item) where T : class
        {
          this.Set<T>().Add(item);
        }
    
        public void Refresh()
        {
          this.ChangeTracker.Entries().ToList().ForEach(x=>x.Reload());
        }
    
        public void Commit()
        {
          this.SaveChanges();
        }
      }
    }
  6. We also need to change the interface for IBlogRepository with the following code:
    using System.Linq;
    using BusinessLogic;
    
    namespace DataAccess
    {
      public interface IBlogRepository
      {
        void RollbackChanges();
        void SaveChanges();
        IQueryable<T> Set<T>() where T : class;
        IQueryable<T> CurrentSet<T>() where T : class;
        IUnitOfWork UnitOfWork { get; }
        void Add<T>(T item) where T : class;
      }
    }
  7. We now want to update the repository BlogRepository with the following code:
    using System;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    
    namespace DataAccess
    {
      public class BlogRepository : IBlogRepository
      {
        private readonly IUnitOfWork _context;
    
        public BlogRepository(IUnitOfWork context)
        {
          _context = context;
        }
    
        public IQueryable<T> Set<T>() where T : class
        {
          return _context.Find<T>();
        }
        public IQueryable<T> CurrentSet<T>() where T : class
        {
          return _context.CurrentFind<T>();
        }
    
        public IUnitOfWork UnitOfWork
        {
          get { return _context; }
        }
    
        public void Add<T>(T item) where T : class
        {
          _context.Add(item);
        }
    
        public void RollbackChanges()
        {
          _context.Refresh();
        }
    
        public void SaveChanges()
        {
          try
          {
            _context.Commit();
          }
          catch (Exception)
          {
            RollbackChanges();
            throw;
          }
    
        }
      }
    }
  8. We then want to add the repository factory into the DataAccess project 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();
          }
        }
      }
    }
  9. Run all of our tests, and they should pass.

How it works...

We start by using a test that compares the update timings from a single thread and multiple background threads. This test gives us the scope by which we implement our multi-threaded solution.

We add our Blog to interact with the mappings and the context that talk to the database. This gives us the communication link that we need to test our threading solution.

We then leverage our repository factory to create multiple contexts to test against, and insert 25 records on each background thread. This will give us a comparison to the main thread.

This recipe is all about the timings of inserting 100 records into the database. We have set up the insert to run both on a single thread, and on four background threads. This will compare the timings of both the runs.

Notice that the threads doing the update are much faster than the single thread doing the same work. This is due to the wait time of the database doing the work, and waiting for a response.

There's more...

When timing and testing multi-threaded applications, we have to make sure to use more than just the code coverage as a metric, as this will not test race conditions and locking scenarios. The following sections show some techniques that can help testing multi-threaded code:

Thread interleaving

Each thread that is spun up can be halted for random amounts of time to test execution locks, data race conditions, and create tests around this. Normally, this includes creating manual harnesses throughout the multi-threaded code so as to insert random blocks to create these scenarios. This can be very time-consuming, but valuable testing.

Data race conditions

Randomly pausing threads to create completion patterns that are not normally seen will surface data race conditions in the multi-threaded code, or optimistic concurrency, and allow them to be reproduced (with the proper amount of logging). This is essential for multi-threading a DataAccess layer, as these bugs are often dismissed as loss data, bad data, flukes, or non-reproducible.

Chess

Microsoft research has developed a tool for automatically testing thread interleaving and data race conditions in multi-threaded applications. You can refer to the following URL for the same:

http://research.microsoft.com/en-us/projects/chess/

..................Content has been hidden....................

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