LINQ Distinct, Except, Contains, Union, Intersect and IEqualityComparer

by Arnold Matusz 23 6 2009

LINQ is one feature I could not live without anymore. It is always a pain to work on projects using older technologies. The gap between .NET 1.1 (VS.NET 2003) and .NET 2.0 (VS.NET 2005) was huge and it was difficult to develop anything in ASP.NET v1.1 after you got your hands on .NET 2.0. But the gap between .NET 3.5 SP1 coupled with Visual Studio 2008 and the prior version is far bigger.

There is LINQ, LINQ to SQL, Lambda Expressions, the Entity Framework (and allot more) which simply boost your development speed. Anyone who hasn’t tried these yet: you definitely need to have a go!

The whole idea behind this blog post is that I’ve been doing some work using the ADO.NET Entity Framework on a project. In a method I’ve been collecting all sorts of data from different static/shared methods from Entity Framework classes (of course all very coming from other ObjectContexts). But once applied the Except operator on an IEnumerable<> collection I found some unexpected results.

The Except operator returns the set difference of two sequences by using the default equality comparer to compare values. In proper English this means that the Except method returns those elements in first sequence that do not appear in second by simply comparing the Objects themselves.

In my situation this was wrong because of course Object1 and Object2 are not equal even if they have all the same values inside them. Of course this will work if your sequences contain only integers or real numbers (because the default comparer will know how to compare two numbers). But the default comparer definitely won’t know how to relate two Entity Framework classes.

This is why you can implement a custom Comparer class inherited from IEqualityComparer<TSource>. Using such a class you can exactly assess when two objects are really equal. In my case, because I’m actually checking whether 2 table rows correspond (are practically the same) I’m only interested to see if their Primary Key is equal.

If you wish to replicate the code examples you can go ahead and create a new project, in which you should create a new Entity Data Model of a database which contains the tables for the ASP.NET Application Services (Authentication, Profiles, Roles, Logs, etc.).

For sake of this example I’ll count:

  1. All registered users
  2. All users that are locked out
  3. using Except – all the users that are not locked out

First of all we need 2 Methods that return the needed data (registered users and locked out users).

    public IEnumerable<aspnet_Users> AllUsers()
    {
        Entities entities = new Entities();
        return entities.aspnet_Users;
    }

    public IEnumerable<aspnet_Users> AllLockedOutUsers()
    {
        Entities entities = new Entities();
        return entities.aspnet_Users.Include("aspnet_Membership").Where(u => u.aspnet_Membership.IsLockedOut);
    }

Of course (and on purpose) both methods create a separate ObjectContext (in my example: Entities) so that we can actually see the effect of Except.

My front end only needs 3 labels: which appropriately display the count values:

    All users: <asp:Label ID="lblAllUsers" runat="server"></asp:Label>
    Locked out users: <asp:Label ID="lblLockedOut" runat="server"></asp:Label>
    Not locked users: <asp:Label ID="lblNotLockedOut" runat="server"></asp:Label>

THE WRONG WAY:

    Entities entities = new Entities();
    IEnumerable<aspnet_Users> all = AllUsers();
    IEnumerable<aspnet_Users> lockedout = AllLockedOutUsers();
    IEnumerable<aspnet_Users> notlockedout = all.Except(lockedout);

    lblAllUsers.Text = all.Count().ToString();
    lblLockedOut.Text = lockedout.Count().ToString();
    lblNotLockedOut.Text = notlockedout.Count().ToString();

Note how all.Except(lockedout) is called without any IEqualityComparer instance (hence the default comparer is used). The outcome of the execution is that the result contains all the items it contained before the call (actually nothing happened).

THE RIGHT WAY:

An EqualityComparer class is needed which should be inherited from IEqualityComparer(T).

public class MyUserComparer : IEqualityComparer<aspnet_Users>
{
    public bool Equals(aspnet_Users a, aspnet_Users b)
    {
        return a.UserId == b.UserId;
    }

    public int GetHashCode(aspnet_Users user)
    {
        return user.UserId.GetHashCode();
    }
}

A very important notice here is: in the GetHashCode() method you explicitly need to return the HashCode of the property you are comparing, not the object itself. If simply won’t work if you use:

    public int GetHashCode(aspnet_Users user)
    {
        return user.GetHashCode();
    }

In fact this is similar to what the default comparer does, which we definitely want to omit.

Finally, here’s the code that will render the expected results. The difference is in the Except call where we instantiate the MyUserComparer class.

    Entities entities = new Entities();
    IEnumerable<aspnet_Users> all = AllUsers();
    IEnumerable<aspnet_Users> lockedout = AllLockedOutUsers();
    IEnumerable<aspnet_Users> notlockedout = all.Except(lockedout, new MyUserComparer());

    lblAllUsers.Text = all.Count().ToString();
    lblLockedOut.Text = lockedout.Count().ToString();
    lblNotLockedOut.Text = notlockedout.Count().ToString();

Conclusion

In the same was as above you can use IEqualityComparer with other query operators like:

  • Distinct
  • Contains
  • Intersect
  • Union

If anyone has difficulties, don’t hesitate to contact me and if this helped you don’t forget to kick it or shout it or … etc.

DZone it Digg it Submit to StumbleUpon Submit to Technorati Submit to reddit.com Submit to del.icio.us Submit to NewsVine Submit to Furl Submit to BlinkList

Tags: , , ,

LINQ | ASP.NET | .NET

Comments

6/23/2009 6:46:58 AM #

trackback

Enumerable.Except(T) and IEqualityComparer - a little help

You are voted (great) - Trackback from WebDevVote.com

WebDevVote.com |

6/23/2009 6:47:12 AM #

trackback

Enumerable.Except(T) and IEqualityComparer - a little help

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com |

6/23/2009 6:47:35 AM #

trackback

Enumerable.Except(T) and IEqualityComparer - a little help | Arnold Matusz's Blog

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetShoutout |

6/29/2009 11:00:13 PM #

Arpad Kecskes

very useful article!
Thanks for that,

Arpad.

Arpad Kecskes Romania |