Saltar al contenido

Usando el patrón de repositorio para cargar entidades ansiosas usando ThenIclude

Solución:

Esta es una pregunta un poco vieja, pero como no tiene una respuesta aceptada, pensé en publicar mi solución a esto.

Estoy usando EF Core y quería hacer exactamente esto, acceder a la carga ansiosa desde fuera de mi clase de repositorio para poder especificar las propiedades de navegación para cargar cada vez que llamo a un método de repositorio. Como tengo una gran cantidad de tablas y datos, no quería un conjunto estándar de entidades de carga ansiosa, ya que algunas de mis consultas solo necesitaban la entidad principal y otras necesitaban todo el árbol.

Mi implementación actual solo admite IQueryable método (es decir. FirstOrDefault, Where, básicamente las funciones lambda estándar) pero estoy seguro de que podría usarlo para pasar a sus métodos de repositorio específicos.

Empecé con el código fuente de EF Core EntityFrameworkQueryableExtensions.cs que es donde el Include y ThenInclude Se definen los métodos de extensión. Desafortunadamente, EF usa una clase interna IncludableQueryable para contener el árbol de propiedades anteriores para permitir un tipo fuerte incluye posteriores. Sin embargo, la implementación de esto no es más que IQueryable con un tipo genérico adicional para la entidad anterior.

Creé mi propia versión llamé IncludableJoin eso toma un IIncludableQueryable como parámetro de constructor y lo almacena en un campo privado para su posterior acceso:

public interface IIncludableJoin<out TEntity, out TProperty> : IQueryable<TEntity>
{
}

public class IncludableJoin<TEntity, TPreviousProperty> : IIncludableJoin<TEntity, TPreviousProperty>
{
    private readonly IIncludableQueryable<TEntity, TPreviousProperty> _query;

    public IncludableJoin(IIncludableQueryable<TEntity, TPreviousProperty> query)
    {
        _query = query;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator<TEntity> GetEnumerator()
    {
        return _query.GetEnumerator();
    }

    public Expression Expression => _query.Expression;
    public Type ElementType => _query.ElementType;
    public IQueryProvider Provider => _query.Provider;

    internal IIncludableQueryable<TEntity, TPreviousProperty> GetQuery()
    {
        return _query;
    }
}

Tenga en cuenta el interno GetQuery método. Esto será importante más adelante.

A continuación, en mi genérico IRepository interfaz, definí el punto de partida para la carga ansiosa:

public interface IRepository<TEntity> where TEntity : class
{
    IIncludableJoin<TEntity, TProperty> Join<TProperty>(Expression<Func<TEntity, TProperty>> navigationProperty);
    ...
}

los TEntity tipo genérico es el interfaz de mi entidad EF. La implementación de la Join El método en mi repositorio genérico es así:

public abstract class SecureRepository<TInterface, TEntity> : IRepository<TInterface>
    where TEntity : class, new()
    where TInterface : class
{
    protected DbSet<TEntity> DbSet;
    protected SecureRepository(DataContext dataContext)
    {
        DbSet = dataContext.Set<TEntity>();
    }

    public virtual IIncludableJoin<TInterface, TProperty> Join<TProperty>(Expression<Func<TInterface, TProperty>> navigationProperty)
    {
        return ((IQueryable<TInterface>)DbSet).Join(navigationProperty);
    }
    ...
}

Ahora, para la parte que realmente permite múltiples Include y ThenInclude. Tengo varios métodos de extensión que toman y regresan y IIncludableJoin para permitir el encadenamiento de métodos. Dentro del cual llamo al EF Include y ThenInclude métodos en el DbSet:

public static class RepositoryExtensions
{
    public static IIncludableJoin<TEntity, TProperty> Join<TEntity, TProperty>(
        this IQueryable<TEntity> query,
        Expression<Func<TEntity, TProperty>> propToExpand)
        where TEntity : class
    {
        return new IncludableJoin<TEntity, TProperty>(query.Include(propToExpand));
    }

    public static IIncludableJoin<TEntity, TProperty> ThenJoin<TEntity, TPreviousProperty, TProperty>(
         this IIncludableJoin<TEntity, TPreviousProperty> query,
         Expression<Func<TPreviousProperty, TProperty>> propToExpand)
        where TEntity : class
    {
        IIncludableQueryable<TEntity, TPreviousProperty> queryable = ((IncludableJoin<TEntity, TPreviousProperty>)query).GetQuery();
        return new IncludableJoin<TEntity, TProperty>(queryable.ThenInclude(propToExpand));
    }

    public static IIncludableJoin<TEntity, TProperty> ThenJoin<TEntity, TPreviousProperty, TProperty>(
        this IIncludableJoin<TEntity, IEnumerable<TPreviousProperty>> query,
        Expression<Func<TPreviousProperty, TProperty>> propToExpand)
        where TEntity : class
    {
        var queryable = ((IncludableJoin<TEntity, IEnumerable<TPreviousProperty>>)query).GetQuery();
        var include = queryable.ThenInclude(propToExpand);
        return new IncludableJoin<TEntity, TProperty>(include);
    }
}

En estos métodos obtengo el interno IIncludableQueryable propiedad utilizando el mencionado GetQuery método, llamando al relevante Include o ThenInclude método, luego devolviendo un nuevo IncludableJoin objeto para soportar el método de encadenamiento.

Y eso es. El uso de esto es así:

IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);

Lo anterior cargaría la base Account entidad, es hijo uno a uno Subscription, es una lista de niños de uno a muchos Addresses y es niño Address. Cada función lambda en el camino está fuertemente tipada y es compatible con intellisense para mostrar las propiedades disponibles en cada entidad.

Puedes cambiarlo a algo como esto:

public virtual TEntity GetById<TEntity>(int id, Func<IQueryable<TEntity>, IQueryable<TEntity>> func) 
{
    DbSet<TEntity> result = this.Set<TEntity>();

    IQueryable<TEntity> resultWithEagerLoading = func(result);

    return resultWithEagerLoading.FirstOrDefault(e => e.Id == id);
}

Y puedes usarlo así:

productRepository.GetById(2, x => x.Include(p => p.Orders)
                                   .ThenInclude(o => o.LineItems)
                                   .Include(p => p.Parts))
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *