Lazy Loading EF Core, upload daughter entities and daughters?

Asked

Viewed 995 times

9

I have a Web Api I’m developing using the DotNet Core and EF Cor but is with the Lazy Loading active, only that in some cases I need to load the entities daughters and daughters of daughters, I created a generic repository to perform CRUD on the bench, I can carry the daughters, but the daughters of daughters can’t.

The generic Get function that carries 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

Implementation of the Generic Repository:

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; }
    }

Entity Filha

    [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 upload Roomtype entities and Roomstayguest and Roomstayfnrh collections

  • This is the big problem of making a repository with the Entity Framework. Notice that you have a .Where() simple along with a .include(), which has become somewhat complex due to its repository. More about: link

  • have you tried to use your logic in your loop to load the next level ? that is after you do the result = result.Include(item). Asqueryable(); and get the entity.Filha1 novate the loop to include the next level.

  • 1

    @Gabrielcoletta very interesting the positioning on the repositories in this link

  • @Lucasriechelmannramos which of the answers you used to solve your problem?

  • 1

    @Virgilionovic your reply, was the best for my situation, thank you

  • @Lucasriechelmannramos because I’m asking, because the answer with acceptance is in trouble ... that was my concern.

Show 1 more comment

2 answers

9


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

Examples

Entity Framework - Dados Relacionados

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

Entity Framework - Chaining of related data

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();
}

Use of 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 more information about loading related data, visit: Loading of Related Data

  • That way I would have to implement a repository for each entity, I’m looking for a generic way to use the generic repository, if I can’t find a way I’ll have to use that one, but thanks for the help.

  • Really. Have you ever considered using the "include" parameter as an Iincludablequeryable? In this way it would be possible to perform using a generic repository.

  • 1

    Just don’t use it ref is a reserved word!

  • 1

    Really, I hadn’t noticed. Thank you.

  • @Jeangustavoprates if you did the test with this method génerico, with me that there did not happen no?

  • I made the correction.

  • @Jeangustavoprates found interesting the implementation but is giving error in the line "result = include(result);" the include does not allow to use as a method, to see here what can be.

  • You entered the update I did?

  • @Jeangustavoprates worked missing update method thanks

  • @Jeangustavoprates I did the test with this code because I saw it now, and I found it very strange the nomenclature required by Include is not this, did it by any chance work with you? I did some test by Trigger and it doesn’t work... !!!

  • @Virgilionovic It is the same nomenclature. Perhaps for a more elegant code it is necessary to create an Extension Method. It works, can test.

  • I have tested and it doesn’t work. Because the Include method requires another type of code.

  • The Include method used in the answer is an extension located in the Entityframeworkqueryableextensions class and requires only that the type that will be extended is Iqueryable<Tentity>. The code snippet in the answer works. If in doubt, please open a question.

  • @Next Jeangustavoprates I have no doubt ... I’m just trying to help, but, all right no problems ...

Show 9 more comments

2

You just put the relationships apart by one point (.), example:

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

you won’t even need to change the code of repository is just pass this way the name of relationships Filha1.SubFilha1 and so on.

The code Include and Theninclude would be ideal, but, you would have to change the code of your repository so that it can support these methods with some logic other than the current one.

I was asked by @Gabrielcolleta if this works, in the version Entity Framework Core and it’s up to an interesting point to say, yes this works perfectly and is contained in the most current version which is one of the things brought up from the previous version, this method Include with Text (string) solves the relationships of relationships, and I believe it is useful to solve this problem. This Include with Text (string) doesn’t have the ThenInclude as is used in the other which is an expression Lambda.

I need to upload Roomtype entities and 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

References:

  • 1

    Load multiple levels works in Entity Framework Core .Include("Filha1.SubFilha1")? I found nothing of the kind.

  • 1

    @Gabrielcoletta yes carries, I study a long time and this functionality comes from the previous version and continues, even before posting this answer made the test ... it worked and generated the SQL with the Inner Join with all relations that have been requested!!!

  • 1

    @Virgilionovic this was the first thing I had tested before searching another way of doing, but is giving the Object Name is invalid, I will perform some tests to see if I am not wrong in the hierarchy, but thanks for the tip

  • @Lucasriechelmannramos puts in your question the 3 classes I tell you where you are wrong!

  • What version of Entity Framework Core?

  • @Gabrielcoletta 2.0.1 to which I made this reply.

  • @Gabrielcoletta 2.0.1

  • @Virgilionovic updated the response with the entities

  • @Lucasriechelmannramos I did all the possible tests and it works perfectly, and added the answer to your problem! take a look

  • @Virgilionovic for the entity Roomtype worked for the other two no, I believe that for Icollections must be otherwise.

  • It’s not like that even @Lucasriechelmannramos .... I did the tests as I said, for aggregation and collection... it worked perfectly!

  • @Lucasriechelmannramos take a look at your relationships !!! maybe that’s it too ... and ask the question as you did!

  • 1

    @Virgilionovic ok I will check here and update the question when solve

  • 1

    solved thank you very much, missing add Migrations and update the database

Show 9 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.