I am running into the "Lazy IO Problem" in Linq and I haven't found a solution that I'm happy with
Setting up the problem
Let say we have SQL tables that looks like
create table Person (
id int primary key not null,
name text not null,
)
create table Dog (
name text primary key not null,
ownerid text primary key not null references Person(name)
)
And in C# we want to use LINQ and Entity Framework to deal with this. Entity Framework classes are defined as partial
so we can extend them to add a .Get(string)
method and this makes for very clean code.
public partial class Dog
{
public static Dog Get(string dogname)
{
using (var db = new MyDataContext())
{
// LINQ is lazy and doesn't load the referenced Person
return db.Dogs.Single(d => d.name == dogname);
}
}
}
Where the problem happens
Now we attempt to use the Dog
object for something
public string DogJson(string dogname)
{
var dog = Dog.Get(dogname);
return JsonConvert.SerializeObject(dog);
}
Since our instance dog
contains dog.Owner
because of the foreign key, JsonConvert
will of course attempt to include it in the json string. But since the DataContext is disposed and LINQ is lazy, an ObjectDisposedException
is of course raised because dog.Person
had not been evaluated when we disposed the DataContext.
In this case, we don't care about the Owner
object at all, we just want to serialize the Dog
to json. What is the best way to do that without?
My solution
I have a solution, but I don't particularly like it. Using projection into an anonamous object and casting back to Dog
, because we are not allowed to explicitly construct a Dog
in the query.
public static Dog Get(string dogname)
{
using (var db = new MyDataContext())
{
var tmpdog = db.Dogs.Where(d => d.name == dogname)
.Select(d => new { name = d.name, ownerid = d.ownerid}).Single();
return new Dog() { name = tmpdog.name, ownerid = tmpdog.ownerid};
}
}
I don't like this solution because it doesn't scale well. This example only has two properties and this gets quickly out of hand. LINQ usually makes for very elegant code and this is not elegant at all. It's also prone to programmer
It sort of feels like I am taking the wrong approach here.