Isg.Specification

I was reading some interesting blogs on the specification pattern today. I was hoping to find a NuGet package, but no such luck. So, I put together the best ideas from the blogs I read and created a new NuGet package called Isg.Specification. Much of this work is not original—my contribution is putting the work of these other developers together in an integrated way and publishing it on NuGet.

Although the write-up of the specification pattern on wikipedia doesn’t mention my specific usage goal, I want to create a parameter object that provides a specification to be used by a LINQ query in EntityFramework. In this way, I can provide a single Get<T>(ParameterObject specification) method to service all Get queries. This simplifies a service interface by removing all the various GetById, GetByName, GetByHasInvoices methods. All that differs between those methods is the predicate applied to the query, so passing the predicate into Get() as a parameter just feels natural. However, I don’t want the caller to be able to specify any predicate they want so using a parameter object that converts itself into a predicate services the purpose of limiting the callers query access as well as simplifying the query API.

Even though my target usage is EntityFramework, and the source code is in the same repository as Isg.EntityFramework, Isg.Specification in no way depends on EntityFramework.

The simplest form of the specification pattern uses simple boolean functions. However, since my target usage is EntityFramework, I needed the functions to be convertible to SQL by the EntityFramework. For this reason, my implementation of ISpecification<T> uses Expression<Func<T, bool>> instead of Func<T, bool> as its signature.

   1:      public interface ISpecification<T>
   2:      {
   3:          Expression<Func<T, bool>> IsSatisfied();
   4:      }

 

The simplest way to get started using the library is to inherit from CompositeSpecification<T>. Using the same sample DbContext I used for Isg.EntityFramework, here is a sample implementation of a Specification object against Customer:

   1:      public class CustomerSpecification : CompositeSpecification<Customer>
   2:      {
   3:          public string Name { get; set; }
   4:          public bool? HasInvoices { get; set; }
   5:   
   6:          protected override IEnumerable<Expression<Func<Customer, bool>>> GetExpressions()
   7:          {
   8:              if (!string.IsNullOrWhiteSpace(Name))
   9:                  yield return ByName();
  10:   
  11:              if (HasInvoices.HasValue)
  12:                  yield return ByHasInvoices();
  13:          }
  14:   
  15:          private Expression<Func<Customer, bool>> ByHasInvoices()
  16:          {
  17:              if (HasInvoices.Value)
  18:                  return c => c.Invoices.Any();
  19:              return c => !c.Invoices.Any();
  20:          }
  21:   
  22:          private Expression<Func<Customer, bool>> ByName()
  23:          {
  24:              return c => c.Name.StartsWith(Name);
  25:          }
  26:      }

 

Usage is pretty easy. ISpecification<T> exposes a single method called IsSatisfied(). Feed the result of this method to a LINQ Where clause and you’re off to the races. CompositeSpecification<T> aggregates the expressions provided by GetExpressions() into a single And-ed expression and uses that for the filter.

In my test cases I create 2 customers, one of whom has invoices and one who does not. You can review the test cases in full here:

Here is sample usage:

   1:          [Test]
   2:          public void HasInvoices()
   3:          {
   4:              // Arrange
   5:              var filter = new CustomerSpecification()
   6:                               {
   7:                                   HasInvoices = true,
   8:                               };
   9:   
  10:              // Act
  11:              using (var context = new CustomerDbContext())
  12:              {
  13:                  var query = context.Customers
  14:                      .Where(filter.IsSatisfied())
  15:                      .ToList();
  16:   
  17:                  // Assert
  18:                  Assert.That(query, Has.Count.EqualTo(1));
  19:                  var result = query.Single();
  20:                  Assert.That(result.Name, Is.EqualTo("Dagny Taggart"));
  21:              }
  22:          }
 
I’ve published Isg.Specification as version 0.1. As always, if you find use for my libraries I would really appreciate your feedback.
 
Enjoy!

Leave a Reply

%d bloggers like this: