Including navigation properties from Entity Framework TPH classes

Ken Smith picture Ken Smith · Jul 22, 2009 · Viewed 8.7k times · Source

I've got an EF hierarchy that (dramatically simplified) looks something like this:

class Room { EntityCollection<Session> Sessions; }
class Session { EntityCollection<Whiteboard> Whiteboards; EntityReference Room; }
class Whiteboard { EntityCollection<WhiteboardShape> WhiteboardShapes; EntityReference Session; }
abstract class WhiteboardShape { EntityReference Whiteboard; }
class WhiteboardShapeEllipse : WhiteboardShape { }
class WhiteboardShapePolyline { WhiteboardShape { EntityCollection<PolylinePoint> PolylinePoints }
class PolylinePoint { EntityReference<WhiteboardShapePolyline> WhiteboardShapePolylineReference; }

In other words, a Room can contain multiple sessions; each Session can contain multiple Whiteboards; and each Whiteboard can contain multiple WhiteboardShapes. These shapes can be of various types, including a WhiteboardShapePolyline, which itself can contain multiple PolylinePoints.

When a remote user initially connects to the room, I need to pass the entire object graph to that user, and I'm trying to figure out how to load that graph from the database into memory as efficiently as possible.

Now, of course, the EF allows for you to do eager loading, like so:

      Room room = ctx.Room
            .Include("Sessions.Whiteboards")
            .FirstOrDefault(r => r.OwnerID == ownerUserID && r.Name == roomName);

But Include() doesn't let me load up the PolylinePoints. Specifically, if I try:

        Room room = ctx.Room
            .Include("Sessions.Whiteboards.WhiteboardShape.PolylinePoint")
            .FirstOrDefault(r => r.OwnerID == ownerUserID && r.Name == roomName);

I get the exception "A specified Include path is not valid. The EntityType 'SlideLinc.Model.WhiteboardShape' does not declare a navigation property with the name 'PolylinePoint'."

Nor does this work:

.Include("Sessions.Whiteboards.WhiteboardShapePolyline.PolylinePoint")

Nor does this:

.Include("Sessions.Whiteboards.WhiteboardShape.WhiteboardShapePolyline.PolylinePoint")

Nor any other way of framing the navigation path that I can think of.

The way that I've ended up doing it sure seems like a hack to me:

        // Make sure we've got everything loaded.
        if (room != null)
        {
            if (!room.Sessions.IsLoaded) { room.Sessions.Load(); }
            foreach (Session session in room.Sessions)
            {
                if (!session.Whiteboards.IsLoaded) { session.Whiteboards.Load(); }
                foreach (Whiteboard whiteboard in session.Whiteboards)
                {
                    if (!whiteboard.WhiteboardShape.IsLoaded) { whiteboard.WhiteboardShape.Load(); }
                    foreach (WhiteboardShape shape in whiteboard.WhiteboardShape)
                    {
                        if (shape is WhiteboardShapePolyline)
                        {
                            WhiteboardShapePolyline polyline = (WhiteboardShapePolyline)shape;
                            if (!polyline.PolylinePoints.IsLoaded) { polyline.PolylinePoints.Load(); }
                        }
                    }
                }
            }
        }

It works, but it's a lot more code than I want, and it's a whole bunch more database accesses than I want.

The closest answer I've found is here, but my poor Linq-starved brain can't figure out how to translate the example code into the more complicated hierarchy that I've got; plus, the sample code at that link is damned ugly and difficult to understand. I don't really want my entire object hierarchy depending on an obscure and invisible side-effect of how the EF internally constructs its hierarchies.

Any other suggestions?

Answer

Craig Stuntz picture Craig Stuntz · Jul 22, 2009

I would probably use projection for this. Instead of returning entity types, project onto lightweight data transfer objects or anonymous types. When you project (e.g., with a LINQ query), the loading happens automatically. You don't need to specify an Include in this case.