Attaching objects with unit of work

In this recipe, we will be using multiple contexts to select the objects and update them, without having the knowledge of where they came from. This is a very common scenario when entities, or representations of entities, are being received by a web service or any other situation where the original context may have fallen out of scope.

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 Detached Objects with Unit Of Work solution in the included source code examples.

How to do it...

  1. We start with a test that defines how threads and contexts should interact when pulling data and then updating it from a different context. This will let us control the scope, and make sure that we have accomplished the goal with the following code:
    using System.Data.Entity;
    using System.Linq;
    using System.Threading;
    using BusinessLogic;
    using DataAccess;
    using DataAccess.Database;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Test.Properties;
    
    namespace Test
    {
      [TestClass]
      public class MultiThreadedOjectsQueriesTests
      {
        private IBlogRepository repo1 = null;
        private IBlogRepository repo2 = null;
        private string _changedInThread;
        private Blog retreivedItem;
    
        [TestMethod]
        public void ShouldAllowUpdatesToAnObjectFromAContextOtherThanTheOneThatRetreivedIt()
        {
          //Arrange
          Database.SetInitializer(new Initializer());
          repo1 = RepositoryFactory.Get(Settings.Default.Connection);
    
          //Act
          Thread thread = new Thread(GetBlogFromSecondContext);
    
          thread.Start();
          while (thread.ThreadState == ThreadState.Running)
          {
            Thread.Sleep(1);
          }
    
          repo1.UnitOfWork.RegisterUnModified(retreivedItem);
          retreivedItem.Title = "Changed on context 1";
    
          //Assert
          Assert.AreEqual(repo1.SaveChanges(), 1);
        }
    
        private void GetBlogFromSecondContext()
        {
          repo2 = RepositoryFactory.Get(Settings.Default.Connection);
          retreivedItem = repo2.Set<Blog>().First();
    
        }
      }
    
    }
  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 data access 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.Data;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq;
    using BusinessLogic;
    using DataAccess.Mappings;
    namespace DataAccess
    {
      public class BlogContext : DbContext, IUnitOfWork
      {
        public BlogContext(string connectionString) : base(connectionString)
        {
    
        }
    
        public int Commit()
        {
          return SaveChanges();
        }
    
        public IQueryable<T> Find<T>() where T : class
        {
          return Set<T>();
        }
    
        public void Refresh()
        {
          ChangeTracker.Entries().ToList().ForEach(x => x.Reload());
        }
    
        public void RegisterAdded<T>(T item) where T : class
        {
          AttachObjectInState(item, EntityState.Added);
        }
    
        public void RegisterDeleted<T>(T item) where T : class
        {
          AttachObjectInState(item, EntityState.Deleted);
        }
    
        public void RegisterModified<T>(T item) where T : class
        {
          AttachObjectInState(item, EntityState.Modified);
        }
    
        public void RegisterUnModified<T>(T item) where T : class
        {
          AttachObjectInState(item, EntityState.Unchanged);
        }
        private void AttachObjectInState<T>(T item, EntityState state) where T : class
        {
          DbEntityEntry<T> entry = Entry(item);
          if (entry == null)
          {
            Set<T>().Attach(item);
            entry = Entry(item);
          }
          entry.State = state;
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
          modelBuilder.Configurations.Add(new BlogMapping());
          base.OnModelCreating(modelBuilder);
        }
        }
    }
  5. We now add the IUnitOfWork methods for registering objects with the following code:
    using System.Linq;
    
    namespace BusinessLogic
    {
        public interface IUnitOfWork
        {
            IQueryable<T> Find<T>() where T : class;
            void Refresh();
            int Commit();
            void RegisterModified<T>(T item) where T : class;
            void RegisterUnModified<T>(T item) where T : class;
            void RegisterAdded<T>(T item) where T : class;
            void RegisterDeleted<T>(T item) where T : class;
      }
    }
  6. We also need to change the interface for IBlogRepository to surface the unit of work with the following code:
    using System.Linq;
    
    namespace BusinessLogic
    {
      public interface IBlogRepository
      {
        void RollbackChanges();
        int SaveChanges();
        IQueryable<T> Set<T>() where T : class;
        IUnitOfWork UnitOfWork { get; }
      }
    }
  7. We now want to add code to the BlogRepository so it can handle surfacing the new interface member 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 IUnitOfWork UnitOfWork
        {
          get { return _context; }
        }
    
        public void RollbackChanges()
        {
          _context.Refresh();
        }
    
        public int SaveChanges()
        {
          try
          {
            return _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...

The test defines that the problem we are trying to solve is selecting an object from one context, and then committing an update to it in another context. This definition gives us the bounds of our scope.

We then add our Blog entity to interact with from the database, our mappings to which we specify the tables and the columns, and finally our context to finish the database interactions. This gives us the basis for the communication to the database.

We then add our repository factory on to this, to allow for the creation of contexts per thread for our selection and updates of data. This gives us the means to get more than one context involved in multiple threads.

We can then leverage our unit of work pattern to attach objects, and set up the pattern by which objects can be moved from one context to another without friction. This allows us to select our object from one context, dispose off that context, and update the objects that are selected from another context, once they are attached to it.

There's more...

Attaching and detaching objects from a context brings on some overhead and patterns that warrant deeper understanding, and so we have outlined them in the following sections:

Attaching related objects

Related objects are not attached by default, and will have to be attached manually. This is to keep an Attach method call from creating duplicate entries for objects already being tracked in the context. This is just something that we have to be aware of, and handle inside our implementation of the attach command.

Detaching objects

When an object is detached, it is set to the detached state in the object tracker, and is no longer change-tracked. You can attach the object to another context to allow it to be change-tracked. It can also be attached back to the original context. Any changes made during the detached time, however, are not tracked, and will need to be manually set by editing the entry state in the change tracker.

See also

In this chapter:

  • Handling data retrieval in highly-threaded environments
..................Content has been hidden....................

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