Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Include() with inherited entities

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).

like image 615
Peter Stegnar Avatar asked Feb 23 '10 11:02

Peter Stegnar


1 Answers

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.

like image 191
Enrico Campidoglio Avatar answered Nov 10 '22 04:11

Enrico Campidoglio