views:

48

answers:

1

Hi Guys,

I have an EF4 Model that is built with abstract entities/classes:

alt text

Notice how State entity has a navigational property called Country.

Note: I have lazy-loading disabled, so i must eager-load on demand.

Now, if i have the following method:

public Location FindSingle(int id)
{
   return _repository.Find().WithId(id).SingleOrDefault();
}

This does not return any associations by default. But how can i dynamically eager-load the associations when i explicitly want to?

I cannot do this:

return _repository.Find().WithId(id).Include("Country").SingleOrDefault();

As i am working with an abstract class called Location, which does not have a navigational property called "Country". I do not know what the derived type is until i actually execute the query with .SingleOrDefault.

So, here's what i've had to do:

public Location FindSingle(int id, bool includeAssociations = false)
{
    var location = _repository.Find().WithId(id).SingleOrDefault();
    return includeAssociations
                       ? LoadAssociation(location)
                       : location;
}

private Location LoadAssociation(Location location)
{
   // test derived-type, e.g:
   var state = location as State;

   if (state != null) 
      return _repository.Find().OfType<State>().Include("Country").WithId(id).SingleOrDefault();
}

Essentially, i'm doing 2 identical calls. Does it work? Yes. Is it pretty? No, and it's not really "eager-loading".

I know this is not the correct solution, can you guys think of the proper one? (and yes i know i can use stored procedures, but i really want to go through my repository/model here, so the entities are correctly attached to the graph, ready for editing).

Even though .Include causes a Left Outer Join, the problem is i'm working off the "Locations" entity set. I need to .Include on the "State", but "States" belong to the "Locations" entity set (derived classes belong to their parent's entity set).

So i guess my question is actually pretty generic - how do we do a .Include on a child of an abstract entity, when we don't know what the child is beforehand?

Remember, i cannot use .OfType<T>() first (and then the .Include on the derived type), as i don't know what T is (and neither does the calling code), hence generics cannot be utilized here.

+1  A: 

The real issue here is that you are holding an Id but you don't know what it represents: it could be for a Country or for a State. At some time you presumably did know what it was, but you didn't maintain that information.

After loading a Location from the repository, how do you know which type to cast it to in order to access the relevant relationship property on it? Presumably you have to use as or is with cast and then you can access these properties. Again that kinda smells bad.

The best option here would be to maintain both the Type and the Id of a Location object so you can reload it using the appropriate repository method.

Another option would be to instead move the relationship up to the Location class so that every Location object has a .Parent Location and a .Children Locations collection. Now you can include them in your Include and when you find you have a State you know to look at .Parent and when you have a Country you know to look at .Children. Use null for no parent and an empty collection for no children. Now when you add continents or cities to your Location class you'll be in great shape to use the same model.

A final option which you can sometimes use in situations like this is to Union two queries after converting them to the common base type, e.g. something like:-

context.Locations.OfType<Country>().Include("States").Cast<Location>().Union(context.Locations.OfType<State>().Include("Countries).Cast<Location>());
Hightechrider
Thanks for your answer. "ID" was just an example. In reality i'm attempting to retrieve a location based on a unique URI (think of a landing page). So basically all i have is a URI, and i need to get info about that "location". I'm not sure if i can move the relationship up to the Location class. These navs are represented physically by FK's on the "State" table (Sql Server). How can a FK have a FK to itself? Yes the UNION is another option, but a costly one, these tables are massive. Thanks for your answer though, will give it some thought.
RPM1984
In SQL terms you'd create a table for the association with `ParentId` and `ChildId` and you'd create FKs for each back to the Location table. If you make this association in the EF Designer it will create a table like that for you. BTW if you are worried enough about performance to want to eager load you should probably move to TPH and store both State and Country in the same table using a discriminator column. All those joins will kill performance otherwise.
Hightechrider
Yes, there are plenty of ways we could have done this. The problem is we're working off a legacy database. At this stage we're just doing TDD, when we know what the UI will need, we'll rethink our design. For now, this is a good answer with a lot of options. +1 and accepted, cheers.
RPM1984