4

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.

benedikt
  • 162
  • 9
  • 1
    If you're really trying to serialize `dog` to JSON, does lazy loading even make sense? IMHO if you're serializing an object to hand it off to a consumer, you'd want to eager load the object. Even if you avoid the serialization exception ... what is the consumer going to do with an incomplete object? Or am I reading too much into your example? – David Dec 03 '14 at 19:05
  • @David - I took a real world example from my code and simplified it for this thread, but you do still raise a valid point. My point-of-view is that that `dog.ownerid` (an `int`) is sufficient for the consumer, that can then load the `Person` if needed rather than accessing `dog.Owner`. I'll give this some thought. – benedikt Dec 03 '14 at 20:12
  • 2
    The real issue is that your caller should dictate the lifetime of the DbContext, not the callee, since the DogJson method is defining the unit of work. Ideally you should be passing a DbContext instance into the static `Get` method. – Brian Driscoll Dec 03 '14 at 20:17
  • In the context of a MVC Web API, should the controller create the DbContext ? – benedikt Dec 03 '14 at 20:20
  • @benedikt The unit of work should determine the creation of the DbContext. You can be fancy about it and use some sort of pattern if you want, but that's the long and short of it. – Brian Driscoll Dec 03 '14 at 20:24
  • @benedikt I see what you mean about `dog.ownerid` being sufficient -- but then your `Dog` class should contain the owner ID (`int`) rather than a reference to a `Person` object, no? Or, if you do want to reference a `Person`, you should be eager loading. Either way, something feels inconsistent. – David Dec 03 '14 at 21:37
  • @benedikt it's pretty typical for the controller to create the DbContext. (Or even better, you inject a repository interface into the controller -- the "production" implementation of which would create the DbContext and fetch the appropriate object(s) for you.) But as Brian already noted this really depends on what your design says the unit of work is. – David Dec 03 '14 at 21:43
  • @David - I completely agree, but this is what entity framework generates automatically if the source table has a foreign key reference. Ideally, I'd like a way to force this behaviour away. Good points on where to create the DbCotext. – benedikt Dec 03 '14 at 22:00
  • @BrianDriscoll - add that as an answer so I can vote and accept? – benedikt Dec 03 '14 at 22:01

3 Answers3

2

I have had this problem before too, but luckily entity framework provides an easy way around it. You can disable the lazy loading and creation of a dynamic proxy before you query. This will allow the json serializer to run without exception.

public static Dog Get(string dogname) 
{
    using (var db = new MyDataContext())
    {
        db.Configuration.ProxyCreationEnabled = false;
        db.Configuration.LazyLoadingEnabled = false;
        return db.Dogs.Single(d => d.name == dogname);
    }
}
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • Maybe I'm suffering from a case of wanting to keep my cake and eat it too, but what if I want to keep the lazy loading? – benedikt Dec 03 '14 at 18:47
  • @benedikt - When you return the record here, the using statement ends and the database context is disposed. Any attempt to lazy load after that point will cause an exception anyway. – Travis J Dec 03 '14 at 18:48
  • I know, thats why I said that the ObjectDisposedException made sense. I'm wondering if there is a way to be able to make a clean projection and still keep the lazy loading. – benedikt Dec 03 '14 at 20:09
  • 1
    @benedikt - If you wanted the Person loaded, you should eager load it with `.Include( d => d.Person )` prior to the `.Single` call. – Travis J Dec 03 '14 at 20:11
  • 1
    I want to **opposit** of what you are suggesting. I want `.Exclude`, which doesn't exist. I would want to get rid of the reference to `Person`, rather than eagerly loading it. – benedikt Dec 03 '14 at 20:19
1

The real issue is that your caller should dictate the lifetime of the DbContext, not the callee, since the DogJson method is defining the unit of work. Ideally you should be passing a DbContext instance into the static Get method.

So, rather, your Get code should look more like this:

public static Dog Get(string dogname, MyDataContext db)
{
    var result = db.Dogs.SingleOrDefault(d => d.name == dogname);
    return result;
}

Then, you can do all of the DTO modifications in your caller, since that's really your unit of work:

public string DogJson(string dogname) 
{
    using (var db = new MyDataContext())
    {
        var dog = Dog.Get(dogname, db);
        var dogDTO = new Dog { name = dog.name, ownerid = dog.ownerid };
        return JsonConvert.SerializeObject(dogDTO);
    }
}
Brian Driscoll
  • 19,373
  • 3
  • 46
  • 65
  • While this does solve my initial encounter with the lazy io problem, this still requires manual construction of a new Dog (or anonamous type) before json serialization. Since this is the best answer, I will accept it (and accept this as a tedious convention in C#) – benedikt Dec 05 '14 at 12:13
1

See this question about ignoring attributes when serializing objects in Newtonsoft, which I believe is the library that contains the JsonConvert.SerializeObject function.

To summarize the most popular answer is to add the [JsonIgnore] attribute to those fields you do not want to be serialized. In your case, that is Owner so the code would be:

[JsonIgnore]
public Person Owner{ get; set; }

The original poster ended up using virtual properties as noted in their own answer.

John Cummings
  • 1,949
  • 3
  • 22
  • 38