Implementing the specification pattern

In this recipe, we will be implementing a specification pattern on top of Entity Framework and Repository Pattern to leverage maximum reuse without surfacing queryable objects to the consuming developers.

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 and updating data.

Open the Improving Complex Query Testing solution in the included source code examples.

How to do it...

Carry out the following steps in order to accomplish this recipe.

  1. We start by writing a test which will query the database with a predefined specification. Use the following code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using BusinessLogic;
    using DataAccess.Queries;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Rhino.Mocks;
    using DataAccess;
    using BusinessLogic.QueryObjects;
    using Test.Properties;
     
    namespace Test
    {
      [TestClass]
      public class QueryTests
      {
        [TestMethod]
        public void ShouldFilterTestData()
        {
          //Arrange
          IQueryable<Blog> items = new List<Blog>
          {
            new Blog()
            {
              Creationdate = DateTime.Now,
              ShortDescription = "Test",
              Title = "Test"
            },
            new Blog()
            {
              Creationdate = DateTime.Now,
              ShortDescription = "not this one",
              Title = "Blog"
            },
            new Blog()
            {
              Creationdate = DateTime.Now,
              ShortDescription = "not this",
              Title = "TeBlog"
            },
            new Blog()
            {
              Creationdate = DateTime.Now,
              ShortDescription = "not this one",
              Title = "TestBlog"
            }
     
          }.AsQueryable();
     
          var context = MockRepository
            .GenerateStrictMock<IDbContext>();
          context.Expect(x => x.AsQueryable<Blog>())
            .Return(items.AsQueryable());
          var repository = new BlogRepository(context);
     
          //Act
          var spec = new TitleNameQuery("Test");
          var returnedValues = repository.Find(spec);
     
          //Assert
          Assert.AreEqual(1, returnedValues.Count());
        }
     
     
        [TestMethod]
        public void ShouldConnectToTheDatabase()
        {
          var repository = new BlogRepository
            (new BlogContext(Settings.Default.BlogConnection));
          var results = repository.Find(new TitleNameQuery("Test"));
        }
      }
     
    }
  2. In the BusinessLogic project, add a new C# class named Blog with 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; }
      }
    }
  3. Add a Mapping folder to the DataAccess project and then add a BlogMapping class to the folder 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. Modify the BlogContext class to contain the new mappings for Blogs with the following code:
    using System;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using DataAccess.Mappings;
    using DataAccess.QueryObjects;
    using BusinessLogic.QueryObjects;
     
    namespace DataAccess
    {
      public class BlogContext : DbContext, IDbContext
      {
          public BlogContext(string connectionString)
            : base(connectionString)
          {
     
          }
     
          protected override void OnModelCreating
            (DbModelBuilder modelBuilder)
          {
            modelBuilder.Configurations.Add(new BlogMapping());
            base.OnModelCreating(modelBuilder);
          }
     
          public void Refresh()
          {
            this.ChangeTracker.Entries().ToList(
              .ForEach(x=>x.Reload());
          }
     
          public void Commit()
          {
            this.SaveChanges();
          }
     
          public IQueryable<T> AsQueryable<T>() where T : class
          {
          return this.Set<T>();
        }
      }
    }
  5. We want to add a new C# class to the BusinessLogic project named QueryObject with the following code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using BusinessLogic.QueryObjects;
     
     
    namespace DataAccess.QueryObjects
    {
      public class QueryObject : IQueryObject
      {
        public Func<IDbContext, int> ContextQuery { get; set; }
     
        protected void CheckContextAndQuery(IDbContext context)
        {
          if (context == null) throw new
            ArgumentNullException("context");
          if (this.ContextQuery == null) throw new
            InvalidOperationException("Null Query cannot
              be executed.");
        }
     
        #region IQueryObject<T> Members
     
        public virtual int Execute(IDbContext context)
        {
          CheckContextAndQuery(context);
          return this.ContextQuery(context);
        }
     
        #endregion
      }
    }
  6. We want to add a new C# class to the DataAccess project named QuerObjectOfT:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Collections.ObjectModel;
    using System.Reflection;
    using System.Linq.Expressions;
     
     
    namespace DataAccess.QueryObjects
    {
      public class QueryObject<T> : QueryObjectBase<T>
      {
        protected override IQueryable<T> ExtendQuery()
        {
          var source = base.ExtendQuery();
          source = this.AppendExpressions(source);
          return source;
        }
     
        public IQueryObject<T> Take(int count)
        {
          var generics = new Type[] { typeof(T) };
          var parameters = new Expression[]
            { Expression.Constant(count) };
          this.AddMethodExpression("Take", generics, parameters);
          return this;
        }
     
        public IQueryObject<T> Skip(int count)
        {
          var generics = new Type[] { typeof(T) };
          var parameters = new Expression[]
            { Expression.Constant(count) };
          this.AddMethodExpression("Skip", generics, parameters);
          return this;
        }
     
        #region Helper methods
     
        static ReadOnlyCollection<MethodInfo> QueryableMethods;
        static QueryObject()
        {
          QueryableMethods = new ReadOnlyCollection<MethodInfo>
            (typeof(System.Linq.Queryable).GetMethods
              (BindingFlags.Public | BindingFlags.Static)
                .ToList());
        }
     
        List<Tuple<MethodInfo, Expression[]>>
          _expressionList = new List<Tuple<MethodInfo,
            Expression[]>>();
        private void AddMethodExpression(string methodName,
          Type[] generics, Expression[] parameters)
        {
          MethodInfo orderMethodInfo = QueryableMethods
            .Where(m => m.Name == methodName && m.GetParameters()
              .Length == parameters.Length + 1).First();
     
          orderMethodInfo = orderMethodInfo
            .MakeGenericMethod(generics);
          _expressionList.Add(new Tuple<MethodInfo,
            Expression[]>(orderMethodInfo, parameters));
        }
     
        private IQueryable<T> AppendExpressions(IQueryable<T> query)
        {
          var source = query;
          foreach (var exp in _expressionList)
          {
            var newParams = exp.Item2.ToList();
            newParams.Insert(0, source.Expression);
            source = source.Provider.CreateQuery<T>
              (Expression.Call(null, exp.Item1, newParams));
          }
          return source;
        }
        #endregion
     
      }
    }
  7. We want to add a new C# generic class to the DataAccess project named QueryObjectBase with the following code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using BusinessLogic.QueryObjects;
     
     
    namespace DataAccess.QueryObjects
    {
      public abstract class QueryObjectBase<T> : IQueryObject<T>
      {
        protected Func<IDbContext, IQueryable<T>> ContextQuery { get; set; }
        protected IDbContext Context { get; set; }
     
        protected void CheckContextAndQuery()
        {
          if (Context == null) throw new
            InvalidOperationException("Context cannot be null.");
          if (this.ContextQuery == null) throw new
            InvalidOperationException("Null Query cannot 
              be executed.");
        }
     
        protected virtual IQueryable<T> ExtendQuery()
        {
          try
          {
            return this.ContextQuery(Context);
          }
          catch (Exception)
          {
            throw; //just here to catch while debugging
          }
        }
     
        #region IQueryObject<T> Members
     
        public virtual IEnumerable<T> Execute(IDbContext context)
        {
          Context = context;
          CheckContextAndQuery();
          var query = this.ExtendQuery();
          return query.ToList();    
        }
     
        #endregion
      }
    }
  8. We then want to add a new C# interface to the BusinessLogic project named IQueryObject with the following code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using BusinessLogic.QueryObjects;
     
    namespace DataAccess.QueryObjects
    {
      public interface IQueryObject
      {
        int Execute(IDbContext context);
      }
      
      public interface IQueryObject<out T>
      {
        IEnumerable<T> Execute(IDbContext context);
      }
    }
  9. We want to modify the IRepository interface in the BusinessLogic project with the following code:
    using System.Collections.Generic;
    using DataAccess.QueryObjects;
     
    namespace BusinessLogic
    {
      public interface IRepository
      {
        IEnumerable<T> Find<T>(IQueryObject<T> spec) where T : class;
      }
    }
  10. We also want to modify the BlogRepository class in Data Access with the following code:
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using DataAccess.QueryObjects;
    using BusinessLogic.QueryObjects;
     
    namespace DataAccess
    {
      public class BlogRepository : IRepository, IDisposable
      {
        private readonly IDbContext _context;
     
        public void Dispose()
        {
          Dispose(true);
          GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
          if (disposing)
          if (_context != null)
          _context.Dispose();
        }
     
        ~BlogRepository()
        {
          Dispose(false);
        }
        public BlogRepository(IDbContext context)
        {
          _context = context;
        }
            
        public IEnumerable<T> Find<T>(IQueryObject<T> spec)
          where T : class
        {
          return spec.Execute(_context);
        }
      }
    }
  11. We then want to add a Queries folder to the DataAccess project and a new C# class named BlogQueries with the following code:
    using System.Linq;
    using DataAccess.QueryObjects;
    using BusinessLogic;
     
     
    namespace DataAccess.Queries
    {
      public class TitleNameQuery : QueryObject<Blog>
      {
        public TitleNameQuery(string title)
        {
          ContextQuery = (c) => c.AsQueryable<Blog>()
            .Where(x => x.Title == title);
        }
     
      }
    }
  12. We run our test and everything passes.

How it works...

As always, we will start with a test that defines our scope. We want to be able to take a specification and limit a set with it. This will allow us to know when we have achieved the goal. We will then set up the blog object, the mapping, and the context.

Once those are in place, we will move to adding our abstract specification. This will serve as the base for all specifications we will use when moving forward. We will also add some extension methods and some generic specifications which will help us leverage the maximum amount of reuse from our efforts.

We will then need to modify the repository interface to accept in a specification of a certain type and return an enumerable collection of that type. These modifications will allow us to use the same repository interface no matter what we are querying against.

We will then need to modify our BlogRepository implementation to execute the specification chain. This could be as simple as our example of one specification, or many times it can be more complex. The beauty in a generic and simple implementation is its power and scalability.

Once this is accomplished, we then add our query library and will give prebuilt specifications back to anyone who needs to use it. We can then run our tests.

There's more...

There is one very important pattern that we should now discuss.

Specification pattern

Specification pattern is a pattern used which the developer outlines a business rule that is combinable with other rules. This allows for a highly customizable system of business rules that are at the same time incredibly testable. This fits perfectly for data access because there are certain reusable joins and filters that can be added to, combined, and used with almost infinite combinations.

See also

In Chapter 7, Using Concurrent and Parallel Processing:

  • Managing parallel contexts recipe
..................Content has been hidden....................

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