'Can't find single property and navigate related data without IQueryable all properties

I have relational data properties on my objects that I would like to access:

public class Person
{
   public ICollection<Address> Addresses { get; private set; } = new Collection<Address>();
   ...
}

public class Address
{
   ...
   public int PersonId { get; set; }
   public Person Person { get; set; }
   ...
}

The only way I have found to traverse, or even exercise, this navigation property without lazy loading (and pulling in proxies, etc) is to eager load it:

var entityList = await _context.Persons.Where(p => p.Id == request.PersonId)
    .Include(p => p.Addresses)
    .ToListAsync();

which is inconvenient because now I always have to do the following in order to cleanly access the class (and its properties):

var person = entityList.FirstOrDefault();

Every example in the Microsoft docs:

var blogs = context.Blogs
    .Include(blog => blog.Posts)
    .ToList();

they grab every blog entity, instead of just a single one, before loading in related data.

I can't explicitly load because that relies on the public virtual EntityEntry<TEntity> Entry<TEntity>(TEntity entity) which I can't accesses via my injected DbContext interface.

Is there a cleaner way to do this without the IQueryable where expression? I simply want to find my single entity and navigate its properties.



Solution 1:[1]

From your question I think you are confusing eager/lazy loading with the LINQ execution methods. For something like this:

var entityList = await _context.Persons.Where(p => p.Id == request.PersonId)
    .Include(p => p.Addresses)
    .ToListAsync();
var person = entityList.FirstOrDefault();

You really just want 1 person with their addresses, then use:

var person = await _context.Persons
    .Include(p => p.Addresses)
    .SingleOrDefaultAsync(p => p.Id == request.PersonId);
    

This will fetch the person with its related addresses. It would be better to use SingleAsync rather than the "OrDefault" variant if you expect there to always be a record. (Throw an exception if an invalid PersonId is sent)

Include determines the eager loading. ToList just tells Linq to expect possible multiple matching rows. Where you might have encountered issues was trying to use Find on the DbSet and it resorting to lazy-loading for the Addresses collection.

The First methods should only ever be used in conjunction with an OrderBy clause where you expect possible multiple matching rows, to ensure the query is predictable.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Steve Py