Question

Je me demande comment gérer correctement le problème de chargement rapide de graphiques d'objets complexes lors de l'utilisation du modèle de référentiel. Ce n'est pas un problème spécifique à ORM, je suppose.

Premier essai:

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

Cela fonctionnerait bien, mais cela impliquerait de me répéter tout le temps (écrire des méthodes personnalisées 'Avec' dans les implémentations de référentiels partout).

Approche suivante:

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

Avec , la méthode ajoutera un élément à une collection privée qui sera utilisée ultérieurement pour déterminer quels accessoires doivent être chargés lors de la récupération de la ou des entités nécessaires.

Ce type fonctionne et va bien. Mais je n'aime pas l'usage:

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

En gros, le problème est qu’il n’ya pas de chaînage. Je voudrais que ce soit comme ça:

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

Je ne pouvais pas atteindre cet objectif . Même si je pouvais - je ne suis pas sûr que cette solution serait élégante.

Cela conduit à penser qu'il me manque quelque chose de fondamental (manque d'exemples n'importe où). Y at-il différentes façons de gérer cela? Quelles sont les meilleures pratiques?

Était-ce utile?

La solution

Problème intéressant et je suis sûr que vous n’êtes pas le premier à avoir des problèmes avec cela (je l’ai absolument).

Pour moi, la vraie question est la suivante: où voulez-vous mettre votre logique de chargement désireuse?

En dehors du référentiel dans le code client

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

Je ne pense pas que ce soit une bonne conception logicielle: il semble que cela pourrait causer "la mort par mille coupures". si de telles constructions sont dispersées dans l’ensemble de votre application.

Ou dans le référentiel . Exemple:

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

Ainsi, votre code client ressemblerait à ceci:

var product = productRepository.GetByIdWithCustomers(id);

Normalement, je crée un référentiel de base contenant uniquement les opérations CRUD de base:

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
}

Ensuite, j'étend cette classe / interface de base afin de fournir des méthodes spécifiques pour récupérer des objets de domaine. Votre approche semble aller dans une direction quelque peu similaire.

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

La bonne chose: tous les éléments ORM (configuration de chargement rapide, profondeur d'extraction, etc.) sont encapsulés dans la classe Repository, le code client obtient simplement le jeu de résultats.

J'ai essayé de travailler avec des référentiels très génériques, comme vous essayez de le faire, mais j'ai surtout fini par écrire des requêtes et des référentiels spécifiques pour mes objets de domaine.

Autres conseils

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

Je peux comprendre votre souhait de déterminer la profondeur de la requête du graphe d'objet comme ci-dessus, mais je pense qu'il pourrait y avoir un moyen plus simple de le faire. Que diriez-vous plutôt que de choisir de retourner un produit (avec le client, le prix et le fabricant) par ID, je renvoie simplement le produit - et toutes ces autres choses sont des propriétés de produit chargées paresseux?

J'obtiens cette "accessibilité complète des graphes" en "enchaînant" le modèle d'objet POCO dans ma couche d'accès aux données. De cette façon, je n'ai pas besoin de savoir combien de données chargées doivent être extraites à un moment donné, je demande simplement ce dont j'ai besoin dans le graphe d'objets, et le modèle sait ce qui est chargé et ce qui doit être récupéré en plus à partir du DAL. Jetez un coup d'œil à ces trois réponses - j’essaie d’expliquer mon approche. Si vous avez besoin de précisions, faites-le moi savoir et je modifierai cette réponse.

C'est une vieille question, mais peut-être que cela peut aider quelqu'un. J'ai passé quelque temps à trouver une bonne approche, voici ce que j'ai trouvé en 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();
    }
}

Comment utiliser:

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

Réf.: Lien

Je peux comprendre ce que vous essayez de faire, mais vous êtes un peu au-delà du schéma de base du référentiel.

Une interface de référentiel minimal peut inclure des méthodes pour:

  • GetById
  • Ajouter
  • Supprimer

Si vous ajoutez des méthodes supplémentaires, vous commencez à vous trouver dans des situations où l'interface n'a pas nécessairement de sens pour toutes vos racines agrégées.

Parfois, il est tout simplement impossible d’avoir une API complètement magnifique. Si ce que vous avez fonctionne "assez bien" pour toi, j'irais avec. Si vous devez vous écarter du modèle de référentiel pour fournir une meilleure API contre laquelle programmer, faites-le!

Le modèle de référentiel n'est pas une solution miracle. Parfois, vous avez besoin d'une solution différente.

Si vous souhaitez indiquer tous les inclus dont vous avez besoin en dehors de votre référentiel, vous pouvez répertorier les paramètres facultatifs (C #) pour chaque méthode générique:

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

Ensuite, sur votre niveau client:

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

Si vous voulez être sûrs de votre saisie, énumérez vos entités:

public enum BusinessEntities { Customer, Price, Manufacturer }

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

Je pense qu'il incombe au client de demander spécifiquement ce qu'il veut. Le référentiel générique doit juste gérer le CRUD de base.

Dans le BaseRepository.cs , vous pouvez créer cette méthode:

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

Dans mon API, j'ai également implémenté une couche de service, mais j'appelle simplement cette méthode et lui transmet le nom de la variable à charger.

Évidemment, dans votre cas, vous devrez inclure quelques chaînes supplémentaires.

J'ai posté une réponse plus tôt mais je n'étais toujours pas satisfait de la solution. Voici donc une meilleure solution.

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

et vous pouvez simplement utiliser la méthode comme suit

await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer); 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top