views:

284

answers:

2

How do you lazy load nested lists without actually executing the query? Using a very basic example, say I have:

class CityBlock {
     IList<Building> BuildingsOnBlock;
     Person BlockOwner;
}

class Building {
     IList<Floor> FloorsInBuilding;
}

class Floor {
     IList<Cubicle> EmployeeCubicles;
}

Class Cubicle {
     System.Guid CubicleID;
     Person CubicleOccupant;
}

And then in my repository layer, I have the following method:

GetCityBlocks()

And then in the Service layer I'll have GetCityBlocksByOwner, where I utilize an extension method to get the city blocks owned by a specific person, say we just want Guido's blocks:

GetCityBlocks().ForOwner("Guido")

If we do a .ToList() in the repository it's going to execute the queries - that's going to be ridiculous since we don't know who's blocks we're getting at that level. So the question is, how do we do this efficiently?

Let's assume there are 50000 block owners, and some 1000000 city blocks, loading all of them is not an option. Using IQueryables won't work due to the nesting (without extreme hacking, at least that I'm aware of). Also, if I try to use something like Rob Conery's LazyList, then we essentially have a leak from our DAL into our domain models, which could be very very bad in the future.

So how do I go about doing this correctly?

  • Is it a matter of determining the correct context? If so, would we do this in the Repository Layer, or the Service layer?
  • Do I half meld together the Service layer and my Repository layer to get very specific service methods?
  • Or am I missing something entirely? (still relatively new to the Linq2Sql thing, which is being phased out anyways so...)

Edit: In the repository pattern, we're currently mapping to our domain objects, so it would look something like this:

public IQueryable<CityBlock> GetCityBlocks(){
    var results = from o in db.city_blocks
                  let buildings = GetBuildingsOnBlock(o.block_id)
                  select new CityBlock {
                      BuildingsOnBlock = buildings,
                      BlockOwner = o.block_owner
                  };
    return results;
}

For this to work, we'd have to make the buildings get a .ToList(), unless we make that actual field in the CityBlock object an IQueryable - which doesn't seem right, because it seems as if too much power would be granted to anyone who accesses the CityBlock.BuildingsOnBlock field. Is this mapping to our domain objects something we should maybe do up in the service layer?

+1  A: 

You do it by returning IQueryables instead of ILists.

ToList() causes the queries to execute immediately, because a conversion must be performed from IQueryable to IList.

As long as you are returning IQueryables lazy loading should defer execution until the data is actually needed, i.e. when ToList() is called.

I can't find a reference at the moment, but it is my understanding that, if you do it this way, linq to sql has the opportunity to optimize the SQL it sends to the server. In other words, it will ultimately read these records:

GetCityBlocks().ForOwner("Guido")

rather than these records:

GetCityBlocks()
Robert Harvey
But having IQueryables in the domain model seems like a problem due to the fact that you're doing so JUST for the DAL execution, not because it's something that's part of that domain model. The IQueryable is not necessarily something that you need for querying those fields/properties/class-objects in that object. Or am I still looking at this issue incorrectly?
MunkiPhD
There is the viewpoint that, if you return IQueryables to the service layer, then the service layer doesn't have a "well-defined interface" (you're giving it too much power to manipulate the query). So there is a tradeoff. It will make your repository much simpler (because you don't need methods for every possible query), and it does have the virtue of doing what you want. I'm not a patterns lawyer, so I'm not going to tell you you can't do it this way, but someone else might.
Robert Harvey
I agree with you in that loosening the controls a bit, but what if we're mapping to our own objects in the repository? Is this something we should do elsewhere? (see my edit)
MunkiPhD
If you return an IQueryable in GetBuildingsOnBlock, you can still constrain it by only returning the needed fields and including a "where" clause in its underlying Linq query, so I would say you're not exposing too much. The only thing the consumer (service layer) could do is constrain it more; it's not like it can expand your IQueryable result.
Robert Harvey
So where would we do the actual binding from the linq models to our domain models? GetBuildingsOnBlock() should be creating the Building Objects (or the query for them), which would be the list<Floor>, which in turn, each has a List<Cubicle>. Linq won't know what IDs to use for those objects since the query hasn't been executed yet.
MunkiPhD
I think you should make List<Floor> and List<Cubicle> IQueryables, and let Linq figure out when the queries need to be executed. You can always turn them back to lists at any time.
Robert Harvey
A: 

You could try a different approach in mapping your domain objects to make it work. The problem is that no matter what you do (unless you change your lists to IQueryables in your domain objects) you will end up ToList()ing while you're mapping.

The other way it to let linq2Sql do the mapping to your POCOs by creating a custom datacontext and not using the designer for the mapping :), that way you keep your domain model clean and let linq2Sql populate the dependencies at the correct time. Note that going into this route has its own problems, but it can be done.

Here's a link to get you started on this route

Achieving POCO s in Linq to SQL

Jaime