The IEqualityComparer in action

I recently had the situation where I had a class similar to the following:

public class Item
{
    public Guid ItemID { get; set; }
    public string ItemName { get; set; }
    public IList Categories { get; set; }
}

public class Category
{
    public Guid CategoryID { get; set; }
    public string CategoryName { get; set; }
}

Data for the application was pulled out of a Linq to SQL class, working off a SQL database.  (I don’t want to get into a discussion on the semantics of my design choice – perhaps Items should be members of Categories instead of the other way around.  Just enjoy the show for now…)

What I wanted to do was to filter a Linq query from the database (containing IQueryable<Item>) for items that contained Categories with a given CategoryID.  No problems!   Just run up a Linq to SQL query to get the Category instance matching that CategoryID as follows:

var myCategory = (from c in datacontext.dbCategories
                  where c.ID == myCategoryID
                  select new Category
                  {
                      CategoryID = c.ID,
                      CategoryName = c.Name
                  }).Single();

Note that the filter as shown below won’t work on an IQueryable<Item> (due to the Contains() clause), so we’ll evaluate the incoming IQueryable<Item> as it comes in, and (for convenience) convert it back to IQueryable on the way out.  We’ll implement the method as an extension method, because we can.

public IQueryable FilterByCategory(this IQueryable qry, Category cat)
{
    return (from i in qry.ToList()        // Evaluate the qry
            where i.Categories.Contains(cat)
            select i).AsQueryable();
}

Now, this is all fine, as far as it goes.   Except for one small thing.

It didn’t work.

My result set was empty.  Always.

So what’s going on?  Basically, here’s the thing.  Have a look at the following code fragments:

int x = 7;
int y = 7;
Debug.Assert(x == y);    // Assert is true

class MyObject
{
    public int Value;

    public MyObject(int val)
    {
        Value = val;
    }
}
MyObject a = new MyObject(7);
MyObject b = new MyObject(7);
Debug.Assert(a == b);    // Assert fails!

It may seem obvious, but the reason the second Assert fails is that, despite having the same internal value, a and b are not the same! And this is the problem with my filter query above.   Despite the Category instance I received from the SQL query having the same internal values as (perhaps) a Category in the Categories property of an Item, they are not the same object and so the comparison will fail.
What we need is some way of telling .NET that two instances of an object are the same, even if they are different objects, and this is where the IEqualityComparer comes in.  We can create the following class:

class CategoryComparer : IEqualityComparer
{
    #region IEqualityComparer Members

    public bool IsEqual(Category x, Category y)
    {
        return x.CategoryID == y.CategoryID;
    }

    public int GetHashCode(Category obj)
    {
        return obj.CategoryID.GetHashCode();
    }

    #endregion
}

We have created a class that says that two instances of Category are equal when their CategoryID values match, and have based their Hash Code on the CategoryID value rather than the instance itself.  All we need to do now is to plug it in to the query, as follows:

public IQueryable FilterByCategory(this IQueryable qry, Category cat)
{
    return (from i in qry.ToList()        // Evaluate the qry
            where i.Categories.Contains(cat, new CategoryComparer())
            select i).AsQueryable();
}

Voila!  .NET is a wonderful environment, but there are always a few gotchas lying around for the unsuspecting.  Work through probelms methodically, and you’ll find there’s always a way.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s