Lazy Loading EF Core, load daughter entities and subfiles?

8

I have a Web Api that I am developing using DotNet Core and EF Cor but it has Lazy Loading active, but in some cases I need to load daughters entities and daughters of daughters , I created a generic repository to do CRUD in the database, I can load the daughters , but daughters daughters p>

The generic Get function that loads the daughters.

public IQueryable<T> Get<T>(Expression<Func<T, bool>> predicate, params string[] include) 
     where T : class
{
        IQueryable<T> result = this._context.Set<T>().Where(predicate);

        foreach (string item in include)
        {
            result = result.Include(item).AsQueryable();
        }

        return result.AsQueryable();
}

Using this function would be

var entidade this.repository.Get<Entity>(x => x.Id == 1, "Filha1", "Filha2")
                 .FirstOrDefault();

entidade.Filha1 // Carrega Corretamente
entidade.Filha2 // Carrega Corretamente
entidade.Filha1.SubFilha1 // Quero conseguir carregar esse nível da entidade

Generic Repository Deployment:

using API.Model.Context;
using API.Model.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace API.Model.Repositories
{
    public class Repository : IDisposable, IRepository
    {
        private readonly ApiContext _context;

        public Repository(ApiContext context)
        {
            this._context = context;
        }

        public void Add<T>(T obj) where T : class
        {
            this._context.Set<T>().Add(obj);
        }

        public void Delete<T>(Func<T, bool> predicate) where T : class
        {
            this._context.Set<T>().Where(predicate).ToList()
                .ForEach(del => this._context.Set<T>().Remove(del));
        }

        public void Dispose()
        {
            this._context.Dispose();
        }

        public void Edit<T>(T obj) where T : class
        {
            this._context.Entry<T>(obj).State = EntityState.Modified;
        }

        public int ExecuteSql(string psSql, params object[] poParams)
        {
            return _context.Database.ExecuteSqlCommand(psSql, poParams);
        }

        public T Find<T>(params object[] key) where T : class
        {
            return this._context.Set<T>().Find(key);
        }

        public IQueryable<T> Get<T>(Expression<Func<T, bool>> predicate) 
               where T : class
        {
            return this._context.Set<T>().Where(predicate)
                       .AsQueryable();
        }

        public IQueryable<T> Get<T>(Expression<Func<T, bool>> predicate,
                                    params string[] include)
             where T : class
        {
            IQueryable<T> result = this._context.Set<T>().Where(predicate);

            foreach (string item in include)
            {

                result = result.Include(item).AsQueryable();
            }

            return result.AsQueryable();
        }

        public IQueryable<T> GetAll<T>() where T : class
        {
            return this._context.Set<T>();
        }

        public void Save()
        {
            this._context.SaveChanges();
        }
    }
}

Parent Entity

    [Table("Booking")]
    public class Booking
    {
        public int Id { get; set; }
        public virtual ICollection<RoomStay> RoomStay { get; set; }
    }

Daughter Entity

    [Table("RoomStay")]
    public class RoomStay
    {
        public int Id { get; set; }
        public int BookingId { get; set; }
        [ForeignKey("BookingId")]
        public virtual Booking Booking { get; set; }

        public int? RoomTypeId { get; set; }
        [ForeignKey("RoomTypeId")]
        public virtual RoomType RoomType { get; set; }

        public virtual ICollection<RoomStayGuest> RoomStayGuest { get; set;                 }
        public virtual ICollection<RoomStayFnrh> RoomStayFnrh { get; set; }
    }

I need to load the RoomType entities and the RoomStayGuest and RoomStayFnrh collections

    
asked by anonymous 27.02.2018 / 14:13

2 answers

8

To include multiple levels of related data, use the ThenInclude() method.

Examples

Entity Framework - Related Data

var example = context.Parent
                            .Include(x => x.Child)
                            .ThenInclude(g => g.Grandson)
Entity Framework - Related data chaining

var example = context.Parent
                            .Include(x => x.Child)
                            .ThenInclude(g => g.Grandson)
                            .ThenInclude(gg => gg.GreatGrandson)

Generic method

public IQueryable<T> Get<T>(Expression<Func<T, bool>> predicate, Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null) 
     where T : class
{
        IQueryable<T> result = this._context.Set<T>().Where(predicate);

        if (include != null)
           result = include(result);

        return result.AsQueryable();
}

Using the generic method

var example = this.repository.Get<Entity>(
                                           x => x.Id == 1, 
                                           r => r
                                                     .Include(c => c.Child)
                                                     .ThenInclude(g => g.Grandson))
                                                     .FirstOrDefault();

For related loading information, go to: a>

    
27.02.2018 / 14:19
2

You just put the relationships separated by a period ( . ), example:

var entidade this.repository.Get<Entity>(x => x.Id == 1, "Filha1.SubFilha1")
                            .FirstOrDefault();

You will not even need to change the code of repository just to pass the name of the Filha1.SubFilha1 relationships, and so on.

The code Include and ThenInclude would be ideal, but you would have to change the code of your repository so that the even if it can support those methods with some logic different from the current one.

I was asked by @GabrielColleta if this works, in the Entity Framework Core version and is an interesting point to say, yes this works perfectly and is contained in the most current version which is one of the things brought from the previous version, Include with paramento Text ( string ) resolves relations of relations, and I believe it is useful to solve this problem. This Include with Text% ( string ) does not have ThenInclude as used in the other that is a Lambda Expression.

  

I need to load the RoomType entities and the RoomStayGuest and RoomStayFnrh collections

var entidade this.repository
          .Get<Entity>(x => x.Id == 1, 
                       "RoomStay.RoomType", 
                       "RoomStay.RoomStayGuest",
                       "RoomStay.RoomStayFnrh")
          .FirstOrDefault();

Tests performed with Entity Framework Core 2.0.1

27.02.2018 / 14:57