si2030
si2030

Reputation: 4045

Entity Framework Core - issue with loading related entities that also contain related entities

I am using Entity Framework Core following Chris Sakell's blog here.

He uses generics to manage his repositories and also a base repository that he uses for all the other repositories.

Part of the base repository has the the following code for the retrieval of a single entity that also downloads related entities using the includeProperties option. Here is the generic code for a retrieving a single item.

public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
    IQueryable<T> query = _context.Set<T>();

    foreach (var includeProperty in includeProperties)
    {
        query = query.Include(includeProperty);
    }

    return query.Where(predicate).FirstOrDefault();
}

I am using it on a client table that has many jobs attached to it.

This is how I structured my code.

    public ClientDetailsViewModel GetClientDetails(int id)
    {
        Client _client = _clientRepository
            .GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);

        if(_client != null)
        {
            ClientDetailsViewModel _clientDetailsVM = mapClientDetailsToVM(_client);
            return _clientDetailsVM;
        }
        else
        {
            return null;
        }
    }

The line:

.GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);

successfully retrieves values for creator state and job.

However, nothing is retrieved for those related entities associated with the "jobs".

enter image description here

In particuar, JobVisits is a collection of visits to jobs.

For completeness I am adding the "job" and "jobvisit" entities below

public class Job : IEntityBase
{
    public int Id { get; set; }

    public int? ClientId { get; set; }
    public Client Client { get; set; }

    public int? JobVisitId { get; set; }
    public ICollection<JobVisit> JobVisits { get; set; }

    public int? JobTypeId { get; set; }
    public JobType JobType { get; set; }

    public int? WarrantyStatusId { get; set; }
    public WarrantyStatus WarrantyStatus { get; set; }

    public int? StatusId { get; set; }
    public Status Status { get; set; }

    public int? BrandId { get; set; }
    public Brand Brand { get; set; }

    public int CreatorId { get; set; }
    public User Creator { get; set; }

    ....
}

public class JobVisit : IEntityBase
{
    ...
    public int? JobId { get; set; }
    public Job Job { get; set; }

    public int? JobVisitTypeId { get; set; }
    public JobVisitType VisitType { get; set; }
}

My question is, how do I modify the repository code above and my GetSingle use so that I can also load the related enitities JobVisit collection and the other related single entities Brand and JobType?

Upvotes: 2

Views: 135

Answers (1)

Guillaume S.
Guillaume S.

Reputation: 1545

It is intended that navigation properties are not necessary retrieved for associated with the "jobs". That is why some properties are null. By default the .Include(property); goes only 1-level deep and that is a good thing. It prevents your query from fetching all the data of your database.

If you want to include multiple levels, you should use .ThenInclude(property) after .Include(property). From the documentation:

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList();
}

My advice is that your method public T GetSingle(...) is nice and I would not change it in order to include deeper levels. Instead of that, you can simply use explicit loading. From the documentation:

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    context.Entry(blog)
        .Collection(b => b.Posts)
        .Load();

    context.Entry(blog)
        .Reference(b => b.Owner)
        .Load();
}

Upvotes: 1

Related Questions