views:

247

answers:

1

In EF eager loading related entities is easy.

But I'm having difficulties including inherited entities when loading data using table-per-type model.

This is my model:

Entities:

  • ArticleBase (base article entity)
    • ArticleSpecial (inherited from ArticleBase)
  • UserBase (base user entity)
    • UserSpecial (inherited from UserBase)
  • Image

Relations are as shown on the image (omitting many columns): alt text

In reality my users are always of type UserSpecial, since UserBase is used in another application, thus we can share credentials. That's the only reason I have two separate tables. UserBase table can't be changed in any way shape or form, because the other app would break.

Question

How am I suppose to load ArticleSpecial with both CreatedBy and EditedBy set, so that both are of type UserSpecial (that defines Image relation)?


I've tried (unsuccessfully though) these options:

1. Using lambda expressions:

context.ArticleBases
    .OfType<ArticleSpecial>()
    .Include("UserCreated.Image")
    .Include("UserEdited.Image");

In this case the problem is that both CreatedBy and EditedBy are related to UserBase, that doesn't define Image navigation. So I should somehow cast these two to UserSpecial type like:

context.ArticleBases
    .OfType<ArticleSpecial>()
    .Include("UserCreated<UserSpecial>.Image")
    .Include("UserEdited<UserSpecial>.Image");

But of course using generics in Include("UserCreated<UserSpecial>.Image") don't work.

2. I have tried using LINQ query

var results = from articleSpecial in ctx.ArticleBase.OfType<ArticleSpecial>()
                  join created in ctx.UserBase.OfType<UserSpecial>().Include("Image")
                  on articleSpecial.UserCreated.Id equals created.Id
                  join edited in ctx.UserBase.OfType<UserSpecial>().Include("Image")
                  on articleSpecial.UserEdited.Id equals edited.Id   
              select articleSpecial;

In this case I'm only getting ArticleSpecial object instances without related properties being set. I know I should select those somehow, but I don't know how?

Select part in my LINQ could be changed to something like

select new { articleSpecial, articleSpecial.UserCreated, articleSpecial.UserEdited };

but images are still not loaded into my context. My joins in this case are barely used to filter out articleSpecial results, but they don't load entities into context (I suppose).

Can anybody provide any help regarding this problem? I think it's not so uncommon.

+2  A: 

This seems to be a limitation in the current version of Entity Framework (1.0) Have a look at this related SO question.

In your case including the related UserCreated and UserEdited properties in the projection is the right solution. However if you also want to populate the Image property on the UserSpecial object, you must be sure to include that as well:

var results = from articleSpecial in ctx.ArticleBase.OfType<ArticleSpecial>()
              select new
              {
                  articleSpecial,
                  articleSpecial.UserCreated,
                  ((UserSpecial)articleSpecial.UserCreated).Image,
                  articleSpecial.UserEdited,
                  ((UserSpecial)articleSpecial.UserEdited).Image
              };

Of course this query builds on the assumption that all ArticleSpecial entities always refer to a UserSpecial entity, otherwise the casting will fail.
If this assumption isn't always true, you could express the same query using the LINQ extension methods and a multi-line lambda function to perform a safe casting:

var results = ctx.ArticleBase
                 .OfType<ArticleSpecial>()
                 .AsEnumerable()
                 .Select(a =>
                  {
                      var userCreated = a.UserCreated as UserSpecial;

                      if (userCreated != null)
                      {
                          var image = userCreated.Image;
                      }

                      var userEdited = a.UserEdited as UserSpecial;

                      if (userEdited != null)
                      {
                          var image = userEdited.Image;
                      }

                      return a;
                  });

In the latter example, you also do not need to include UserSpecial and Image entities in the results. Instead you just need to access the navigation properties on the ArticleSpecial entities during the projection phase in order to force Entity Framework to eager load the related objects.

Enrico Campidoglio
I'm trying to do this same thing but I'm confused because there is no "UserCreated" in the model pictured in the question. Where is that coming from?
AaronLS
Also, the second option with the multiline lambda gives me an error indicating it cannot be converted to an expression tree. See: http://weblogs.asp.net/zeeshanhirani/archive/2008/07/10/not-all-lambdas-can-be-converted-to-expression-trees.aspx
AaronLS
@AaronLS I believe that "UserCreated" and "UserEdited" are really the navigation properties "CreatedBy" and "EditedBy" pictured in the model. They have probably been renamed :-)
Enrico Campidoglio
@AaronLS You are right. That expression cannot be converted to an SQL statement. In this case we'll have to use LINQ to Objects by querying an IEnumerable instead of an IQueryable. I updated my answer.
Enrico Campidoglio