Search code examples
c#abstract-classentity-framework-5eager-loadingnavigation-properties

Problem with Eager Loading Nested Navigation Based on Abstract Entity


I a portion of my EF model that looks like this:

enter image description here

Summary:

  • Location has many Posts
  • Post is an abstract class
  • Discussion derives from Post
  • Discussions have many Comments

Now, the query i'm trying to achieve:

Get information about Location Id 1234, including any Discussions and Comments associated with those Discussions.

I can get discussions and the comments like this:

var discussions = ctx.Posts
                     .OfType<Discussion>()
                     .Include(x => x.Comments)
                     .ToList();

But i can't seem to get it based on the Posts navigation on the Location entity.

I've tried this:

var locationWithDiscussionsAndComments = ctx
                    .Locations
                    .Include(x => x.Posts
                                   .OfType<Discussion>()
                                   .Select(y => y.Comments))
                    .SingleOrDefault();

Which compiles, but i get the error:

System.ArgumentException: The include path expression must refer to a property defined by the entity, optionally also with nested properties or calls to Select. Parameter name: path

Any ideas? I could probably go "backwards" from the Posts:

var locationWithDiscussionsAndComments = ctx
                   .Posts
                   .Include(x => x.Location)
                   .OfType<Discussion>()
                   .Include(x => x.Comments)
                   .Where(x => x.LocationId == 1234)
                   .Select(x => x.Location)
                   .ToList();

But that is both hairy and semantically wrong in terms of my repositories (i shouldn't have to go through a post repository to get information about a location).

Any ideas?

EDIT

So after having a bigger think about it, i realized that OfType<T> is a filter operation. As as we know, EF does not support filtering with eager loading. The only options are retrieving everything, or using anonymous type projection.

No way i can retrieve everything, as there is far too much meta data involved. So i'm attempting the anonymous type projection.


Solution

  • The new Query method might help you:

    var location = context.Locations.SingleOrDefault();
    
    context.Entry(location)
           .Collection(l => l.Posts)
           .Query()
           .OfType<Discussion>()
           .Load();
    


    Repository Implementation:

    We can add a new LoadProperty generic method to the Repository<T> class that leverages this new QUery method:

    public void LoadProperty<TElement>(T entity, 
            Expression<Func<T, ICollection<TElement>>> navigationProperty,
            Expression<Func<TElement, bool>> predicate) where TElement : class
    {
        _context.Set<T>().Attach(entity);
    
        _context.Entry(entity)         
                .Collection(navigationProperty)
                .Query()
                .Where(predicate)
                .Load();
    }
    

    Using the LoadProperty method:

    Location location = _locationRepository.Find(1);
    _locationRepository.LoadProperty(location, l => l.Posts, p => p is Discussion);