Monday, April 06, 2009

LINQ to SQL and Overriding .Equals = not a good idea.

Short version: don’t override .Equals in your LINQ TO SQL entities, unless you really know what “equals” means to your objects!

Long version: on my current project, we’re using LINQ to SQL for our ORM needs, and it’s been working pretty well for us so far.  Pretty well, except for late last week when we realized that we weren’t properly persisting a number of records in our object graph.

We have a fairly complex object model, with some deep graphs of related objects, and have been using the .Add method to add child and grandchild objects into our “Order” object’s graph.  There was one particular “Child” record that was only being created one time per order, when we expected several variations of this child, yet only the first was being saved.

Our object graphs were build correctly, SubmitChanges was doing its job, we weren’t getting any errors, and yet these records just weren’t making it to the database.  After a few conversations with other people that have some LINQ to SQL experience and me trying some of the ugliest hacks I could think of, it dawned on me to try testing another theory I had – create another order-detail that looked similar to an existing order-detail.

Lo and behold, this similar order-detail also did not persist, and it was behaving like the other “exceptional” order-details – they were all in the object-graph, but not in the Insert Changes of my data-context!  Once I was able to see this, I took a closer look at the order-detail partial class we had written, and there I saw an overridden .Equals implementation.  Unfortunately, this .Equals was testing for property values within the order-detail to determine if a detail might match another detail instance, not testing for reference equality, which is evidently important to LINQ to SQL.

So there you have it – when working with LINQ to SQL entities, take care in how you decide to utilize the .Equals method and how you decide to override it.

3 comments:

  1. I stumbled onto this because I have been considering overriding Equals and figured that that modifying the generated code for linq2sql objects wasn't a good idea. However, how else should comparison work for these objects? Perhaps I don't understand something about the linq2sql objects, but without an overriden Equals that compares the Id values of the objects, how does it "properly" compare objects? IE: (l2sObj1 == l2sObj2)

    If you compare two objects it should be ACTUALLY comparing the Id property (primary key in the db model), not the memory reference. How else do you achieve this without overriding the Equals method.

    I stumbled onto this issue because I have databound a combobox with items from this list, and then when I sent the SelectedItem to an item that was retrieved in a separate call, it didn't actually find the right item because the list item's memory doesn't match the item I was setting the SelectedItem to.

    Any ideas?

    ReplyDelete
  2. Your linq2sql objects are partial classes, so you can add your own partial classes to add functionality to your entities. However, if you're using the designer, I'd steer clear of editing the dbml file by hand as you risk losing edits that way.

    As far as overriding .Equals, in my case, our primary key was an identity, so any non-persisted objects would show 0 as the ID prior to insert.

    What tech were you using for your combobox (WPF, WinForm, Asp.Net)? I'm not sure exactly what you mean when you say the memory doesn't match - is there something else causing edits to your bound items and the combobox doesn't reflect these edits?

    ReplyDelete
  3. You could always do something like the following:

    class YourEntityComparer : IEqualityComparer{ ... }

    new YourEntityComparer().Equals(entity1, entity2);

    Of course you'd want to make it a bit cleaner than that, but that's the gyst of what I've done. I needed the comparer to pass to the Distinct() extension, but you could use it like the above as well.

    ReplyDelete