Facet Mapping with LINQ, Part 2

5 minutes read

Originally posted on: http://geekswithblogs.net/jolson/archive/2008/06/03/facet-mapping-with-linq-part-2.aspx

In "Facet Mapping with LINQ, Part 1", we discussed how to add Facet Mapping to your .NET application using an attribute-based solution. This solution just doesn't leave me very excited. As we recounted, there are some problems with using an attribute-based approach:

  • Littering domain models with presentation layer-specific attributes 
  • Doesn't support existing POCOs (that perhaps can't change)
  • Can't support combinations of members (without create a new method property that actually does the combining (read: ugly)).

I personally only want one area of code to change if I alter my facet mapping. Having to change my POCOs when I want to change facet maps "smells" to me (and is itself a violation of the Single Responsibility Principle). Let's look at another way we can implement facet mapping in .NET via some more LINQ-goodness.

In a previous post, I talked about my new-found love for Fluent Languages in .NET (ala Moq or NInject). With attributes out of the way for facet mapping, I wanted to develop a library for facet mapping that was easy to use and could be used with existing code. Here are some of the "requirements" for the new facet mapping code (read: "requirements" means arbitrary features that I think are good and that I would like to see :P):

  • Usable with an existing set of POCOs (without having to change a single line of code in those objects)
  • Easily support combination of properties/methods or any arbitrary code that a person would want to create a facet on
  • Readable and easy to use (read: my own reason to try to a fluent language interface :P)

Let's take a look at the proposed API (taken straight from my unit tests):

            // Given our list of cars
            List<Car> cars = new List<Car>() {
                new Car { Make = "Toyota", Model = "Corola", Price = 20000, Year = 2001 },
                new Car { Make = "Toyota", Model = "Corola", Price = 30000, Year = 2002 }
            };

            // Generate a facet map (list of Facets) that we can use
            var facets = FacetMap<Car>.Create()
                .AddFacetOn(c => c.Make)
                .AddFacetOn(c => c.Model)
                .GenerateFrom(cars)
                .ToList();

We can also define an arbitrary expression that we want to generate a facet on:

            var facets = FacetMap<Car>.Create()
                .AddFacetOn("MakeAndModel", c => c.Make + " " + c.Model)
                .GenerateFrom(cars)
                .InParallel()
                .ToList();

With FacetMap being a generic class, we can use it to generate facets on any existing CLR class we wish to. And with the use of lambda expressions to specify what we are creating facets on, our code can be refactored a bunch and not break our facet mapping.

Implementing FacetMap isn't actually all that difficult. Since all our operations can chain be chained together with each other, all of them actually return the same interface (our IFacetMap that the FacetMap class implements).

The first thing you notice is that we are creating a generic class so that our facet maps can be used with any POCO that exists out there (theoretically). And, we are hiding the constructor so that we can use a factory method (I happen to think it reads better, could just be my personal opinion though).

    public class FacetMap<TResource> : IFacetMap<TResource>
    {
        /// <summary>
        /// Constructor is hidden as we are using a factory method
        /// </summary>
        private FacetMap() 
        {
            facetGenerators = new Dictionary<string, Delegate>();
        }

        /// <summary>
        /// Factory Method to create a new Facet Map
        /// </summary>
        /// <returns>New FacetMap</returns>
        public static IFacetMap<TResource> Create()
        {
            return new FacetMap<TResource>();
        }

        ...
    }

Our class implemented our IFacetMap interface so that external code using our FacetMap is relying on abstractions outside of our Factory Method. And as you can see, all our methods return this interface so that we can chain all the methods together to form the fluent language shown above (the one exception being our Generate() method where we just return our list of Facets that were generated):

    public interface IFacetMap<TResource>
    {
        IFacetMap<TResource> AddFacetOn<TResult>(Expression<Func<TResource, TResult>> facetValue);
        IFacetMap<TResource> AddFacetOn<TResult>(string facetName, Expression<Func<TResource, TResult>> facetValue);
        IEnumerable<Facet<TResource>> GenerateFrom(IEnumerable<TResource> resources);
    }

Why the "Expression<Func<X, Y>>" instead of just "Func<X, Y>". This is more a matter of convenience for the end-user. The end-user can write the code as if it was a normal Func<X, Y>, but we can do some fun things with the lambda expression like automatically generating the name of the facet based on the property or method the user passes in (notice that in the case of a more complex lambda expression, the user must specify the facet name since there is no way to generate a guaranteed user-friendly name for the lambda expression).

Internally, we are using a Dictionary<string, Delegate> to store all the passed in lambda expressions that are going to be used to generate our facets with. In the case where a facet name isn't explicitly stated, we automatically use the name of the Property or Method (depending on which type of expression was passed in):

        private Dictionary<string, Delegate> facetGenerators;

        public IFacetMap<TResource> AddFacetOn<TResult>(Expression<Func<TResource, TResult>> facetValue)
        {
            string facetName;
            if (facetValue.Body.NodeType == ExpressionType.MemberAccess)
            {
                MemberExpression expression = (MemberExpression)facetValue.Body;
                facetName = expression.Member.Name;
            }
            else if (facetValue.Body.NodeType == ExpressionType.Call)
            {
                MethodCallExpression expression = (MethodCallExpression)facetValue.Body;
                facetName = expression.Method.Name;
            }
            else
            {
                throw new InvalidHeadingExpressionException();
            }

            AddFacetOn(facetName, facetValue);
            return this;
        }

        public IFacetMap<TResource> AddFacetOn<TResult>(string facetName, Expression<Func<TResource, TResult>> facetValue)
        {
            if (!facetGenerators.ContainsKey(facetName))
            {
                facetGenerators.Add(facetName, facetValue.Compile());
            }
            else
            {
                throw new NonUniqueFacetAddedException();
            }

            return this;
        }

The last thing we need to do is implement our Generate() method. Here, we are largely going to reuse the LINQ statement from our last post (check it out), the difference being that instead of pulling facets from properties decorated with our attribute we will use out internal dictionary that is built from our AddFacetOn methods.

        public IEnumerable<Facet<TResource>> GenerateFrom(IEnumerable<TResource> resources)
        {
            return from facet in facetGenerators
                   select new Facet<TResource>
                   {
                       Name = facet.Key,
                       Headings = (from resource in resources
                                   let value = facet.Value.DynamicInvoke(resource)
                                   orderby value
                                   group resource by value into g
                                   select new Heading<TResource>
                                   {
                                       Value = g.Key.ToString(),
                                       MatchCount = g.Count(),
                                       Filter = BuildFilter(facet.Value, g.Key.ToString())
                                   }).ToList()
                   };
        }

        private Func<TResource, bool> BuildFilter(Delegate facetValue, string headingValue)
        {
            return (resource) => facetValue.DynamicInvoke(resource).ToString() == headingValue;
        }

And finally, that for every header we generate, we also generate a Filter (shown above). This Filter is of type Func<TResource, bool> so that it can be used in a Where() method to filter our existing list of resources at a later time based on the header that was chosen.

As you can see, there is some cool stuff you can do with LINQ around fluent languages very easily. And not only that, you can add some cool navigational techniques to your application using Facet Mapping that is quite easy to implement using the new features in LINQ.

Updated:

Leave a Comment