Domanda

Mi chiedo come gestire correttamente il problema di caricamento impaziente per grafici di oggetti complessi quando si utilizza il modello Repository. Questo non è un problema specifico di ORM, immagino.

Primo tentativo:

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

Funzionerebbe benissimo, ma implicherebbe ripetermi continuamente (scrivere metodi personalizzati "With" nelle implementazioni di repository ovunque).

Prossimo approccio:

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

With aggiungerà un oggetto alla collezione privata che verrà utilizzato in seguito per scoprire quali oggetti di scena dovrebbero essere caricati con impazienza quando si recuperano le entità necessarie.

Questo tipo funziona e va bene. Ma non mi piace l'uso:

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

Fondamentalmente - il problema è che non c'è il concatenamento. Vorrei che fosse così:

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

Non ci sono riuscito . Anche se potessi - non sono sicuro che quella soluzione sarebbe elegante.

Questo porta a pensare che mi manchi qualcosa di fondamentale (mancanza di esempi ovunque). Esistono diversi modi per gestirlo? Quali sono le migliori pratiche?

È stato utile?

Soluzione

Problema interessante e sono sicuro che non sei il primo ad avere problemi con questo (ne ho assolutamente).

Per me, la vera domanda è: dove vuoi mettere la tua logica di caricamento desideroso?

Al di fuori del repository nel codice client

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

Non penso che sia un buon design del software: sembra che questo potrebbe causare "la morte di mille tagli" se tali costrutti sono sparsi in tutta la tua app.

O all'interno del repository . Esempio:

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

Quindi il tuo codice client sarebbe simile al seguente:

var product = productRepository.GetByIdWithCustomers(id);

Normalmente faccio un BaseRepository che ha solo le operazioni CRUD di base definite:

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
}

Quindi estendo questa Classe / Interfaccia di base al fine di fornire metodi specifici per il recupero di oggetti di dominio. Il tuo approccio sembra andare in una direzione un po 'simile.

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

La cosa buona: tutto il materiale ORM (impaziente caricamento config, profondità di recupero ecc.) è incapsulato nella classe Repository, il codice client ottiene solo il set di risultati.

Ho provato a lavorare con repository molto generici come te stai cercando di fare, ma alla fine ho finito per scrivere query e repository specifici per i miei oggetti di dominio.

Altri suggerimenti

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

Posso capire il tuo desiderio di determinare la profondità della query del grafico dell'oggetto come sopra, ma penso che potrebbe esserci un modo più semplice per farlo. Che ne dite invece di scegliere di restituire un prodotto (con cliente, prezzo e produttore) per ID semplicemente restituisco il prodotto - e tutte quelle altre cose sono proprietà caricate in modo pigro del prodotto?

Ottengo questa "completa accessibilità al grafico" "concatenando" il modello a oggetti POCO nel mio livello di accesso ai dati. In questo modo non ho bisogno di sapere quanti dati caricati desiderosi estrarre in qualsiasi momento, chiedo solo ciò di cui ho bisogno dal grafico a oggetti e il modello sa cosa viene caricato e cosa deve essere recuperato ulteriormente dal DAL. Dai un'occhiata a these tre risposte - cerco di spiegare il mio approccio lì. Se hai bisogno di ulteriori chiarimenti, fammelo sapere e io modificherò questa risposta.

È una vecchia domanda ma forse può aiutare qualcuno. Ho passato un po 'di tempo a trovare un buon approccio, ecco cosa ho trovato in 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();
    }
}

Come usare:

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

Rif: Link

Posso apprezzare ciò che stai cercando di fare, ma sei un po 'al di là del modello base di repository.

Un'interfaccia di repository minima può includere metodi per:

  • GetById
  • Aggiungi
  • Rimuovi

Se aggiungi altri metodi, inizi a imbatterti in situazioni in cui l'interfaccia non ha necessariamente senso per tutte le tue radici aggregate.

A volte non è possibile avere un'API completamente bella. Se quello che hai funziona "abbastanza buono" per te, ci andrei. Se devi allontanarti dal modello di repository per fornire un'API migliore su cui programmare, fallo!

Il modello di repository non è una soluzione be-all / end-all. A volte hai bisogno di una soluzione diversa.

Se vuoi indicare tutte le Include di cui hai bisogno al di fuori del tuo repository, puoi elencare parametri opzionali (C #) per ogni metodo generico:

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

Quindi sul tuo livello client:

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

Se vuoi essere sicuro, enumera le tue entità:

public enum BusinessEntities { Customer, Price, Manufacturer }

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

Penso che sia responsabilità del cliente chiedere specificamente ciò che desidera. Il repository generico dovrebbe semplicemente gestire il CRUD di base.

Nel BaseRepository.cs puoi creare questo metodo:

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

Nella mia API ho anche implementato un livello di servizio ma dall'API chiamo semplicemente questo metodo e gli passo il nome della variabile da caricare.

Ovviamente, nella tua situazione, dovrai includere qualche stringa in più.

Ho pubblicato una risposta in precedenza ma non ero ancora soddisfatto della soluzione. Quindi ecco una soluzione migliore.

in 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 puoi semplicemente usare il metodo come segue

await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer); 
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top