Geeks With Blogs

News

Series

Add to Technorati Favorites


An Archived Managed World This blog has moved to http://www.managed-world.com/blog

As I've been digging more into all the C# 3.0 goodness, I've been learning a lot more about lambda expressions, expression trees, query expressions, and all sorts of other things LINQ. One of the cool scenarios I think LINQ-To-Objects enables is the concept of facet mapping.

From the facet mapping website:

"First, a quick definition of terms (our glossary has more complete definitions). A resource is one of the objects you want to find by navigating a facet map. In this case, the resources are bottles of wine -- specifically, bottles of wine pirated from wine.com and compiled into a sample dataset for this demo.

 

A facet is a certain classifiable characteristic of the resource -- a way to classify something. Here we have "Varietal", "Region", and "Price", which are different ways for different people to classify wines. The different facets allow each of those people to focus on what they want.

 

A heading is an attribute that each bottle of wine might have. Varietals, e.g. Merlot or Champagne, are headings here, as are such appellations as Tuscany or Sonoma Valley. Plenty of other attributes could be headings, like "12% alcohol", or "medium-bodied", or "wine I drank in high school", but we'd have to define more facets to support them. "

 

Undoubtedly, you've seen facet mapping in action on many websites already, whether you are browsing computer hardware at http://www.newegg.com, or searching for cars on http://www.autobase.com. Facet Mapping is a very useful navigation technique. Not only that, it is a very simple technique to implement as well.

Defining Facets

The first thing we need to do is define our facet headings that we are going to generate pivot points for. Perhaps the easiest way to accomplish this is through the application of attributes. By decorating our POCO's (Plain Ol' CLR Object's) properties with an attribute, it becomes very easy for our facet mapping code to determine what headings it needs to calculate, and how to calculate them.

Let's use the context of Cars to discuss facet mapping. A car may have a Make, a Model, and possibly a Year associated with it. Any of these can be facet headings as we will see later. For the sake of this demo, the code is very bare-bones (as you'll see).

    public class Car
    {
        public string Make { get; set; }
        public string Model { get; set; }
        public string Year { get; set; }
    }

First, we need some classes that will help us build the results of our facet mapping efforts:

    public class Facet
    {
        public string Name { get; set; }
        public IList<Heading> Headings { get; set; }
    }

    public class Heading
    {
        public string Name { get; set; }
        public int Count { get; set; }
    }

In the case of our Car (to put this into context), we may have a Facet for "Make" with a list of the headings that make up our Facet. In the case of a Make Facet, we may have a Heading of "Ford" with a Count of 2, a Heading of "Toyota" with a Count of 5, and a Heading of "Cadillac" with a Count of 8. This data would be used by our UI to generate navigational links that may look like "Ford (2), Toyota (5), Cadillac (8)".

Finally, we need to find what properties of our Car are the ones we will be using to generate a Facet Map from. As mentioned above, in this example we are going to use an Attribute to identify the properties that we will generate our Facets from (again, this is _very_ bare-bones :P).

public class FacetAttribute : Attribute { }

Then, we just need to apply the attribute to our Car class.

    public class Car
    {
        [Facet()]
        public string Make { get; set; }

        [Facet()]
        public string Model { get; set; }
 
        [Facet()]
        public string Year { get; set; }
    }
In Walks LINQ

This is the point where I start to get really excited. One of the things I love about LINQ, is that you can focus much more on telling the computer what you want done, rather than how it gets done. Instead of working with loops, conditional statements, logic on how to generate counts of values, you can express your intentions a lot easier by using query expressions.

The first thing we need to is get the list of properties that we need to generate our headings for.

var facetList = from p in typeof(Car).GetProperties()
                where p.GetCustomAttributes(typeof(FacetAttribute), true).Count() > 0

This will loop through all the property definitions of our Car, and grab only the properties that have been decorated. To complete the LINQ query expression, we now need to get all the unique values for each property and return the number of cars that have that value for that property. This is also where the fun really starts. To do this we are going to use an embedded query expression as follows (lines 3-14)

                var facetList = from p in typeof(Car).GetProperties()
                                where p.GetCustomAttributes(typeof(FacetAttribute), true).Count() > 0
                                select new Facet
                                {
                                    Name = p.Name,
                                    Headings = (from c in cars
                                                orderby p.GetValue(c, null)
                                                group c by p.GetValue(c, null) into g
                                                select new Heading
                                                {
                                                    Name = g.Key.ToString(),
                                                    Count = g.Count()
                                                }).ToList()
                                };

The "magic" happens on line 6 where we use the embedded query expression to generate a list of Headings for each property. This is also where I think the use of LINQ really starts to shine. For each property we are generating headings for, we are grouping that property by the values of that property. Then we return the values of the property in the grouping and the number of cars that contain each specific value in our list of cars.

You could obviously write your typical pre-LINQ imperative code to generate these Facets, but it sure wouldn't be nearly as fun or easy. And when you're largely treating your objects as data that you need to "query" for information, I think LINQ is the perfect tool to have in your arsenal. Your mileage may vary, but I find this code much easier to read than a bunch of imperative code in loops and conditionals to try to generate the number of cars with each specific value for each specific property.

The Downsides

While the Attribute-based approach is certainly easy to start with, it has many downsides. When dealing with only plain, vanilla, string-based headings, it's not the most extensible approach. Adding numeric ranges to our facet mapping is not the easiest addition, and the attribute-based approach starts to becomes more constrictive the further we try to push it.

In my personal opinion, "implementation difficulty" isn't the most important downside to consider. I personally sense several different "smells" when looking at this code, mostly around design violations when writing object-oriented code. First of all (and probably one of my biggest concerns), our POCOs are not littered with attributes that are meant specifically for the presentation layer. And I don't know about you, but I'm not sure I like my potential domain objects littered with details for the UI. Not only does this smell to me, but if somebody else wants to use my objects and they don't care about facet mapping, they are essentially SOL.

There's also the constraints that happen when trying to extend this model. What happens if I want to facet map around values returned from method calls. I could certainly apply the attribute to the method as well, and then add some more code to our query expression to return properties and method definitions that have the attribute applied to them. But even then, we are start to stretch the readability of the code. And then there is the concern of trying to do facet mapping on values that aren't a specific property or method. For instance, what if I want to generate a Facet on a value that is a combination of two properties? Or a property and a method call? This is obviously not the most flexible solution we could come up with.

Using Attributes also means that our classes are now dependent upon a concrete implementation rather than a more abstract type. If the attribute has a reason to change, our class will potentially have to change as well. Combined with the previous drawback, it seems rather awkward to me that a domain object would potentially be changing because of a modification made to a presentation technique.

Well, if I'm taking about all sorts of downsides to this approach, why discuss this topic at all? Because it's a great learning experience, and gives us a common ground of understanding when we dive into a more C#-3.0-esque approach.

A Different Approach

In the next post in this series, we will talk about a different approach we can to doing facet mapping with LINQ that also happens to be much more sexy and just drips with C# 3.0 goodness. Not only that, but it just so happens to be a fluent interface as well (very fun)! So until then, I hope you enjoyed this trip down LINQ lane.

Posted on Friday, May 30, 2008 8:18 AM Development | Back to top


Comments on this post: Facet Mapping with LINQ, Part 1

# This post sucks
Requesting Gravatar...
Hi

This website is really dumb. My senile grandmother would be better at explaining Facet Mapping with Linq.


var idiot = true;

Bye
Left by Bob Evert on May 23, 2013 12:27 AM

Your comment:
 (will show your gravatar)


Copyright © Jason Olson | Powered by: GeeksWithBlogs.net