Wednesday, January 14, 2009

Lazy Loading Considered Harmful

Yesterday, I read a blog post from Scott Bellware's blog regarding lazy loading in O/R mappers. I just wanted to bring your attention to the reasoning behind his approach towards choosing which relational associations to include and which not to include in the equivalent object-oriented model.

Wednesday, January 7, 2009

LINQ to SQL Common Base Class Problem

if you are a fan of LINQ to SQL, you may remember Dinesh's post where he mentioned the possibility to create and use a common base class for all your entities. I really like the idea, and to clarify, I have been using it in one of my projects even before seeing his post regarding the subject.

But unfortunately, yesterday, while playing with a piece of code, I happened to face a problem that I want to describe in this post. Here it is:

Make the following simple LINQ to SQL model:
public abstract class Entity
{
public virtual long ID { get; set; }
}

[
Table]
public partial class Order : Entity
{
[
Column(Name="OrderID", IsPrimaryKey = true)]
public override long ID { get; set; }
}

And query your model with the following code:

var db = new DBDataContext("someconnectionstring");
var orders = db.Orders.Where(o => o.ID == 10).ToList();

Surprisingly, you'll get an InvalidOperationException with the message "class member Entity.ID is unampped".

Not interesting enough? Change ToList to SingleOrDefault, change equality operator to some other operator, or make the predicate more complex, and you won't get the exception anymore. Just in the case that you query your entity via PK and materialize it as a collection, you'll get the exception described above.

In my opinion, it clearly IS A BUG.

But what about the workaround? Analyzing the problem more thoroughly, I found out that the constructed MemberExpression for o.ID was pointing to Entity.ID instead of Order.ID. Ummmm . . . I think it's not the best way to create the expression tree when the programmer clearly describes that he/she needs Order.ID. And LINQ to SQL, does not seem to handle this expression correctly in some simple situations (BTW, inspecting the stack trace of the exception, it seems that the bug is somewhere where LINQ to SQL tries to check its identity cache for the requested entity).

So, as the first try, I tried to make the expression point to Order.ID:


var orders = db.Orders.Where(o => ((Order)o).ID == 10).ToList();


And it worked! Unfortunately, the problem with this approach is the explicit cast to Order, that in addition to be a very ugly syntax, for which you may not find an explanation if you have designed a LINQ-to-SQL-based generic Repository as a framework to be used by other programmers, and you wanna tell them to obey the rule.

So, I decided to find another solution. And I happened to find the following one:

var orders = db.Orders.Select(o => o).Where(o => o.ID == 10).ToList();


i.e. add a dummy identity projection to the query expression and you won't get the exception anymore.

UPDATE:
I submitted the issue to Microsoft via Connect. feel free to validate it if you find it important.



kick it on DotNetKicks.com