Pergunta

Eu estou querendo saber como lidar corretamente com problema ansioso-loading para gráficos de objetos complexos quando usando o padrão Repository. Isso não é problema específico ORM eu acho.

Primeira tentativa:

public interface IProductRepository : IRepository<Product>
{
  Product GetById(int id);
  IProductRepository WithCustomers();
}

Isso poderia funcionar bem, mas isso implicaria repetir-me o tempo todo (escrevendo personalizado 'com' métodos em implementações de repositórios em todos os lugares).

A seguir abordagem:

public interface IRepository<T> where T : IAggregateRoot
{
  ...
  void With(Expression<Func<T, object>> propToExpand);
}

método With irá adicionar um item à coleção particular que será usado mais tarde para descobrir o que adereços deve estar ansioso carregado ao recuperar necessárias entidade / s.

obras um deste tipo e é muito bem. Mas eu não gosto de uso:

productRepository.With(x=>x.Customer);
productRepository.With(x=>x.Price);
productRepository.With(x=>x.Manufacturer);
var product = productRepository.GetById(id);

Basicamente - o problema é que não há encadeamento. Eu gostaria que fosse assim:

var product = productRepository
  .With(x=>x.Customer)
  .With(x=>x.Price)
  .With(x=>x.Manufacturer)
  .GetById(id);

eu não poderia conseguir isso . Mesmo se eu pudesse -. Eu não tenho certeza se essa solução seria elegante

Isto leva a pensamentos que eu estou faltando algo fundamental (falta de exemplos em qualquer lugar). São maneiras há diferentes como lidar com isso? Quais são as melhores práticas?

Foi útil?

Solução

problema interessante e eu tenho certeza que você não é o primeiro a ter problemas com isso (eu absolutelty ter).

Para mim, a verdadeira questão é: onde você quer colocar sua lógica de carregamento ansioso?

Do lado de fora do repositório no código do cliente

var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);

Eu não acho que é bom design de software:. Parece que isso poderia causar "morte por mil cortes" se tais construções estão espalhadas através de seu aplicativo inteiro

Ou dentro do repositório . Exemplo:

interface IProductRepository {
    Product GetById(int id);
    Product GetByIdWithCustomers(int i);
}

Portanto, o seu código de cliente ficaria assim:

var product = productRepository.GetByIdWithCustomers(id);

Normalmente eu fazer um BaseRepository que tem apenas as operações básicas CRUD definido:

public class BaseRepository<TEntity, TPrimaryKey> {
    public void Save(TEntity entity) { ... }
    public void Delete(TEntity entity) { ... }
    public TEntity Load(TPrimaryKey id) { ... } // just gets the entity by primary key
}

Em seguida, estendo esta base de Classe / interface, a fim de proporcionar métodos específicos para a recuperação de objectos de domínio. Sua abordagem parece ir em uma direção um pouco semelhante.

public class MediaRepository : BaseRepository<Media, int> {
    public long CountMediaWithCategories() { ... }
    public IList<Media> MediaInCategories(IList<Category> categories) { .... }
}

A coisa boa: todo o material ORM (ansioso carregamento de configuração, buscar profundidade etc) é encapsulado na classe Repository, o código do cliente só fica o conjunto de resultados.

Eu tentei trabalhar com repositórios muito genéricas como você está tentando fazer, mas principalmente eu acabei escrevendo consultas e repositórios específicos para os meus objetos de domínio.

Outras dicas

var product = productRepository
 .With(x=>x.Customer)
 .With(x=>x.Price)
 .With(x=>x.Manufacturer)
 .GetById(id);

Eu posso entender o seu desejo de determinar a profundidade de consulta do objeto gráfico, como acima, mas eu acho que pode haver uma maneira mais fácil de fazê-lo. Que tal em vez de escolher para devolver um produto (com o Cliente, preço e Manufacturer) por ID i simplesmente devolver o Produto? - e todas essas outras coisas são propriedades carregadas preguiçosos de produtos

eu conseguir isso 'acessibilidade grafo completo' por 'encadeamento' pelo modelo de objeto POCO na minha camada de acesso a dados. Desta forma eu não preciso saber a quantidade de dados carregados ansiosos para retirar a qualquer momento, eu só pedir o que eu preciso do gráfico do objeto, eo modelo sabe o que é carregado e que precisa recuperar adicionalmente a partir da DAL. Dê uma olhada em estes três respostas - Eu tento explicar a minha abordagem lá. Se precisar de mais esclarecimentos me avise e eu vou editar esta resposta.

É uma questão de idade, mas talvez ele pode ajudar alguém. Eu passei algum tempo para encontrar um bom aproach, aqui é o que eu encontrei em C #:

IRepository.cs:

public interface IRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , params Expression<Func<TEntity, object>>[] properties);
}

Repository.cs

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{    
    private readonly DbSet<TEntity> _dbset;

    public Repository(DbSet<TEntity> dbset)
    {
        _dbset = dbset;
    }

    public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , Expression<Func<TEntity, object>>[] properties)
    {
        if (where == null) 
            throw new ArgumentNullException(nameof(where));    
        if (properties == null) 
            throw new ArgumentNullException(nameof(properties));

        var query = _dbset as IQueryable<TEntity>; // _dbSet = dbContext.Set<TEntity>()

        query = properties
                   .Aggregate(query, (current, property) => current.Include(property));

        return query.AsNoTracking().Where(where).ToList();
    }
}

Como usar:

var repository = new Repository<User>();
var users = repository.GetAll(p => p.Id == 1, d => d.Address, d => d.Carts);

Ref: Fazer a ligação

Eu posso apreciar o que você está tentando fazer, mas você está um pouco além do repositório-padrão básico.

A interface de repositório mínima pode incluir métodos para:

  • GetById
  • Adicionar
  • Remover

Se você adicionar métodos adicionais em cima disso, você começar a correr em situações onde a interface não necessariamente fazem sentido para todas as suas raízes agregadas.

Às vezes é apenas não é viável ter um completamente bela API. Se o que você tem obras "suficientemente bom" para você, eu iria com ele. Se você precisa ficar longe do padrão de repositório para proporcionar uma melhor API para o programa contra, fazê-lo!

O padrão de repositório não é um ser-tudo extremidade-toda solução /. Às vezes você precisa de uma solução diferente.

Se você quiser indicar toda a Inclui você precisa fora de seu repositório, você pode listar parâmetros opcionais (C #) para cada método genérico:

TEntity Find(Func<TEntity, bool> expression, params string[] eagerLoads);

Em seguida, em seu camada do cliente:

IProductRepository.Find(x => x.Id == id, "Customer", "Price")

Se você quer ser tipo seguro, enumerar suas entidades:

public enum BusinessEntities { Customer, Price, Manufacturer }

IProductRepository.Find(x => x.Id == id, BusinessEntities.Customer.ToString(), BusinessEntities.Price.ToString())

Eu acho que é responsabilidade do cliente para pedir especificamente para o que quer. Repositório genérico deve apenas lidar com CRUD básico.

No BaseRepository.cs você pode criar esse método:

public async Task<IEnumerable<T>> GetWithChild(string child)
{
    return await _entities.Include(child).ToListAsync();
}

Na minha API eu também implementou uma camada de serviço, mas a partir da API eu simplesmente chamar esse método e passá-lo o nome da variável a carga.

Obviamente, em sua situação, você precisa incluir mais algumas cordas.

Eu postei uma resposta mais cedo, mas eu ainda não estava satisfeito com a solução. Então aqui está uma solução melhor.

nos BaseRepository.cs

public async Task<IEnumerable<T>> GetAll(params Expression<Func<T, object>>[] properties)
{
      IQueryable<T> query = _entities;

      query = properties.Aggregate(query, (current, property) => current.Include(property));

      return await query.AsNoTracking().ToListAsync();
}

e você pode simplesmente usar o método da seguinte maneira

await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer); 
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top